Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(minor-axelarnet-gateway): call contract with token to nexus module #646

Merged
merged 10 commits into from
Oct 9, 2024
15 changes: 8 additions & 7 deletions contracts/axelarnet-gateway/src/clients/external.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, CosmosMsg, HexBinary, QuerierWrapper};
use cosmwasm_std::{Addr, CosmosMsg, Empty, HexBinary, QuerierWrapper};
use router_api::{Address, CrossChainId};

/// `AxelarExecutableMsg` is a struct containing the args used by the axelarnet gateway to execute a destination contract on Axelar.
Expand All @@ -19,26 +19,26 @@ enum ExecuteMsg {
Execute(AxelarExecutableMsg),
}

pub struct Client<'a> {
client: client::ContractClient<'a, ExecuteMsg, ()>,
pub struct Client<'a, T = Empty> {
client: client::ContractClient<'a, ExecuteMsg, (), T>,
}

impl<'a> Client<'a> {
impl<'a, T> Client<'a, T> {
pub fn new(querier: QuerierWrapper<'a>, destination: &'a Addr) -> Self {
Client {
client: client::ContractClient::new(querier, destination),
}
}

pub fn execute(&self, msg: AxelarExecutableMsg) -> CosmosMsg {
pub fn execute(&self, msg: AxelarExecutableMsg) -> CosmosMsg<T> {
self.client.execute(&ExecuteMsg::Execute(msg))
}
}

#[cfg(test)]
mod test {
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::{to_json_binary, Addr, HexBinary, WasmMsg};
use cosmwasm_std::{to_json_binary, Addr, Empty, HexBinary, WasmMsg};
use router_api::CrossChainId;

use crate::clients::external;
Expand All @@ -55,7 +55,8 @@ mod test {
cc_id: CrossChainId::new("source-chain", "message-id").unwrap(),
};

let client = external::Client::new(deps.as_ref().querier, &destination_addr);
let client: external::Client<'_, Empty> =
external::Client::new(deps.as_ref().querier, &destination_addr);

assert_eq!(
client.execute(executable_msg.clone()),
Expand Down
2 changes: 1 addition & 1 deletion contracts/axelarnet-gateway/src/clients/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ mod test {
let msg = InstantiateMsg {
chain_name: "source-chain".parse().unwrap(),
router_address: "router".to_string(),
nexus_gateway: "nexus-gateway".to_string(),
nexus: "nexus".to_string(),
};

instantiate(deps, env, info, msg.clone()).unwrap();
Expand Down
5 changes: 3 additions & 2 deletions contracts/axelarnet-gateway/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use axelar_core_std::nexus;
use axelar_wasm_std::error::ContractError;
use axelar_wasm_std::{address, FnExt, IntoContractError};
#[cfg(not(feature = "library"))]
Expand Down Expand Up @@ -55,7 +56,7 @@ pub fn instantiate(
let config = Config {
chain_name: msg.chain_name,
router: address::validate_cosmwasm_address(deps.api, &msg.router_address)?,
nexus_gateway: address::validate_cosmwasm_address(deps.api, &msg.nexus_gateway)?,
nexus: address::validate_cosmwasm_address(deps.api, &msg.nexus)?,
};

state::save_config(deps.storage, &config)?;
Expand All @@ -68,7 +69,7 @@ pub fn execute(
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response<nexus::execute::Message>, ContractError> {
match msg.ensure_permissions(deps.storage, &info.sender)? {
ExecuteMsg::CallContract {
destination_chain,
Expand Down
118 changes: 86 additions & 32 deletions contracts/axelarnet-gateway/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::iter;
use std::str::FromStr;

use axelar_core_std::nexus;
Expand All @@ -6,10 +7,9 @@ use axelar_wasm_std::token::GetToken;
use axelar_wasm_std::{address, FnExt, IntoContractError};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
to_json_binary, Addr, DepsMut, HexBinary, MessageInfo, QuerierWrapper, Response, Storage,
WasmMsg,
Addr, BankMsg, Coin, DepsMut, HexBinary, MessageInfo, QuerierWrapper, Response, Storage,
};
use error_stack::{bail, ensure, report, Result, ResultExt};
use error_stack::{bail, ensure, report, ResultExt};
use itertools::Itertools;
use router_api::client::Router;
use router_api::{Address, ChainName, CrossChainId, Message};
Expand Down Expand Up @@ -53,6 +53,8 @@ pub enum Error {
NonceOverflow,
#[error("invalid token received")]
InvalidToken,
#[error("invalid routing destination")]
InvalidRoutingDestination,
}

#[cw_serde]
Expand All @@ -74,30 +76,28 @@ impl CallContractData {
}
}

enum RoutingDestination {
Nexus,
Router,
}

type Result<T> = error_stack::Result<T, Error>;

pub fn call_contract(
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
storage: &mut dyn Storage,
querier: QuerierWrapper,
info: MessageInfo,
call_contract: CallContractData,
) -> Result<Response, Error> {
) -> Result<Response<nexus::execute::Message>> {
let Config {
router,
chain_name,
nexus_gateway,
nexus,
} = state::load_config(storage);

let client: nexus::Client = client::CosmosClient::new(querier).into();
let nexus::query::TxHashAndNonceResponse { tx_hash, nonce } =
client.tx_hash_and_nonce().change_context(Error::Nexus)?;

let id = CrossChainId::new(
chain_name,
HexTxHashAndEventIndex::new(
tx_hash,
u32::try_from(nonce).change_context(Error::NonceOverflow)?,
),
)
.change_context(Error::InvalidCrossChainId)?;
let id = unique_cross_chain_id(&client, chain_name)?;
let source_address = Address::from_str(info.sender.as_str())
.change_context(Error::InvalidSourceAddress(info.sender.clone()))?;
let msg = call_contract.to_message(id, source_address);
Expand All @@ -112,14 +112,13 @@ pub fn call_contract(
payload: call_contract.payload,
token: token.clone(),
};
let res = match token {
None => route_to_router(storage, &Router::new(router), vec![msg])?,
Some(token) => Response::new().add_message(WasmMsg::Execute {
contract_addr: nexus_gateway.to_string(),
msg: to_json_binary(&nexus_gateway::msg::ExecuteMsg::RouteMessageWithToken(msg))
.expect("failed to serialize route message with token"),
funds: vec![token],
}),

let res = match determine_routing_destination(&client, &msg.destination_chain)? {
RoutingDestination::Nexus => route_to_nexus(&client, nexus, msg, token)?,
RoutingDestination::Router if token.is_none() => {
route_to_router(storage, &Router::new(router), vec![msg])?
}
_ => bail!(Error::InvalidRoutingDestination),
}
.add_event(event.into());

Expand All @@ -130,7 +129,7 @@ pub fn route_messages(
storage: &mut dyn Storage,
sender: Addr,
msgs: Vec<Message>,
) -> Result<Response, Error> {
) -> Result<Response<nexus::execute::Message>> {
let Config {
chain_name, router, ..
} = state::load_config(storage);
Expand All @@ -144,7 +143,11 @@ pub fn route_messages(
}
}

pub fn execute(deps: DepsMut, cc_id: CrossChainId, payload: HexBinary) -> Result<Response, Error> {
pub fn execute(
deps: DepsMut,
cc_id: CrossChainId,
payload: HexBinary,
) -> Result<Response<nexus::execute::Message>> {
let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into();
let msg = state::mark_as_executed(
deps.storage,
Expand All @@ -163,6 +166,7 @@ pub fn execute(deps: DepsMut, cc_id: CrossChainId, payload: HexBinary) -> Result
.change_context(Error::InvalidDestinationAddress(
msg.destination_address.to_string(),
))?;

Response::new()
.add_message(external::Client::new(deps.querier, &destination).execute(executable_msg))
.add_event(AxelarnetGatewayEvent::MessageExecuted { msg }.into())
Expand Down Expand Up @@ -195,7 +199,7 @@ fn prepare_msgs_for_execution(
store: &mut dyn Storage,
chain_name: ChainName,
msgs: Vec<Message>,
) -> Result<Response, Error> {
) -> Result<Response<nexus::execute::Message>> {
for msg in msgs.iter() {
ensure!(
chain_name == msg.destination_chain,
Expand All @@ -218,9 +222,9 @@ fn prepare_msgs_for_execution(
/// Route messages to the router, ignore unknown messages.
fn route_to_router(
store: &mut dyn Storage,
router: &Router,
router: &Router<nexus::execute::Message>,
msgs: Vec<Message>,
) -> Result<Response, Error> {
) -> Result<Response<nexus::execute::Message>> {
let msgs: Vec<_> = msgs
.into_iter()
.unique()
Expand All @@ -238,10 +242,7 @@ fn route_to_router(

/// Verify that the message is stored and matches the one we're trying to route. Returns Ok(None) if
/// the message is not stored.
fn try_load_executable_msg(
store: &mut dyn Storage,
msg: Message,
) -> Result<Option<Message>, Error> {
fn try_load_executable_msg(store: &mut dyn Storage, msg: Message) -> Result<Option<Message>> {
let stored_msg = state::may_load_routable_msg(store, &msg.cc_id)
.change_context(Error::ExecutableMessageAccess)?;

Expand All @@ -253,3 +254,56 @@ fn try_load_executable_msg(
None => Ok(None),
}
}

/// Query Nexus module in core to generate an unique cross chain id.
fn unique_cross_chain_id(client: &nexus::Client, chain_name: ChainName) -> Result<CrossChainId> {
let nexus::query::TxHashAndNonceResponse { tx_hash, nonce } =
client.tx_hash_and_nonce().change_context(Error::Nexus)?;

CrossChainId::new(
chain_name,
HexTxHashAndEventIndex::new(
tx_hash,
u32::try_from(nonce).change_context(Error::NonceOverflow)?,
),
)
.change_context(Error::InvalidCrossChainId)
}

/// Query Nexus module in core to decide should route message to core
fn determine_routing_destination(
client: &nexus::Client,
name: &ChainName,
) -> Result<RoutingDestination> {
let dest = match client
.is_chain_registered(name)
.change_context(Error::Nexus)?
{
true => RoutingDestination::Nexus,
false => RoutingDestination::Router,
};

Ok(dest)
}

/// Route message to the Nexus module
fn route_to_nexus(
client: &nexus::Client,
nexus: Addr,
msg: Message,
token: Option<Coin>,
) -> Result<Response<nexus::execute::Message>> {
let msg: nexus::execute::Message = (msg, token.clone()).into();

token
.map(|token| BankMsg::Send {
to_address: nexus.to_string(),
amount: vec![token],
})
.map(Into::into)
.into_iter()
.chain(iter::once(client.route_message(msg)))
.collect::<Vec<_>>()
.then(|msgs| Response::new().add_messages(msgs))
.then(Ok)
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 4 additions & 3 deletions contracts/axelarnet-gateway/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pub struct InstantiateMsg {
pub chain_name: ChainName,
/// Address of the router contract on axelar.
pub router_address: String,
/// Address of the nexus gateway contract on axelar.
pub nexus_gateway: String,
/// Address of the nexus module account on axelar.
pub nexus: String,
}

#[cw_serde]
Expand All @@ -33,7 +33,8 @@ pub enum ExecuteMsg {
},

/// Initiate a cross-chain contract call from Axelarnet to another chain.
/// The message will be routed to the destination chain's gateway via the router.
/// If the destination chain is registered with core, the message will be routed to core with an optional token.
/// Otherwise, the message will be routed to the destination chain's gateway via the router.
#[permission(Any)]
CallContract {
destination_chain: ChainName,
Expand Down
2 changes: 1 addition & 1 deletion contracts/axelarnet-gateway/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub enum Error {
pub struct Config {
pub chain_name: ChainName,
pub router: Addr,
pub nexus_gateway: Addr,
pub nexus: Addr,
}

#[cw_serde]
Expand Down
Loading
Loading