diff --git a/contracts/cw20-ics20/src/contract.rs b/contracts/cw20-ics20/src/contract.rs index ac0e26894..2be1f3b4b 100644 --- a/contracts/cw20-ics20/src/contract.rs +++ b/contracts/cw20-ics20/src/contract.rs @@ -3,14 +3,15 @@ use cosmwasm_std::{ IbcQuery, MessageInfo, Order, PortIdResponse, Response, StdResult, }; -use cw2::set_contract_version; +use cw2::{get_contract_version, set_contract_version}; use cw20::{Cw20CoinHuman, Cw20ReceiveMsg}; use crate::amount::Amount; use crate::error::ContractError; use crate::ibc::Ics20Packet; use crate::msg::{ - ChannelResponse, ExecuteMsg, InitMsg, ListChannelsResponse, PortResponse, QueryMsg, TransferMsg, + ChannelResponse, ExecuteMsg, InitMsg, ListChannelsResponse, MigrateMsg, PortResponse, QueryMsg, + TransferMsg, }; use crate::state::{Config, CHANNEL_INFO, CHANNEL_STATE, CONFIG}; use cw0::{nonpayable, one_coin}; @@ -130,6 +131,17 @@ pub fn execute_transfer( Ok(res) } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + let version = get_contract_version(deps.storage)?; + if version.contract != CONTRACT_NAME { + return Err(ContractError::CannotMigrate { + previous_contract: version.contract, + }); + } + Ok(Response::default()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/contracts/cw20-ics20/src/error.rs b/contracts/cw20-ics20/src/error.rs index 297a40305..a3f05a780 100644 --- a/contracts/cw20-ics20/src/error.rs +++ b/contracts/cw20-ics20/src/error.rs @@ -37,6 +37,18 @@ pub enum ContractError { #[error("Insufficient funds to redeem voucher on channel")] InsufficientFunds {}, + + #[error("Only accepts tokens that originate on this chain, not native tokens of remote chain")] + NoForeignTokens {}, + + #[error("Parsed port from denom ({port}) doesn't match packet")] + FromOtherPort { port: String }, + + #[error("Parsed channel from denom ({channel}) doesn't match packet")] + FromOtherChannel { channel: String }, + + #[error("Cannot migrate from different contract type: {previous_contract}")] + CannotMigrate { previous_contract: String }, } impl From for ContractError { diff --git a/contracts/cw20-ics20/src/ibc.rs b/contracts/cw20-ics20/src/ibc.rs index 8ffba8c6a..13e3cc7ae 100644 --- a/contracts/cw20-ics20/src/ibc.rs +++ b/contracts/cw20-ics20/src/ibc.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{ attr, entry_point, from_binary, to_binary, BankMsg, Binary, CosmosMsg, DepsMut, Env, HumanAddr, - IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcOrder, IbcPacket, IbcReceiveResponse, - StdResult, Uint128, WasmMsg, + IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, + IbcReceiveResponse, StdResult, Uint128, WasmMsg, }; use crate::amount::Amount; @@ -142,19 +142,23 @@ pub fn ibc_packet_receive( _env: Env, packet: IbcPacket, ) -> Result { - let res = match do_ibc_packet_receive(deps, packet) { + let res = match do_ibc_packet_receive(deps, &packet) { Ok(msg) => { // build attributes first so we don't have to clone msg below // similar event messages like ibctransfer module + + // This cannot fail as we parse it in do_ibc_packet_receive. Best to pass the data somehow? + let denom = parse_voucher_denom(&msg.denom, &packet.src).unwrap(); + let attributes = vec![ attr("action", "receive"), attr("sender", &msg.sender), attr("receiver", &msg.receiver), - attr("denom", &msg.denom), + attr("denom", denom), attr("amount", msg.amount), attr("success", "true"), ]; - let to_send = Amount::from_parts(msg.denom, msg.amount); + let to_send = Amount::from_parts(denom.into(), msg.amount); let msg = send_amount(to_send, HumanAddr::from(msg.receiver)); IbcReceiveResponse { acknowledgement: ack_success(), @@ -179,15 +183,44 @@ pub fn ibc_packet_receive( Ok(res) } +// Returns local denom if the denom is an encoded voucher from the expected endpoint +// Otherwise, error +fn parse_voucher_denom<'a>( + voucher_denom: &'a str, + remote_endpoint: &IbcEndpoint, +) -> Result<&'a str, ContractError> { + let split_denom: Vec<&str> = voucher_denom.splitn(3, '/').collect(); + if split_denom.len() != 3 { + return Err(ContractError::NoForeignTokens {}); + } + // a few more sanity checks + if split_denom[0] != remote_endpoint.port_id { + return Err(ContractError::FromOtherPort { + port: split_denom[0].into(), + }); + } + if split_denom[1] != remote_endpoint.channel_id { + return Err(ContractError::FromOtherChannel { + channel: split_denom[1].into(), + }); + } + + Ok(split_denom[2]) +} + // this does the work of ibc_packet_receive, we wrap it to turn errors into acknowledgements -fn do_ibc_packet_receive(deps: DepsMut, packet: IbcPacket) -> Result { +fn do_ibc_packet_receive(deps: DepsMut, packet: &IbcPacket) -> Result { let msg: Ics20Packet = from_binary(&packet.data)?; - let channel = packet.dest.channel_id; - let denom = msg.denom.clone(); + let channel = packet.dest.channel_id.clone(); + + // If the token originated on the remote chain, it looks like "ucosm". + // If it originated on our chain, it looks like "port/channel/ucosm". + let denom = parse_voucher_denom(&msg.denom, &packet.src)?; + let amount = msg.amount; CHANNEL_STATE.update( deps.storage, - (&channel, &denom), + (&channel, denom), |orig| -> Result<_, ContractError> { // this will return error if we don't have the funds there to cover the request (or no denom registered) let mut cur = orig.ok_or(ContractError::InsufficientFunds {})?; @@ -391,11 +424,13 @@ mod test { receiver: &str, ) -> IbcPacket { let data = Ics20Packet { - denom: denom.into(), + // this is returning a foreign (our) token, thus denom is // + denom: format!("{}/{}/{}", REMOTE_PORT, "channel-1234", denom), amount: amount.into(), sender: "remote-sender".to_string(), receiver: receiver.to_string(), }; + print!("Packet denom: {}", &data.denom); IbcPacket { data: to_binary(&data).unwrap(), src: IbcEndpoint { diff --git a/contracts/cw20-ics20/src/msg.rs b/contracts/cw20-ics20/src/msg.rs index 744b98fda..6c4e1054b 100644 --- a/contracts/cw20-ics20/src/msg.rs +++ b/contracts/cw20-ics20/src/msg.rs @@ -8,12 +8,15 @@ use cw20::Cw20ReceiveMsg; use crate::amount::Amount; use crate::state::ChannelInfo; -#[derive(Serialize, Deserialize, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] pub struct InitMsg { /// default timeout for ics20 packets, specified in seconds pub default_timeout: u64, } +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +pub struct MigrateMsg {} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg {