From fb6de4e945cafd1b20c6b27716cd6a59d3d46c88 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 13 May 2022 17:38:48 +0200 Subject: [PATCH 1/4] fix coding for ack and ftt --- shared/src/ledger/ibc/handler.rs | 79 +++++++++++++++++++------------ shared/src/ledger/ibc/vp/mod.rs | 8 ++-- shared/src/ledger/ibc/vp/token.rs | 19 ++++---- shared/src/types/ibc/data.rs | 63 ++++++++++++++++++------ shared/src/types/token.rs | 7 +-- tests/src/vm_host_env/ibc.rs | 6 +-- 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index ac510d8b91..55453ed020 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -675,9 +675,17 @@ pub trait IbcActions { msg: &MsgRecvPacket, ) -> std::result::Result<(), Self::Error> { // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.receive_token(&msg.packet, &data)?; - } + let packet_ack = + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + match self.receive_token(&msg.packet, &data) { + Ok(_) => PacketAck::result_success(), + Err(_) => PacketAck::result_error( + "receiving a token failed".to_string(), + ), + } + } else { + PacketAck::result_error("unknown packet data".to_string()) + }; // store the receipt let receipt_key = storage::receipt_key( @@ -693,7 +701,7 @@ pub trait IbcActions { &msg.packet.destination_channel, msg.packet.sequence, ); - let ack = PacketAck::default().encode_to_vec(); + let ack = packet_ack.encode_to_vec(); let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); self.write_ibc_data(&ack_key, ack_commitment)?; @@ -718,6 +726,14 @@ pub trait IbcActions { &mut self, msg: &MsgAcknowledgement, ) -> std::result::Result<(), Self::Error> { + let ack = PacketAck::try_from(msg.acknowledgement.clone()) + .map_err(Error::IbcData)?; + if !ack.is_success() { + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + self.refund_token(&msg.packet, &data)?; + } + } + let commitment_key = storage::commitment_key( &msg.packet.source_port, &msg.packet.source_channel, @@ -725,6 +741,14 @@ pub trait IbcActions { ); self.delete_ibc_data(&commitment_key)?; + // get and increment the next sequence ack + let port_channel_id = port_channel_id( + msg.packet.source_port.clone(), + msg.packet.source_channel, + ); + let seq_key = storage::next_sequence_ack_key(&port_channel_id); + self.get_and_inc_sequence(&seq_key)?; + let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); self.emit_ibc_event(event)?; @@ -917,13 +941,12 @@ pub trait IbcActions { data.sender, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::SendingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::SendingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::SendingToken(format!( "Invalid token address: token {}, error {}", @@ -937,13 +960,13 @@ pub trait IbcActions { )) })?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // sink zone let burn = Address::Internal(InternalAddress::IbcBurn); self.transfer_token(&source, &burn, &token, amount)?; @@ -982,13 +1005,12 @@ pub trait IbcActions { data.receiver, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::ReceivingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::ReceivingToken(format!( "Invalid token address: token {}, error {}", @@ -1007,7 +1029,7 @@ pub trait IbcActions { packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // unescrow the token because this chain is the source let escrow = Address::Internal(InternalAddress::ibc_escrow_address( @@ -1035,13 +1057,12 @@ pub trait IbcActions { data.sender, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::ReceivingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::ReceivingToken(format!( "Invalid token address: token {}, error {}", @@ -1060,7 +1081,7 @@ pub trait IbcActions { packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // mint the token because the sender chain is the sink zone let mint = Address::Internal(InternalAddress::IbcMint); self.transfer_token(&mint, &dest, &token, amount)?; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b5dec0293c..fa500d5e43 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1570,7 +1570,7 @@ mod tests { .write(&key, PacketReceipt::default().as_bytes().to_vec()) .expect("write failed"); let key = ack_key(&get_port_id(), &get_channel_id(), sequence); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&key, ack).expect("write failed"); let tx_code = vec![]; @@ -1648,7 +1648,7 @@ mod tests { write_log.commit_block(&mut storage).expect("commit failed"); // prepare data - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); let proof_packet = CommitmentProofBytes::try_from(vec![0]).unwrap(); let proofs = Proofs::new(proof_packet, None, None, None, Height::new(0, 1)) @@ -1837,7 +1837,7 @@ mod tests { &msg.packet.destination_channel, msg.packet.sequence, ); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); @@ -1886,7 +1886,7 @@ mod tests { .expect("write failed"); let ack_key = ack_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 06181bdd45..da67f222c7 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -121,18 +121,17 @@ where { fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { let data = FungibleTokenPacketData::from(msg.clone()); - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // sink zone let target = Address::Internal(InternalAddress::IbcBurn); let target_key = token::balance_key(&token, &target); @@ -174,8 +173,7 @@ where let data: FungibleTokenPacketData = serde_json::from_slice(&packet.data) .map_err(Error::DecodingPacketData)?; - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; @@ -184,7 +182,7 @@ where packet.source_port.clone(), packet.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // this chain is the source let source = Address::Internal(InternalAddress::ibc_escrow_address( @@ -226,18 +224,17 @@ where let data: FungibleTokenPacketData = serde_json::from_slice(&packet.data) .map_err(Error::DecodingPacketData)?; - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", packet.source_port.clone(), packet.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // sink zone: mint the token for the refund let source = Address::Internal(InternalAddress::IbcMint); let source_key = token::balance_key(&token, &source); diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 4c5d5f2469..947142ab97 100644 --- a/shared/src/types/ibc/data.rs +++ b/shared/src/types/ibc/data.rs @@ -16,7 +16,9 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; +use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ + Acknowledgement, MsgAcknowledgement, +}; use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; @@ -32,14 +34,14 @@ use crate::ibc::core::ics26_routing::error::Error as Ics26Error; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::downcast; use crate::ibc_proto::google::protobuf::Any; -use crate::ibc_proto::ibc::core::channel::v1::acknowledgement::Response; -use crate::ibc_proto::ibc::core::channel::v1::Acknowledgement; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Decoding IBC data error: {0}")] DecodingData(prost::DecodeError), + #[error("Decoding Json data error: {0}")] + DecodingJsonData(serde_json::Error), #[error("Decoding message error: {0}")] DecodingMessage(Ics26Error), #[error("Downcast error: {0}")] @@ -326,39 +328,70 @@ impl Default for PacketReceipt { } /// Acknowledgement for a packet -#[derive(Clone, Debug)] -pub struct PacketAck(pub Acknowledgement); +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PacketAck { + /// Success Acknowledgement + Result(String), + /// Error Acknowledgement + Error(String), +} + +/// Success acknowledgement +const ACK_SUCCESS_B64: &str = "AQ=="; +/// Error acknowledgement +const ACK_ERR_STR: &str = + "error handling packet on destination chain: see events for details"; // TODO temporary type. add a new type for ack to ibc-rs impl PacketAck { + /// Success acknowledgement + pub fn result_success() -> Self { + Self::Result(ACK_SUCCESS_B64.to_string()) + } + + /// Acknowledgement with an error + pub fn result_error(err: String) -> Self { + Self::Error(format!("{}: {}", ACK_ERR_STR, err)) + } + + /// Check if the ack is for success + pub fn is_success(&self) -> bool { + match self { + Self::Result(_) => true, + Self::Error(_) => false, + } + } + /// Encode the ack pub fn encode_to_vec(&self) -> Vec { - serde_json::to_vec(&self.0) + serde_json::to_vec(&self) .expect("Encoding acknowledgement shouldn't fail") } } -impl Default for PacketAck { - fn default() -> Self { - Self(Acknowledgement { - response: Some(Response::Result(vec![1_u8])), - }) +impl TryFrom for PacketAck { + type Error = Error; + + fn try_from(ack: Acknowledgement) -> Result { + serde_json::from_slice(&ack.into_bytes()) + .map_err(Error::DecodingJsonData) } } // for the string to be used by the current reader impl Display for PacketAck { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(&self.0).unwrap()) + write!(f, "{}", serde_json::to_string(&self).unwrap()) } } -// TODO temporary type. add a new type for ack to ibc-rs +// TODO temporary type. add a new type for packet data to ibc-rs /// Data to transfer a token #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct FungibleTokenPacketData { /// the token denomination to be transferred - pub denomination: String, + pub denom: String, /// the token amount to be transferred pub amount: String, /// the sender address @@ -372,7 +405,7 @@ impl From for FungibleTokenPacketData { // TODO validation let token = msg.token.unwrap(); Self { - denomination: token.denom, + denom: token.denom, amount: token.amount, sender: msg.sender.to_string(), receiver: msg.receiver.to_string(), diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..2831b532fa 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -411,11 +411,8 @@ impl TryFrom for Transfer { Address::decode(&data.sender).map_err(TransferError::Address)?; let target = Address::decode(&data.receiver).map_err(TransferError::Address)?; - let token_str = data - .denomination - .split('/') - .last() - .ok_or(TransferError::NoToken)?; + let token_str = + data.denom.split('/').last().ok_or(TransferError::NoToken)?; let token = Address::decode(token_str).map_err(TransferError::Address)?; let amount = diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 9a8ead4be7..c9f03d3450 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -64,7 +64,7 @@ use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; -use namada::types::ibc::data::FungibleTokenPacketData; +use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; @@ -584,7 +584,7 @@ pub fn msg_packet_recv(packet: Packet) -> MsgRecvPacket { pub fn msg_packet_ack(packet: Packet) -> MsgAcknowledgement { MsgAcknowledgement { packet, - acknowledgement: vec![0].into(), + acknowledgement: PacketAck::result_success().encode_to_vec().into(), proofs: dummy_proofs(), signer: Signer::new("test"), } @@ -601,7 +601,7 @@ pub fn received_packet( let timeout_timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); let data = FungibleTokenPacketData { - denomination: token, + denom: token, amount: 100u64.to_string(), sender: address::testing::gen_established_address().to_string(), receiver: receiver.to_string(), From 868b8da9aad0ed477b080c0530067f7df22bbb03 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 18 May 2022 18:11:07 +0200 Subject: [PATCH 2/4] fix IBC token VP for ack error --- shared/src/ledger/ibc/vp/token.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index da67f222c7..c4af113550 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -102,6 +102,9 @@ where Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg)) => { self.validate_receiving_token(&msg.packet) } + Ics26Envelope::Ics4PacketMsg(PacketMsg::AckPacket(msg)) => { + self.validate_refunding_token(&msg.packet) + } Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { self.validate_refunding_token(&msg.packet) } From 3b8f1dd85f211dd072220c300bdb0348b6508ce2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 18 Oct 2022 09:07:30 +0000 Subject: [PATCH 3/4] [ci] wasm checksums update --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 8ddbe1dc85..680cea0e82 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.865260ca3ca9ed4c611cc5cbc8b4d864232f447f06c59afa0b7c1c63c3fa897c.wasm", - "tx_ibc.wasm": "tx_ibc.1360fdf276c4591d937aaac3bb40ddb573abeba382651474ae23138aac65c3e5.wasm", - "tx_init_account.wasm": "tx_init_account.72d9e1daa43998ce617eafba1547b34f26b128f0fb6e02cfdbc85ecf1f345fd4.wasm", - "tx_init_nft.wasm": "tx_init_nft.22a886305fda24438dbf3fc0f864ee32db4a398bf748edd2fddba1fc4679bc35.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.649bde547179ebee5449f5954425cd266c5063fae97f98f06de327387e8898ba.wasm", - "tx_init_validator.wasm": "tx_init_validator.d40077c1bf1263e1e8e42f54cd893473c5d9c1f395e5d30f44a34d98d9b8dde4.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.78b3a43c5c5b4362cb3023c8e7e25755447e9022952306a62f2d8aab6e99dbee.wasm", - "tx_transfer.wasm": "tx_transfer.4c06f6af1f008fccdd42fefdc8015adea9fa60f802603a8be149ec2c6421656e.wasm", - "tx_unbond.wasm": "tx_unbond.c3d54895e1a271c86838d54d821d52b5bf5764928811cff30767a4445ebbf653.wasm", - "tx_update_vp.wasm": "tx_update_vp.3b709f301e55cb970ec1df98c85c2486561c3243ab1c191d3e078ee345b8b93a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.671b46a77d03d10ca4e406f2bbc48adba0e541272a2963fd88d3a8c60e888fcd.wasm", - "tx_withdraw.wasm": "tx_withdraw.39e0012e110b19c7000400d11adc355bd8e89605b3c9ca10017c4766eed0ad69.wasm", - "vp_nft.wasm": "vp_nft.b23df820ae3faa13c9ed73121886b712fa0c613b7f43567f528cda05ef75fab6.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.45c740dcfa6e803d55a56cb51b02f2086147dfef3e955c0e8c3f18bf93ae0227.wasm", - "vp_token.wasm": "vp_token.74bca3c9999640c39bbd1c1364b65ffe8086e2d3ed124413251295d527326b57.wasm", - "vp_user.wasm": "vp_user.1c75ea1e55eb1244f023a525f9e20f0d34e52aebf0bd008dcf122fdc13bdf16a.wasm" + "tx_bond.wasm": "tx_bond.f3ba26325f3aa922fd51321361c2725650d176e81e47899bac00774583bd5b83.wasm", + "tx_ibc.wasm": "tx_ibc.609c755f203ea8533ac6785049cef86f17c92fc53e3476febc1104ad2f1ed928.wasm", + "tx_init_account.wasm": "tx_init_account.67ebdbdb799a4a6e7e25b01de31efff1f21be4a0361ff77c74ffde2643d55b91.wasm", + "tx_init_nft.wasm": "tx_init_nft.ce249fb6a4bae1ad0e0d7f82d6091d6c357bbfdcc3a9fbb6d81a1693babb81ca.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a1dae1cf75f6abccd78d62adc6df7d480e246acc681b894e380d3aecda1d3c5b.wasm", + "tx_init_validator.wasm": "tx_init_validator.ba0557060c670f0604b6e9326c438cc1f5d211bc66d3a8beae2b409a4ce70ed0.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f466c132545d08a58b5ea1f969b2dbd3267f93d1cde13e38cafd90d26daccf20.wasm", + "tx_transfer.wasm": "tx_transfer.8bb17847b0a69eb71b29fc2f672f5e2bace2c18ac73cacc0fa78738d6ce8a194.wasm", + "tx_unbond.wasm": "tx_unbond.2ce3ac3112c21c2f395dc705598545a8308e8e6071d812cf2c2604158444cb35.wasm", + "tx_update_vp.wasm": "tx_update_vp.e35177857c71be91f6af278d67e0c9704befeec895089376a3ead30d5d38aa74.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.567e4954ffdab694ce67beb92219d2e40f91150fd77a371dddb1fcaf3b936347.wasm", + "tx_withdraw.wasm": "tx_withdraw.8042162a4b64afccdbd97e03f687d6470e98b8ec53b1d5b6326daa18f30a7fb0.wasm", + "vp_nft.wasm": "vp_nft.9d70c00149e9b82971bc79d275e717b243aea576b77e533ec1340ab3bc2f80ce.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cf7fde2edad89c6997088dcc36aa5b6dbb47614742e85dc50a7f49cc7c3820d.wasm", + "vp_token.wasm": "vp_token.dd90fa4da0f4fc3f024d0b975adac57bc47e224a7a7ad2e4b6658e58ac104381.wasm", + "vp_user.wasm": "vp_user.acd21959bd5cb3277893eaca048c7d94855e79de418af0083ebcecc84b0dba78.wasm" } \ No newline at end of file From 015a5571cba8034c2cff8f92cb05ce90b023ad5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 14 Nov 2022 17:45:39 +0100 Subject: [PATCH 4/4] changelog: add #261 --- .changelog/unreleased/bug-fixes/261-ibc-ack-ftt-coding.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/261-ibc-ack-ftt-coding.md diff --git a/.changelog/unreleased/bug-fixes/261-ibc-ack-ftt-coding.md b/.changelog/unreleased/bug-fixes/261-ibc-ack-ftt-coding.md new file mode 100644 index 0000000000..06b7b0c279 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/261-ibc-ack-ftt-coding.md @@ -0,0 +1,2 @@ +- Fix compatiblity of IBC Acknowledgement message and FungibleTokenData with + ibc-go ([#261](https://github.com/anoma/namada/pull/261)) \ No newline at end of file