diff --git a/.changelog/unreleased/bug-fixes/625-ibc-ics20-transfer.md b/.changelog/unreleased/bug-fixes/625-ibc-ics20-transfer.md new file mode 100644 index 00000000000..3f06e49e38e --- /dev/null +++ b/.changelog/unreleased/bug-fixes/625-ibc-ics20-transfer.md @@ -0,0 +1,2 @@ +- Fix IBC token transfer to comply with ICS20. + ([#625](https://github.com/anoma/namada/pull/625)) \ No newline at end of file diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index ed776fe21af..66f73843033 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -336,7 +336,8 @@ where gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } - InternalAddress::IbcEscrow(_) + InternalAddress::IbcToken(_) + | InternalAddress::IbcEscrow | InternalAddress::IbcBurn | InternalAddress::IbcMint => { // validate the transfer diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index ac510d8b91c..21425718d14 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -115,6 +115,8 @@ pub enum Error { SendingToken(String), #[error("Receiving a token error: {0}")] ReceivingToken(String), + #[error("IBC storage error: {0}")] + IbcStorage(storage::Error), } // This is needed to use `ibc::Handler::Error` with `IbcActions` in @@ -161,9 +163,8 @@ pub trait IbcActions { /// Transfer token fn transfer_token( &mut self, - src: &Address, - dest: &Address, - token: &Address, + src: &Key, + dest: &Key, amount: Amount, ) -> std::result::Result<(), Self::Error>; @@ -675,9 +676,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 +702,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 +727,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 +742,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)?; @@ -910,26 +935,27 @@ pub trait IbcActions { &mut self, msg: &MsgTransfer, ) -> std::result::Result<(), Self::Error> { - let data = FungibleTokenPacketData::from(msg.clone()); - let source = Address::decode(data.sender.clone()).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { + let mut data = FungibleTokenPacketData::from(msg.clone()); + if let Some(hash) = storage::token_hash_from_denom(&data.denom) + .map_err(Error::IbcStorage)? + { + let denom_key = storage::ibc_denom_key(&hash); + let denom_bytes = + self.read_ibc_data(&denom_key)?.ok_or_else(|| { + Error::SendingToken(format!( + "No original denom: denom_key {}", + denom_key + )) + })?; + let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { Error::SendingToken(format!( - "No token was specified: {}", - data.denomination + "Decoding the denom failed: denom_key {}, error {}", + denom_key, e )) })?; - let token = Address::decode(token_str).map_err(|e| { - Error::SendingToken(format!( - "Invalid token address: token {}, error {}", - token_str, e - )) - })?; + data.denom = denom.to_string(); + } + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; let amount = Amount::from_str(&data.amount).map_err(|e| { Error::SendingToken(format!( "Invalid amount: amount {}, error {}", @@ -937,25 +963,59 @@ pub trait IbcActions { )) })?; + let source_addr = Address::decode(&data.sender).map_err(|e| { + Error::SendingToken(format!( + "Invalid sender address: sender {}, error {}", + data.sender, e + )) + })?; + // check the denomination field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { - // sink zone - let burn = Address::Internal(InternalAddress::IbcBurn); - self.transfer_token(&source, &burn, &token, amount)?; + let (source, target) = if data.denom.starts_with(&prefix) { + // the receiver's chain was the source + // transfer from the origin-specific account of the token + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + let src = token::multitoken_balance_key(&key_prefix, &source_addr); + + let key_prefix = storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + let burn = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcBurn), + ); + (src, burn) } else { - // source zone - let escrow = - Address::Internal(InternalAddress::ibc_escrow_address( - msg.source_port.to_string(), - msg.source_channel.to_string(), - )); - self.transfer_token(&source, &escrow, &token, amount)?; - } + // this chain is the source + // escrow the amount of the token + let src = if data.denom == token.to_string() { + token::balance_key(&token, &source_addr) + } else { + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &source_addr) + }; + + let key_prefix = storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + (src, escrow) + }; + self.transfer_token(&source, &target, amount)?; // send a packet let port_channel_id = @@ -976,50 +1036,82 @@ pub trait IbcActions { packet: &Packet, data: &FungibleTokenPacketData, ) -> std::result::Result<(), Self::Error> { - let dest = Address::decode(data.receiver.clone()).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid receiver address: receiver {}, error {}", - data.receiver, e - )) - })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; - let token = Address::decode(token_str).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid token address: token {}, error {}", - token_str, e - )) - })?; + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; let amount = Amount::from_str(&data.amount).map_err(|e| { Error::ReceivingToken(format!( "Invalid amount: amount {}, error {}", data.amount, e )) })?; + // The receiver should be an address because the origin-specific account + // key should be assigned internally + let dest_addr = Address::decode(&data.receiver).map_err(|e| { + Error::ReceivingToken(format!( + "Invalid receiver address: receiver {}, error {}", + data.receiver, e + )) + })?; let prefix = format!( "{}/{}/", packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { - // unescrow the token because this chain is the source - let escrow = - Address::Internal(InternalAddress::ibc_escrow_address( - packet.destination_port.to_string(), - packet.destination_channel.to_string(), - )); - self.transfer_token(&escrow, &dest, &token, amount)?; - } else { - // mint the token because the sender chain is the source - let mint = Address::Internal(InternalAddress::IbcMint); - self.transfer_token(&mint, &dest, &token, amount)?; - } + let (source, target) = match data.denom.strip_prefix(&prefix) { + Some(denom) => { + // unescrow the token because this chain was the source + let escrow_prefix = storage::ibc_account_prefix( + &packet.destination_port, + &packet.destination_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &escrow_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + let dest = if denom == token.to_string() { + token::balance_key(&token, &dest_addr) + } else { + let key_prefix = storage::ibc_token_prefix(denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &dest_addr) + }; + (escrow, dest) + } + None => { + // mint the token because the sender chain is the source + let key_prefix = storage::ibc_account_prefix( + &packet.destination_port, + &packet.destination_channel, + &token, + ); + let mint = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); + + // prefix the denom with the this chain port and channel + let denom = format!( + "{}/{}/{}", + &packet.destination_port, + &packet.destination_channel, + &data.denom + ); + let key_prefix = storage::ibc_token_prefix(&denom) + .map_err(Error::IbcStorage)?; + let dest = + token::multitoken_balance_key(&key_prefix, &dest_addr); + + // store the prefixed denom + let token_hash = storage::calc_hash(&denom); + let denom_key = storage::ibc_denom_key(token_hash); + self.write_ibc_data(&denom_key, denom.as_bytes())?; + + (mint, dest) + } + }; + self.transfer_token(&source, &target, amount)?; + Ok(()) } @@ -1029,25 +1121,7 @@ pub trait IbcActions { packet: &Packet, data: &FungibleTokenPacketData, ) -> std::result::Result<(), Self::Error> { - let dest = Address::decode(data.sender.clone()).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; - let token = Address::decode(token_str).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid token address: token {}, error {}", - token_str, e - )) - })?; + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; let amount = Amount::from_str(&data.amount).map_err(|e| { Error::ReceivingToken(format!( "Invalid amount: amount {}, error {}", @@ -1055,24 +1129,56 @@ pub trait IbcActions { )) })?; + let dest_addr = Address::decode(&data.sender).map_err(|e| { + Error::SendingToken(format!( + "Invalid sender address: sender {}, error {}", + data.sender, e + )) + })?; + let prefix = format!( "{}/{}/", packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.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)?; + let (source, target) = if data.denom.starts_with(&prefix) { + // mint the token because the amount was burned + let key_prefix = storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let mint = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + let dest = token::multitoken_balance_key(&key_prefix, &dest_addr); + (mint, dest) } else { - // unescrow the token because the sender chain is the source zone - let escrow = - Address::Internal(InternalAddress::ibc_escrow_address( - packet.source_port.to_string(), - packet.source_channel.to_string(), - )); - self.transfer_token(&escrow, &dest, &token, amount)?; - } + // unescrow the token because the acount was escrowed + let dest = if data.denom == token.to_string() { + token::balance_key(&token, &dest_addr) + } else { + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &dest_addr) + }; + + let key_prefix = storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + (escrow, dest) + }; + self.transfer_token(&source, &target, amount)?; + Ok(()) } } diff --git a/shared/src/ledger/ibc/storage.rs b/shared/src/ledger/ibc/storage.rs index 51cfc8890ed..ed47b4c68a7 100644 --- a/shared/src/ledger/ibc/storage.rs +++ b/shared/src/ledger/ibc/storage.rs @@ -2,6 +2,7 @@ use std::str::FromStr; +use sha2::{Digest, Sha256}; use thiserror::Error; use crate::ibc::core::ics02_client::height::Height; @@ -16,7 +17,7 @@ use crate::ibc::core::ics24_host::path::{ SeqAcksPath, SeqRecvsPath, SeqSendsPath, }; use crate::ibc::core::ics24_host::Path; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress, HASH_LEN}; use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; const CLIENTS_COUNTER: &str = "clients/counter"; @@ -24,6 +25,9 @@ const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; const CAPABILITIES_INDEX: &str = "capabilities/index"; const CAPABILITIES: &str = "capabilities"; +const DENOM: &str = "denom"; +/// Key segment for a multitoken related to IBC +pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -34,6 +38,8 @@ pub enum Error { InvalidKey(String), #[error("Port capability error: {0}")] InvalidPortCapability(String), + #[error("Denom error: {0}")] + Denom(String), } /// IBC storage functions result @@ -54,6 +60,7 @@ pub enum IbcPrefix { Receipt, Ack, Event, + Denom, Unknown, } @@ -76,6 +83,7 @@ pub fn ibc_prefix(key: &Key) -> Option { "receipts" => IbcPrefix::Receipt, "acks" => IbcPrefix::Ack, "event" => IbcPrefix::Event, + "denom" => IbcPrefix::Denom, _ => IbcPrefix::Unknown, }) } @@ -482,3 +490,82 @@ pub fn capability(key: &Key) -> Result { ))), } } + +/// The storage key to get the denom name from the hashed token +pub fn ibc_denom_key(token_hash: impl AsRef) -> Key { + let path = format!("{}/{}", DENOM, token_hash.as_ref()); + ibc_key(path).expect("Creating a key for the denom key shouldn't fail") +} + +/// Key's prefix for the escrow, burn, or mint account +pub fn ibc_account_prefix( + port_id: &PortId, + channel_id: &ChannelId, + token: &Address, +) -> Key { + Key::from(token.to_db_key()) + .push(&MULTITOKEN_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&port_id.to_string().to_db_key()) + .expect("Cannot obtain a storage key") + .push(&channel_id.to_string().to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Token address from the denom string +pub fn token(denom: impl AsRef) -> Result
{ + let token_str = denom.as_ref().split('/').last().ok_or_else(|| { + Error::Denom(format!("No token was specified: {}", denom.as_ref())) + })?; + Address::decode(token_str).map_err(|e| { + Error::Denom(format!( + "Invalid token address: token {}, error {}", + token_str, e + )) + }) +} + +/// Get the hash of IBC token address from the denom string +pub fn token_hash_from_denom(denom: impl AsRef) -> Result> { + match denom + .as_ref() + .strip_prefix(&format!("{}/", MULTITOKEN_STORAGE_KEY)) + { + Some(addr_str) => { + let addr = Address::decode(addr_str).map_err(|e| { + Error::Denom(format!( + "Decoding the denom failed: ibc_token {}, error {}", + addr_str, e + )) + })?; + match addr { + Address::Internal(InternalAddress::IbcToken(h)) => Ok(Some(h)), + _ => Err(Error::Denom(format!( + "Unexpected address was given: {}", + addr + ))), + } + } + None => Ok(None), + } +} + +/// Hash the denom +pub fn calc_hash(denom: impl AsRef) -> String { + let mut hasher = Sha256::new(); + hasher.update(denom.as_ref()); + format!("{:.width$x}", hasher.finalize(), width = HASH_LEN) +} + +/// Key's prefix of the received token over IBC +pub fn ibc_token_prefix(denom: impl AsRef) -> Result { + let token = token(&denom)?; + let hash = calc_hash(&denom); + let ibc_token = Address::Internal(InternalAddress::IbcToken(hash)); + let prefix = Key::from(token.to_db_key()) + .push(&MULTITOKEN_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&ibc_token.to_db_key()) + .expect("Cannot obtain a storage key"); + Ok(prefix) +} diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs new file mode 100644 index 00000000000..40e95c51c2d --- /dev/null +++ b/shared/src/ledger/ibc/vp/denom.rs @@ -0,0 +1,88 @@ +//! IBC validity predicate for denom + +use thiserror::Error; + +use super::Ibc; +use crate::ledger::ibc::storage; +use crate::ledger::native_vp::VpEnv; +use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::types::ibc::data::{ + Error as IbcDataError, FungibleTokenPacketData, IbcMessage, +}; +use crate::types::storage::KeySeg; +use crate::vm::WasmCacheAccess; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Decoding TX data error: {0}")] + DecodingTxData(std::io::Error), + #[error("IBC data error: {0}")] + InvalidIbcData(IbcDataError), + #[error("Invalid packet data: {0}")] + PacketData(String), + #[error("Denom error: {0}")] + Denom(String), +} + +/// IBC channel functions result +pub type Result = std::result::Result; + +impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + pub(super) fn validate_denom(&self, tx_data: &[u8]) -> Result<()> { + let ibc_msg = IbcMessage::decode(tx_data)?; + let msg = ibc_msg.msg_recv_packet()?; + match serde_json::from_slice::( + &msg.packet.data, + ) { + Ok(data) => { + let denom = format!( + "{}/{}/{}", + &msg.packet.destination_port, + &msg.packet.destination_channel, + &data.denom + ); + let token_hash = storage::calc_hash(&denom); + let denom_key = storage::ibc_denom_key(&token_hash.raw()); + match self.ctx.read_bytes_post(&denom_key) { + Ok(Some(v)) => match std::str::from_utf8(&v) { + Ok(d) if d == denom => Ok(()), + Ok(d) => Err(Error::Denom(format!( + "Mismatch the denom: original {}, denom {}", + denom, d + ))), + Err(e) => Err(Error::Denom(format!( + "Decoding the denom failed: key {}, error {}", + denom_key, e + ))), + }, + _ => Err(Error::Denom(format!( + "Looking up the denom failed: Key {}", + denom_key + ))), + } + } + Err(e) => Err(Error::PacketData(format!( + "unknown packet data: error {}", + e + ))), + } + } +} + +impl From for Error { + fn from(err: IbcDataError) -> Self { + Self::InvalidIbcData(err) + } +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + Self::DecodingTxData(err) + } +} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b5dec0293c6..930d8df8709 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -3,6 +3,7 @@ mod channel; mod client; mod connection; +mod denom; mod packet; mod port; mod sequence; @@ -46,6 +47,8 @@ pub enum Error { PacketError(packet::Error), #[error("Sequence validation error: {0}")] SequenceError(sequence::Error), + #[error("Denom validation error: {0}")] + DenomError(denom::Error), #[error("IBC event error: {0}")] IbcEvent(String), #[error("Decoding transaction data error: {0}")] @@ -141,6 +144,7 @@ where } IbcPrefix::Ack => self.validate_ack(key)?, IbcPrefix::Event => {} + IbcPrefix::Denom => self.validate_denom(tx_data)?, IbcPrefix::Unknown => { return Err(Error::KeyError(format!( "Invalid IBC-related key: {}", @@ -288,6 +292,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: denom::Error) -> Self { + Self::DenomError(err) + } +} + /// A dummy header used for testing #[cfg(any(feature = "test", feature = "testing"))] pub fn get_dummy_header() -> crate::types::storage::Header { @@ -1570,7 +1580,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 +1658,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 +1847,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 +1896,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/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 207727e91cd..aafb8890b62 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -6,7 +6,8 @@ use super::super::handler::{ self, make_send_packet_event, make_timeout_event, packet_from_message, }; use super::super::storage::{ - port_channel_sequence_id, Error as IbcStorageError, + ibc_denom_key, port_channel_sequence_id, token_hash_from_denom, + Error as IbcStorageError, }; use super::{Ibc, StateChange}; use crate::ibc::core::ics02_client::height::Height; @@ -32,8 +33,11 @@ use crate::ibc::core::ics24_host::identifier::{ }; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; +use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::{self, StorageHasher}; -use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; +use crate::types::ibc::data::{ + Error as IbcDataError, FungibleTokenPacketData, IbcMessage, +}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; @@ -64,6 +68,8 @@ pub enum Error { IbcEvent(String), #[error("IBC proof error: {0}")] Proof(String), + #[error("IBC denom error: {0}")] + Denom(String), } /// IBC packet functions result @@ -99,11 +105,12 @@ where let channel = self .channel_end(&(commitment_key.0.clone(), commitment_key.1)) .map_err(|e| Error::InvalidChannel(e.to_string()))?; - let packet = packet_from_message( + let mut packet = packet_from_message( &msg, commitment_key.2, channel.counterparty(), ); + self.update_denom(&mut packet)?; let commitment = self .get_packet_commitment(&commitment_key) .map_err(|_| { @@ -694,6 +701,39 @@ where Ok(()) } } + + fn update_denom(&self, packet: &mut Packet) -> Result<()> { + if let Ok(mut data) = + serde_json::from_slice::(&packet.data) + { + if let Some(token_hash) = token_hash_from_denom(&data.denom) + .map_err(|e| { + Error::Denom(format!("Invalid denom: error {}", e)) + })? + { + let denom_key = ibc_denom_key(&token_hash); + let denom_bytes = match self.ctx.read_bytes_pre(&denom_key) { + Ok(Some(v)) => v, + _ => { + return Err(Error::Denom(format!( + "No original denom: denom_key {}", + denom_key + ))); + } + }; + let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { + Error::Denom(format!( + "Decoding the denom failed: denom_key {}, error {}", + denom_key, e + )) + })?; + data.denom = denom.to_string(); + packet.data = serde_json::to_vec(&data) + .expect("encoding the packet data shouldn't fail"); + } + } + Ok(()) + } } /// The proof for the counterpart channel should be in proofs.other_proof diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 06181bdd45e..1ef67d4543a 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -10,6 +10,7 @@ use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::Msg use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics04_channel::packet::Packet; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; +use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::proto::SignedTxData; @@ -18,9 +19,7 @@ use crate::types::ibc::data::{ Error as IbcDataError, FungibleTokenPacketData, IbcMessage, }; use crate::types::storage::Key; -use crate::types::token::{ - self, is_non_owner_balance_key, Amount, AmountParseError, -}; +use crate::types::token::{self, Amount, AmountParseError}; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -32,20 +31,22 @@ pub enum Error { IbcMessage(IbcDataError), #[error("Invalid message error")] InvalidMessage, - #[error("Invalid address error")] + #[error("Invalid address error: {0}")] Address(AddressError), #[error("Token error")] NoToken, - #[error("Parsing amount error")] + #[error("Parsing amount error: {0}")] Amount(AmountParseError), - #[error("Decoding error")] + #[error("Decoding error: {0}")] Decoding(std::io::Error), - #[error("Decoding PacketData error")] + #[error("Decoding PacketData error: {0}")] DecodingPacketData(serde_json::Error), - #[error("Invalid token transfer error")] + #[error("Invalid token transfer error: {0}")] TokenTransfer(String), #[error("IBC message is required as transaction data")] NoTxData, + #[error("Invalid denom error: {0}")] + Denom(String), } /// Result for IBC token VP @@ -85,10 +86,26 @@ where // Check the non-onwer balance updates let keys_changed: HashSet = keys_changed .iter() - .filter(|k| is_non_owner_balance_key(k).is_some()) + .filter(|k| { + matches!( + token::is_any_multitoken_balance_key(k), + Some(( + _, + Address::Internal( + InternalAddress::IbcEscrow + | InternalAddress::IbcBurn + | InternalAddress::IbcMint + ) + )) + ) + }) .cloned() .collect(); - if keys_changed.len() != 1 { + if keys_changed.is_empty() { + // no account is checked by this VP + return Ok(true); + } else if keys_changed.len() > 1 { + // a transaction can update at most 1 special IBC account for now // a transaction can update at most 1 non-owner balance for now return Err(Error::TokenTransfer( "Invalid transfer for multiple non-owner balances".to_owned(), @@ -102,6 +119,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) } @@ -120,22 +140,78 @@ where CA: 'static + WasmCacheAccess, { 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 = Address::decode(token_str).map_err(Error::Address)?; + let mut data = FungibleTokenPacketData::from(msg.clone()); + if let Some(token_hash) = + ibc_storage::token_hash_from_denom(&data.denom).map_err(|e| { + Error::Denom(format!("Invalid denom: error {}", e)) + })? + { + let denom_key = ibc_storage::ibc_denom_key(&token_hash); + let denom_bytes = match self.ctx.read_bytes_pre(&denom_key) { + Ok(Some(v)) => v, + _ => { + return Err(Error::Denom(format!( + "No original denom: denom_key {}", + denom_key + ))); + } + }; + let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { + Error::Denom(format!( + "Decoding the denom failed: denom_key {}, error {}", + denom_key, e + )) + })?; + data.denom = denom.to_string(); + } + let token = ibc_storage::token(&data.denom) + .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let denom = if let Some(denom) = data + .denom + .strip_prefix(&format!("{}/", ibc_storage::MULTITOKEN_STORAGE_KEY)) + { + let denom_key = ibc_storage::ibc_denom_key(&denom); + match self.ctx.read_bytes_pre(&denom_key)? { + Some(v) => std::str::from_utf8(&v) + .map_err(|e| { + Error::TokenTransfer(format!( + "Decoding the denom failed: denom_key {}, error {}", + denom_key, e + )) + })? + .to_string(), + None => { + return Err(Error::TokenTransfer(format!( + "No original denom: denom_key {}", + denom_key + ))); + } + } + } else { + data.denom.clone() + }; + // check the denomination field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let key_prefix = ibc_storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + + let change = if denom.starts_with(&prefix) { // sink zone - let target = Address::Internal(InternalAddress::IbcBurn); - let target_key = token::balance_key(&token, &target); + // check the amount of the token has been burned + let target_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcBurn), + ); let post = try_decode_token_amount( self.ctx.read_bytes_temp(&target_key)?, )? @@ -144,12 +220,11 @@ where post.change() } else { // source zone - let target = - Address::Internal(InternalAddress::ibc_escrow_address( - msg.source_port.to_string(), - msg.source_channel.to_string(), - )); - let target_key = token::balance_key(&token, &target); + // check the amount of the token has been escrowed + let target_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); let pre = try_decode_token_amount(self.ctx.read_bytes_pre(&target_key)?)? .unwrap_or_default(); @@ -174,9 +249,8 @@ 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 = Address::decode(token_str).map_err(Error::Address)?; + let token = ibc_storage::token(&data.denom) + .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; let prefix = format!( @@ -184,14 +258,18 @@ where packet.source_port.clone(), packet.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let key_prefix = ibc_storage::ibc_account_prefix( + &packet.destination_port, + &packet.destination_channel, + &token, + ); + let change = if data.denom.starts_with(&prefix) { // this chain is the source - let source = - Address::Internal(InternalAddress::ibc_escrow_address( - packet.destination_port.to_string(), - packet.destination_channel.to_string(), - )); - let source_key = token::balance_key(&token, &source); + // check the amount of the token has been unescrowed + let source_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); let pre = try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? .unwrap_or_default(); @@ -202,8 +280,11 @@ where pre.change() - post.change() } else { // the sender is the source - let source = Address::Internal(InternalAddress::IbcMint); - let source_key = token::balance_key(&token, &source); + // check the amount of the token has been minted + let source_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); let post = try_decode_token_amount( self.ctx.read_bytes_temp(&source_key)?, )? @@ -226,21 +307,27 @@ 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 key_prefix = ibc_storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + 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); + let source_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); let post = try_decode_token_amount( self.ctx.read_bytes_temp(&source_key)?, )? @@ -249,12 +336,10 @@ where Amount::max().change() - post.change() } else { // source zone: unescrow the token for the refund - let source = - Address::Internal(InternalAddress::ibc_escrow_address( - packet.source_port.to_string(), - packet.source_channel.to_string(), - )); - let source_key = token::balance_key(&token, &source); + let source_key = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); let pre = try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? .unwrap_or_default(); diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 6f047730fed..a503b796f0d 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -53,18 +53,20 @@ mod internal { "ano::Proof of Stake "; pub const POS_SLASH_POOL: &str = "ano::Proof of Stake Slash Pool "; - pub const IBC: &str = - "ano::Inter-Blockchain Communication "; pub const PARAMETERS: &str = "ano::Protocol Parameters "; pub const GOVERNANCE: &str = "ano::Governance "; pub const SLASH_FUND: &str = "ano::Slash Fund "; + pub const IBC: &str = + "ibc::Inter-Blockchain Communication "; + pub const IBC_ESCROW: &str = + "ibc::IBC Escrow Address "; pub const IBC_BURN: &str = - "ano::IBC Burn Address "; + "ibc::IBC Burn Address "; pub const IBC_MINT: &str = - "ano::IBC Mint Address "; + "ibc::IBC Mint Address "; pub const ETH_BRIDGE: &str = "ano::ETH Bridge Address "; } @@ -75,6 +77,8 @@ const PREFIX_ESTABLISHED: &str = "est"; const PREFIX_IMPLICIT: &str = "imp"; /// Fixed-length address strings prefix for internal addresses. const PREFIX_INTERNAL: &str = "ano"; +/// Fixed-length address strings prefix for IBC addresses. +const PREFIX_IBC: &str = "ibc"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -178,7 +182,6 @@ impl Address { InternalAddress::PosSlashPool => { internal::POS_SLASH_POOL.to_string() } - InternalAddress::Ibc => internal::IBC.to_string(), InternalAddress::Parameters => { internal::PARAMETERS.to_string() } @@ -188,8 +191,12 @@ impl Address { InternalAddress::SlashFund => { internal::SLASH_FUND.to_string() } - InternalAddress::IbcEscrow(hash) => { - format!("{}::{}", PREFIX_INTERNAL, hash) + InternalAddress::Ibc => internal::IBC.to_string(), + InternalAddress::IbcToken(hash) => { + format!("{}::{}", PREFIX_IBC, hash) + } + InternalAddress::IbcEscrow => { + internal::IBC_ESCROW.to_string() } InternalAddress::IbcBurn => internal::IBC_BURN.to_string(), InternalAddress::IbcMint => internal::IBC_MINT.to_string(), @@ -232,36 +239,45 @@ impl Address { .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; Ok(Address::Implicit(ImplicitAddress(pkh))) } - Some((PREFIX_INTERNAL, raw)) => match string { + Some((PREFIX_INTERNAL, _)) => match string { internal::POS => Ok(Address::Internal(InternalAddress::PoS)), internal::POS_SLASH_POOL => { Ok(Address::Internal(InternalAddress::PosSlashPool)) } - internal::IBC => Ok(Address::Internal(InternalAddress::Ibc)), internal::PARAMETERS => { Ok(Address::Internal(InternalAddress::Parameters)) } - internal::IBC_BURN => { - Ok(Address::Internal(InternalAddress::IbcBurn)) - } internal::GOVERNANCE => { Ok(Address::Internal(InternalAddress::Governance)) } internal::SLASH_FUND => { Ok(Address::Internal(InternalAddress::SlashFund)) } - internal::IBC_MINT => { - Ok(Address::Internal(InternalAddress::IbcMint)) - } internal::ETH_BRIDGE => { Ok(Address::Internal(InternalAddress::EthBridge)) } + _ => Err(Error::new( + ErrorKind::InvalidData, + "Invalid internal address", + )), + }, + Some((PREFIX_IBC, raw)) => match string { + internal::IBC => Ok(Address::Internal(InternalAddress::Ibc)), + internal::IBC_ESCROW => { + Ok(Address::Internal(InternalAddress::IbcEscrow)) + } + internal::IBC_BURN => { + Ok(Address::Internal(InternalAddress::IbcBurn)) + } + internal::IBC_MINT => { + Ok(Address::Internal(InternalAddress::IbcMint)) + } _ if raw.len() == HASH_LEN => Ok(Address::Internal( - InternalAddress::IbcEscrow(raw.to_string()), + InternalAddress::IbcToken(raw.to_string()), )), _ => Err(Error::new( ErrorKind::InvalidData, - "Invalid internal address", + "Invalid IBC internal address", )), }, _ => Err(Error::new( @@ -437,12 +453,14 @@ pub enum InternalAddress { PoS, /// Proof-of-stake slash pool contains slashed tokens PosSlashPool, - /// Inter-blockchain communication - Ibc, /// Protocol parameters Parameters, + /// Inter-blockchain communication + Ibc, + /// IBC-related token + IbcToken(String), /// Escrow for IBC token transfer - IbcEscrow(String), + IbcEscrow, /// Burn tokens with IBC token transfer IbcBurn, /// Mint tokens from this address with IBC token transfer @@ -456,13 +474,17 @@ pub enum InternalAddress { } impl InternalAddress { - /// Get an escrow address from the port ID and channel ID - pub fn ibc_escrow_address(port_id: String, channel_id: String) -> Self { + /// Get an IBC token address from the port ID and channel ID + pub fn ibc_token_address( + port_id: String, + channel_id: String, + token: &Address, + ) -> Self { let mut hasher = Sha256::new(); - let s = format!("{}/{}", port_id, channel_id); + let s = format!("{}/{}/{}", port_id, channel_id, token); hasher.update(&s); - let hash = format!("{:.width$X}", hasher.finalize(), width = HASH_LEN); - InternalAddress::IbcEscrow(hash) + let hash = format!("{:.width$x}", hasher.finalize(), width = HASH_LEN); + InternalAddress::IbcToken(hash) } } @@ -474,11 +496,12 @@ impl Display for InternalAddress { match self { Self::PoS => "PoS".to_string(), Self::PosSlashPool => "PosSlashPool".to_string(), - Self::Ibc => "IBC".to_string(), Self::Parameters => "Parameters".to_string(), Self::Governance => "Governance".to_string(), Self::SlashFund => "SlashFund".to_string(), - Self::IbcEscrow(hash) => format!("IbcEscrow: {}", hash), + Self::Ibc => "IBC".to_string(), + Self::IbcToken(hash) => format!("IbcToken: {}", hash), + Self::IbcEscrow => "IbcEscrow".to_string(), Self::IbcBurn => "IbcBurn".to_string(), Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), @@ -712,11 +735,12 @@ pub mod testing { match InternalAddress::PoS { InternalAddress::PoS => {} InternalAddress::PosSlashPool => {} - InternalAddress::Ibc => {} InternalAddress::Governance => {} InternalAddress::SlashFund => {} InternalAddress::Parameters => {} - InternalAddress::IbcEscrow(_) => {} + InternalAddress::Ibc => {} + InternalAddress::IbcToken(_) => {} + InternalAddress::IbcEscrow => {} InternalAddress::IbcBurn => {} InternalAddress::IbcMint => {} InternalAddress::EthBridge => {} /* Add new addresses in the @@ -727,8 +751,9 @@ pub mod testing { Just(InternalAddress::PosSlashPool), Just(InternalAddress::Ibc), Just(InternalAddress::Parameters), - arb_port_channel_id() - .prop_map(|(p, c)| InternalAddress::ibc_escrow_address(p, c)), + Just(InternalAddress::Ibc), + arb_ibc_token(), + Just(InternalAddress::IbcEscrow), Just(InternalAddress::IbcBurn), Just(InternalAddress::IbcMint), Just(InternalAddress::Governance), @@ -737,8 +762,20 @@ pub mod testing { ] } - fn arb_port_channel_id() -> impl Strategy { - ("[a-zA-Z0-9_]{2,128}", any::()) - .prop_map(|(id, counter)| (id, format!("channel-{}", counter))) + fn arb_ibc_token() -> impl Strategy { + // use sha2::{Digest, Sha256}; + ("[a-zA-Z0-9_]{2,128}", any::()).prop_map(|(id, counter)| { + let mut hasher = sha2::Sha256::new(); + let s = format!( + "{}/{}/{}", + id, + format_args!("channel-{}", counter), + &xan() + ); + hasher.update(&s); + let hash = + format!("{:.width$x}", hasher.finalize(), width = HASH_LEN); + InternalAddress::IbcToken(hash) + }) } } diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 4c5d5f2469c..947142ab97a 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 787a8855dc5..8b811f6de89 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -9,7 +9,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::types::address::{Address, Error as AddressError, InternalAddress}; +use crate::types::address::{Address, Error as AddressError}; use crate::types::ibc::data::FungibleTokenPacketData; use crate::types::storage::{DbKeySeg, Key, KeySeg}; @@ -302,24 +302,6 @@ pub fn is_any_token_balance_key(key: &Key) -> Option<&Address> { } } -/// Check if the given storage key is non-owner's balance key. If it is, returns -/// the address. -pub fn is_non_owner_balance_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(_), - DbKeySeg::StringSeg(key), - DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => match owner { - Address::Internal(InternalAddress::IbcEscrow(_)) - | Address::Internal(InternalAddress::IbcBurn) - | Address::Internal(InternalAddress::IbcMint) => Some(owner), - _ => None, - }, - _ => None, - } -} - /// Check if the given storage key is multitoken balance key for the given /// token. If it is, returns the sub prefix and the owner. pub fn is_multitoken_balance_key<'a>( @@ -411,11 +393,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 9a8ead4be76..4b21e97b79f 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -64,8 +64,8 @@ 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::storage::{self, BlockHash, BlockHeight}; +use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; +use namada::types::storage::{self, BlockHash, BlockHeight, Key}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; use namada_tx_prelude::StorageWrite; @@ -146,16 +146,16 @@ pub fn validate_ibc_vp_from_tx<'a>( pub fn validate_token_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, - addr: &Address, + target: &Key, ) -> std::result::Result { let (verifiers, keys_changed) = tx_env .write_log .verifiers_and_changed_keys(&tx_env.verifiers); - if !verifiers.contains(addr) { + if !keys_changed.contains(target) { panic!( - "The given token address {} isn't part of the tx verifiers set: \ + "The given target address {} isn't part of the tx verifiers set: \ {:#?}", - addr, verifiers + target, keys_changed, ); } let (vp_wasm_cache, _vp_cache_dir) = @@ -176,40 +176,6 @@ pub fn validate_token_vp_from_tx<'a>( TestIbcTokenVp { token }.validate(tx.data.as_ref().unwrap()) } -// /// Initialize the native token VP for the given address -// pub fn init_token_vp_from_tx<'a>( -// tx_env: &'a TestTxEnv, -// tx: &'a Tx, -// addr: &Address, -// ) -> (TestIbcTokenVp<'a>, TempDir) { -// let (verifiers, keys_changed) = tx_env -// .write_log -// .verifiers_and_changed_keys(&tx_env.verifiers); -// if !verifiers.contains(addr) { -// panic!( -// "The given token address {} isn't part of the tx verifiers set: \ -// {:#?}", -// addr, verifiers -// ); -// } -// let (vp_wasm_cache, vp_cache_dir) = -// wasm::compilation_cache::common::testing::cache(); - -// let ctx = Ctx::new( -// &ADDRESS, -// &tx_env.storage, -// &tx_env.write_log, -// tx, -// VpGasMeter::new(0), -// &keys_changed, -// &verifiers, -// vp_wasm_cache, -// ); -// let token = IbcToken { ctx }; - -// (TestIbcTokenVp { token }, vp_cache_dir) -// } - /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. pub fn init_storage() -> (Address, Address) { tx_host_env::with(|env| { @@ -584,7 +550,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 +567,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(), diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 3325c6eb6a7..c44cc25c2e7 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -23,6 +23,7 @@ mod tests { use itertools::Itertools; use namada::ibc::tx_msg::Msg; use namada::ledger::ibc::handler::IbcActions; + use namada::ledger::ibc::storage as ibc_storage; use namada::ledger::ibc::vp::{ get_dummy_header as tm_dummy_header, Error as IbcError, }; @@ -1215,11 +1216,14 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was escrowed - let escrow = address::Address::Internal( - address::InternalAddress::ibc_escrow_address( - msg.source_port.to_string(), - msg.source_channel.to_string(), - ), + let key_prefix = ibc_storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let token_vp_result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); @@ -1266,6 +1270,12 @@ mod tests { let (port_id, channel_id, channel_writes) = ibc::prepare_opened_channel(&conn_id, false); writes.extend(channel_writes); + // the origin-specific token + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); + let key = token::multitoken_balance_key(&key_prefix, &sender); + let init_bal = Amount::from(1_000_000_000u64); + writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { env.storage.write(&key, &val).expect("write error"); @@ -1274,8 +1284,9 @@ mod tests { // Start a transaction to send a packet // Set this chain is the sink zone - let token = format!("{}/{}/{}", port_id, channel_id, token); - let msg = ibc::msg_transfer(port_id, channel_id, token, &sender); + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let msg = + ibc::msg_transfer(port_id.clone(), channel_id, denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1294,8 +1305,12 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was burned - let burn = - address::Address::Internal(address::InternalAddress::IbcBurn); + let key_prefix = + ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); + let burn = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcBurn), + ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); assert!(result.expect("token validation failed unexpectedly")); } @@ -1313,6 +1328,13 @@ mod tests { let (port_id, channel_id, channel_writes) = ibc::prepare_opened_channel(&conn_id, false); writes.extend(channel_writes); + // the origin-specific token + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); + let key = token::multitoken_balance_key(&key_prefix, &receiver); + let init_bal = Amount::from(1_000_000_000u64); + writes.insert(key, init_bal.try_to_vec().unwrap()); + writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { env.storage.write(&key, &val).expect("write error"); @@ -1321,7 +1343,7 @@ mod tests { // packet let packet = ibc::received_packet( - port_id, + port_id.clone(), channel_id, ibc::sequence(1), token.to_string(), @@ -1348,8 +1370,12 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was minted - let mint = - address::Address::Internal(address::InternalAddress::IbcMint); + let key_prefix = + ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); + let mint = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcMint), + ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); assert!(result.expect("token validation failed unexpectedly")); } @@ -1373,20 +1399,19 @@ mod tests { }); }); // escrow in advance - let counterparty = ibc::dummy_channel_counterparty(); - let escrow = address::Address::Internal( - address::InternalAddress::ibc_escrow_address( - port_id.to_string(), - channel_id.to_string(), - ), + let key_prefix = + ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); + let escrow = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let key = token::balance_key(&token, &escrow); let val = Amount::from(1_000_000_000u64).try_to_vec().unwrap(); tx_host_env::with(|env| { - env.storage.write(&key, &val).expect("write error"); + env.storage.write(&escrow, &val).expect("write error"); }); // Set this chain as the source zone + let counterparty = ibc::dummy_channel_counterparty(); let token = format!( "{}/{}/{}", counterparty.port_id().clone(), @@ -1612,11 +1637,14 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded - let escrow = address::Address::Internal( - address::InternalAddress::ibc_escrow_address( - packet.source_port.to_string(), - packet.source_channel.to_string(), - ), + let key_prefix = ibc_storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); @@ -1681,11 +1709,14 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded - let escrow = address::Address::Internal( - address::InternalAddress::ibc_escrow_address( - packet.source_port.to_string(), - packet.source_channel.to_string(), - ), + let key_prefix = ibc_storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index cd2f0a12934..e17270cfb90 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -3,13 +3,12 @@ pub use namada::ledger::ibc::handler::{Error, IbcActions, Result}; use namada::ledger::storage_api::{StorageRead, StorageWrite}; use namada::ledger::tx_env::TxEnv; -use namada::types::address::Address; pub use namada::types::ibc::IbcEvent; use namada::types::storage::{BlockHeight, Key}; use namada::types::time::Rfc3339String; use namada::types::token::Amount; -use crate::token::transfer; +use crate::token::transfer_with_keys; use crate::Ctx; impl IbcActions for Ctx { @@ -50,12 +49,11 @@ impl IbcActions for Ctx { fn transfer_token( &mut self, - src: &Address, - dest: &Address, - token: &Address, + src: &Key, + dest: &Key, amount: Amount, ) -> std::result::Result<(), Self::Error> { - transfer(self, src, dest, token, None, amount)?; + transfer_with_keys(self, src, dest, amount)?; Ok(()) } diff --git a/wasm/checksums.json b/wasm/checksums.json index 8ddbe1dc853..59b0074caca 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.cfd4508bed8da57ff8cb89b030534ca7e4fe1c802b90a89a8cd4abbe6871fe0a.wasm", + "tx_ibc.wasm": "tx_ibc.0cf0332b4f69a989b79c5068d36053949b01ac462fd6f3978a1c0278b85ba450.wasm", + "tx_init_account.wasm": "tx_init_account.4881a8dc127f16e0e08ed18b04013e1f386688ce7391218a57be3eab3a884c96.wasm", + "tx_init_nft.wasm": "tx_init_nft.ea5285472e11de3b3a1bef60bb9e227252976c31a9f60c0d388d70f361679ac0.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.86269b4a7e90efb297d60dc97a79a94c2ba2727b4173083c5d39db5f7a64a459.wasm", + "tx_init_validator.wasm": "tx_init_validator.cf81a933705f4b027e47c51b53d788b56accc05e1b15b32537b6fece6d9f7b60.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.6ccd6d4f01dacaac3057592b3fe32f4478e2698d51a722ba059d49e8e2e7194d.wasm", + "tx_transfer.wasm": "tx_transfer.4fdb8a897d579c244fbb77ab7e21b6121d46aa1087aa41059ec49474b970c571.wasm", + "tx_unbond.wasm": "tx_unbond.9dc2c92511bcac6f181f1287fce24ce592ef6da00ca913f151cc75f14bf28553.wasm", + "tx_update_vp.wasm": "tx_update_vp.370eb3cb15f66363319211355350117bb11b2effb4c57a52d18813371aa143bf.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7fe9da954ad4e837d5a618d214d1a4264c7daabf73158188724f82464e82a935.wasm", + "tx_withdraw.wasm": "tx_withdraw.cefbf27a1eead88c02b2f846c8bb7b0995dbcac49c9b412692eb71b1c186c29f.wasm", + "vp_nft.wasm": "vp_nft.202e3dc8a01740b438507d624898ad1c8c8771f41746f49fbf099dfa4a582ae4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.46dc197e952cada03200deae88ba0f9491e5cc5d86faf25152c681572c444fbf.wasm", + "vp_token.wasm": "vp_token.746b17c48d1541a29ccbf861c21280351489970e672fb54505788fc910877025.wasm", + "vp_user.wasm": "vp_user.67cf3481ce8ae0f859c4f1f9025120b202263438ea9e6602267337643073d7c1.wasm" } \ No newline at end of file