Skip to content

Commit

Permalink
feat(minor-axelarnet-gateway): call contract with token to nexus modu…
Browse files Browse the repository at this point in the history
…le (#646)

Co-authored-by: Sammy <[email protected]>
  • Loading branch information
haiyizxx and fish-sammy authored Oct 9, 2024
1 parent b36fb4a commit 7ea208f
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 162 deletions.
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(
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)
}
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

0 comments on commit 7ea208f

Please sign in to comment.