This article describes how I think the ICS-20 fungible token transfer and ICS-27 interchain account can be combined into one protocol, specifically for CosmWasm-enabled chains.
For simplicity, let's call this new standard "ICS-20e" (e = extended). ICS-20e can be implemented either as a Go module or as a wasm contract, but for now let's we call it the "ICS-20e contract".
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Binary, Coin, Uint128};
#[cw_serde]
struct PacketData {
sender: String,
coins: Vec<Coin>,
msg: PacketMsg,
}
#[cw_serde]
enum PacketMsg {
NoAction {
recipient: String,
},
Instantiate {
code_id: u64,
msg: Binary,
label: String,
admin: Option<String>,
},
Execute {
contract_addr: String,
msg: Binary,
},
Migrate {
contract_addr: String,
code_id: u64,
msg: Binary,
},
}
Firstly, while the current ICS-20 standard only allows one coin per packet, with ICS-20e it is possible to batch many tokens into a single packet.
Secondly, the packet contains a msg
parameter. While similar to the current ICS-20's memo
, it specifies that the msg must be one of the variants: no action, instantiate, execute, or migrate.
An ICA is a wasm contract that implements the following execute method:
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Respoonse, StdError, WasmMsg};
use cw_storage_plus::Item;
const ICS20E_CONTRACT: Item<Addr> = Item::new("ics20e_contract");
#[entry_point]
fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: WasmMsg,
) -> Result<Response> {
if info.sender != ICS20E_CONTRACT.load(deps.storage)? {
return Err(StdError::Unauthorized {});
}
Ok(Response::new().add_message(WasmMsg))
}
See we see, the ICA contract accepts an arbitrary WasmMsg
, verifies that it is from the ICS-20e contract, then simply dispatches it.
When a packet is received, the action to take depends on the msg
parameter. If it is Ics20Msg::NoAction
, then the transfer is handled similarly to the current ICS-20: IOU tokens with the appropriate ibc/*
denoms are minted to the recipient
, with no further action.
If the msg is Instantiate
, Execute
, or Migrate
, the module first checks whether the sender has an ICA contract associated with it. If no, one will be instantiated and the address recorded in the following map:
use cw_storage_plus::Map;
/// (connection id, sender address) => interchain account address
const INTERCHAIN_ACCOUNTS: Map<(&str, &str), Addr> = Map::new("icas");
Once the ICA is created, the ICS-20e contract will translate the ICS20Msg
to WasmMsg
, and send it to the ICA contract:
use cosmwasm_std::BankMsg;
fn handle_packet(packet: PacketData, ica_addr: &Addr) -> Result<Response> {
// convert denoms on the source chain to the denom traces on the recipient
// chain
let coins: Vec<Coins> = to_denom_traces(&packet.coins)?;
let wasm_msg = match packet.msg {
// if the packet msg is "no action", simply mint the coins to the
// recipient with no further action
Ics20Msg::NoAction {
recipient,
} => {
return Ok(Response::new().add_message(BankMsg::Send {
to_address: recipient,
amount: coins,
}));
},
// if the packet msg is "instantiate", translate it to a `WasmMsg::Instantiate`
IcsMsg::Instantiate {
code_id,
msg,
label,
admin,
} => WasmMsg::Instantiate {
admin,
code_id,
msg,
funds: coins,
},
// same with execute and migrate
IcsMsg::Execute {
contract_addr,
msg,
} => WasmMsg::Execute {
contract_addr,
msg,
funds: coins,
},
IcsMsg::Migrate {
contract_addr,
new_code_id,
msg,
} => {
if !coin.is_empty() {
return Err(StdError::generic_err(
"contract migration does not support funds",
));
}
WasmMsg::Migrate {
contract_addr,
new_code_id,
msg,
}
},
}
// send the translated WasmMsg to the ICA
Ok(Response::new().add_message(WasmMsg::Execute {
contract_addr: ica_addr.to_string(),
msg: to_binary(&wasm_msg)?,
funds: coins,
}))
}
As we see, this standard combines both ICS-20 and ICS-27, allowing transferring multiple tokens to an account on another chain, then (optionally) instantiate/execute/migrate a wasm contract with those tokens.
Compared to the Osmosis module:
- individual ICA address for each sender
- supports instantiate and migrate in addition to execute
- supports multiple coins in one packet