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

Fix ics20 denom #244

Merged
merged 2 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤗 Makes debugging for me so much easier

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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing errors should be app level and stored with the event attribute. I may have run into this with an old state and could not read the event.


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to check all part. 👍

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