Skip to content

Commit

Permalink
Merge pull request #244 from CosmWasm/fix-ics20-denom
Browse files Browse the repository at this point in the history
Fix ics20 denom
  • Loading branch information
ethanfrey authored Mar 12, 2021
2 parents 851e3b9 + 27ecc50 commit 1be56bf
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 13 deletions.
16 changes: 14 additions & 2 deletions contracts/cw20-ics20/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Response, ContractError> {
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<Binary> {
match msg {
Expand Down
12 changes: 12 additions & 0 deletions contracts/cw20-ics20/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FromUtf8Error> for ContractError {
Expand Down
55 changes: 45 additions & 10 deletions contracts/cw20-ics20/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -142,19 +142,23 @@ pub fn ibc_packet_receive(
_env: Env,
packet: IbcPacket,
) -> Result<IbcReceiveResponse, Never> {
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(),
Expand All @@ -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<Ics20Packet, ContractError> {
fn do_ibc_packet_receive(deps: DepsMut, packet: &IbcPacket) -> Result<Ics20Packet, ContractError> {
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 {})?;
Expand Down Expand Up @@ -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 <port>/<channel>/<denom>
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 {
Expand Down
5 changes: 4 additions & 1 deletion contracts/cw20-ics20/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 1be56bf

Please sign in to comment.