Skip to content

Commit

Permalink
Merge pull request #868 from nicolaslara/nicolas/ibc-memo
Browse files Browse the repository at this point in the history
Added optional memo to ics20 packets
  • Loading branch information
ueco-jb authored Apr 20, 2023
2 parents 645c781 + 7176d26 commit a959518
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 1 deletion.
96 changes: 95 additions & 1 deletion contracts/cw20-ics20/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ pub fn execute_transfer(
amount.denom(),
sender.as_ref(),
&msg.remote_address,
);
)
.with_memo(msg.memo);
packet.validate()?;

// Update the balance now (optimistically) like ibctransfer modules.
Expand Down Expand Up @@ -382,6 +383,7 @@ mod test {
use super::*;
use crate::test_helpers::*;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR};
use cosmwasm_std::{coin, coins, CosmosMsg, IbcMsg, StdError, Uint128};

Expand Down Expand Up @@ -431,6 +433,7 @@ mod test {
channel: send_channel.to_string(),
remote_address: "foreign-address".to_string(),
timeout: None,
memo: None,
};

// works with proper funds
Expand Down Expand Up @@ -492,6 +495,7 @@ mod test {
channel: send_channel.to_string(),
remote_address: "foreign-address".to_string(),
timeout: Some(7777),
memo: None,
};
let msg = ExecuteMsg::Receive(Cw20ReceiveMsg {
sender: "my-account".into(),
Expand Down Expand Up @@ -538,6 +542,7 @@ mod test {
channel: send_channel.to_string(),
remote_address: "foreign-address".to_string(),
timeout: Some(7777),
memo: None,
};
let msg = ExecuteMsg::Receive(Cw20ReceiveMsg {
sender: "my-account".into(),
Expand Down Expand Up @@ -608,4 +613,93 @@ mod test {
let config = query_config(deps.as_ref()).unwrap();
assert_eq!(config.default_gas_limit, Some(123456));
}

fn test_with_memo(memo: &str) {
let send_channel = "channel-5";
let mut deps = setup(&[send_channel, "channel-10"], &[]);

let transfer = TransferMsg {
channel: send_channel.to_string(),
remote_address: "foreign-address".to_string(),
timeout: None,
memo: Some(memo.to_string()),
};

// works with proper funds
let msg = ExecuteMsg::Transfer(transfer);
let info = mock_info("foobar", &coins(1234567, "ucosm"));
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages[0].gas_limit, None);
assert_eq!(1, res.messages.len());
if let CosmosMsg::Ibc(IbcMsg::SendPacket {
channel_id,
data,
timeout,
}) = &res.messages[0].msg
{
let expected_timeout = mock_env().block.time.plus_seconds(DEFAULT_TIMEOUT);
assert_eq!(timeout, &expected_timeout.into());
assert_eq!(channel_id.as_str(), send_channel);
let msg: Ics20Packet = from_binary(data).unwrap();
assert_eq!(msg.amount, Uint128::new(1234567));
assert_eq!(msg.denom.as_str(), "ucosm");
assert_eq!(msg.sender.as_str(), "foobar");
assert_eq!(msg.receiver.as_str(), "foreign-address");
assert_eq!(
msg.memo
.expect("Memo was None when Some was expected")
.as_str(),
memo
);
} else {
panic!("Unexpected return message: {:?}", res.messages[0]);
}
}

#[test]
fn execute_with_memo_works() {
test_with_memo("memo");
}

#[test]
fn execute_with_empty_string_memo_works() {
test_with_memo("");
}

#[test]
fn memo_is_backwards_compatible() {
let mut deps = setup(&["channel-5", "channel-10"], &[]);
let transfer: TransferMsg = cosmwasm_std::from_slice(
br#"{"channel": "channel-5", "remote_address": "foreign-address"}"#,
)
.unwrap();

let msg = ExecuteMsg::Transfer(transfer);
let info = mock_info("foobar", &coins(1234567, "ucosm"));
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(1, res.messages.len());
if let CosmosMsg::Ibc(IbcMsg::SendPacket {
channel_id: _,
data,
timeout: _,
}) = &res.messages[0].msg
{
let msg: Ics20Packet = from_binary(data).unwrap();
assert_eq!(msg.memo, None);

// This is the old version of the Ics20Packet. Deserializing into it
// should still work as the memo isn't included
#[cw_serde]
struct Ics20PacketNoMemo {
pub amount: Uint128,
pub denom: String,
pub sender: String,
pub receiver: String,
}

let _msg: Ics20PacketNoMemo = from_binary(data).unwrap();
} else {
panic!("Unexpected return message: {:?}", res.messages[0]);
}
}
}
12 changes: 12 additions & 0 deletions contracts/cw20-ics20/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub struct Ics20Packet {
pub receiver: String,
/// the sender address
pub sender: String,
/// optional memo for the IBC transfer
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
}

impl Ics20Packet {
Expand All @@ -42,9 +45,14 @@ impl Ics20Packet {
amount,
sender: sender.to_string(),
receiver: receiver.to_string(),
memo: None,
}
}

pub fn with_memo(self, memo: Option<String>) -> Self {
Ics20Packet { memo, ..self }
}

pub fn validate(&self) -> Result<(), ContractError> {
if self.amount.u128() > (u64::MAX as u128) {
Err(ContractError::AmountOverflow {})
Expand Down Expand Up @@ -465,6 +473,7 @@ mod test {
amount: amount.into(),
sender: "remote-sender".to_string(),
receiver: receiver.to_string(),
memo: None,
};
print!("Packet denom: {}", &data.denom);
IbcPacket::new(
Expand Down Expand Up @@ -511,6 +520,7 @@ mod test {
channel: send_channel.to_string(),
remote_address: "remote-rcpt".to_string(),
timeout: None,
memo: None,
};
let msg = ExecuteMsg::Receive(Cw20ReceiveMsg {
sender: "local-sender".to_string(),
Expand All @@ -525,6 +535,7 @@ mod test {
amount: Uint128::new(987654321),
sender: "local-sender".to_string(),
receiver: "remote-rcpt".to_string(),
memo: None,
};
let timeout = mock_env().block.time.plus_seconds(DEFAULT_TIMEOUT);
assert_eq!(
Expand Down Expand Up @@ -591,6 +602,7 @@ mod test {
channel: send_channel.to_string(),
remote_address: "my-remote-address".to_string(),
timeout: None,
memo: None,
});
let info = mock_info("local-sender", &coins(987654321, denom));
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
Expand Down
2 changes: 2 additions & 0 deletions contracts/cw20-ics20/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub struct TransferMsg {
pub remote_address: String,
/// How long the packet lives in seconds. If not specified, use default_timeout
pub timeout: Option<u64>,
/// An optional memo to add to the IBC transfer
pub memo: Option<String>,
}

#[cw_serde]
Expand Down

0 comments on commit a959518

Please sign in to comment.