diff --git a/.changelog/unreleased/bug-fixes/3409-masp-ibc-replay-protection-using-txdata-on-0.39.0.md b/.changelog/unreleased/bug-fixes/3409-masp-ibc-replay-protection-using-txdata-on-0.39.0.md new file mode 100644 index 0000000000..2f5d64cc2e --- /dev/null +++ b/.changelog/unreleased/bug-fixes/3409-masp-ibc-replay-protection-using-txdata-on-0.39.0.md @@ -0,0 +1,2 @@ +- Add replay protection to MASP-IBC transactions. + ([\#3409](https://github.com/anoma/namada/pull/3409)) \ No newline at end of file diff --git a/.github/workflows/scripts/hermes.txt b/.github/workflows/scripts/hermes.txt index e0d562fa8b..b2649256e6 100644 --- a/.github/workflows/scripts/hermes.txt +++ b/.github/workflows/scripts/hermes.txt @@ -1 +1 @@ -1.8.2-namada-beta12-rc +1.8.2-namada-beta12-rc3 diff --git a/Cargo.lock b/Cargo.lock index 73f102d855..ce34b11d4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,9 +972,9 @@ dependencies = [ [[package]] name = "ciborium" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -983,18 +983,18 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", - "half 2.4.1", + "half", ] [[package]] @@ -3019,16 +3019,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hash32" version = "0.2.1" @@ -4073,13 +4063,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "libc", - "windows-sys 0.52.0", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -4909,6 +4899,7 @@ dependencies = [ "prost-types 0.12.3", "rand 0.8.5", "rand_core 0.6.4", + "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -5295,7 +5286,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "regex", - "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -6116,9 +6106,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.5" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -6127,9 +6117,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -6137,9 +6127,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", @@ -6150,9 +6140,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -6235,9 +6225,9 @@ checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits 0.2.17", "plotters-backend", @@ -6248,15 +6238,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] @@ -7339,9 +7329,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -7390,15 +7380,15 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ - "half 1.8.2", + "half", "serde", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -9121,9 +9111,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce4a267a570e121c9375136adefa2c48810273907de9c6817bc19db4d6144bc" +checksum = "b1852ee143a2d8143265bfee017c43bf690702d6c2b45a763a2f13e669f5b7ec" dependencies = [ "bytes", "cfg-if", @@ -9150,9 +9140,9 @@ dependencies = [ [[package]] name = "wasmer-cache" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a40804bcc2567f112003182fc5edc29584da5199c4a1f5a8d6a6e4b65feff0" +checksum = "092ca4da29a7320b99c9b6660dfbde055f4db0a71c1273860272c213b2f7e019" dependencies = [ "blake3", "hex", @@ -9162,9 +9152,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c23098e86ef1038155684fe50f0c1079a0e2a2e70f115b789df17e6ba98d20" +checksum = "6b4f157d715f3bb60c2c9d7b9e48299a30e9209f87f4484f79f9cd586b40b6ee" dependencies = [ "backtrace", "bytes", @@ -9190,9 +9180,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-cranelift" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95287b79973ad5f485215733ef9f0d4bb951a6b7e655585d2bd3d4a4ba1253c9" +checksum = "eb457e66b77ca2188fbbd6c2056ec6e8ccb4bddee73e60ba9d39733d7b2e8068" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -9209,9 +9199,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d78d59be3ce78ad859e176b88f0d5bec0120ece0684922d7c5da1289e251b1" +checksum = "0a3196b2a87d5c6692021ece7ad1cf7fe43b7f1669c3aba1b8ccfcebe660070c" dependencies = [ "byteorder", "dynasm", @@ -9250,9 +9240,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48f36aeeecb655f15fdd358bdf6e4cec27df181468fa4226084157e8462bd5e" +checksum = "32cd5732ff64370e98986f9753cce13b91cc9d3c4b649e31b0d08d5db69164ea" dependencies = [ "proc-macro-error", "proc-macro2", @@ -9262,9 +9252,9 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cb97b6b20084757a2a8d548dc0d4179c3fe9e2d711740423a1e6aa3f8b9091" +checksum = "c890fd0dbda40df03977b899d1ad7113deba3c225f2cc7b88deb7633044d3e07" dependencies = [ "bytecheck", "enum-iterator", @@ -9283,9 +9273,9 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc1e19d986844b17b927ec8b0c7f3da6a7a2c2cb3b0f8ca5d4cb1a1f71bfb124" +checksum = "5e0dc60ab800cf0bd44e2d35d88422d256d2470b00c72778f91bfb826c42dbd0" dependencies = [ "backtrace", "cc", @@ -9785,9 +9775,9 @@ dependencies = [ [[package]] name = "yaml-rust2" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498f4d102a79ea1c9d4dd27573c0fc96ad74c023e8da38484e47883076da25fb" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" dependencies = [ "arraydeque", "encoding_rs", diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index aba3cb9802..cfba20a1f7 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -403,7 +403,7 @@ fn prepare_ibc_tx_and_ctx(bench_name: &str) -> (BenchShieldedCtx, BatchedTx) { shielded_ctx.generate_shielded_action( Amount::native_whole(10), TransferSource::ExtendedSpendingKey(albert_spending_key), - TransferTarget::Address(defaults::bertha_address()), + defaults::bertha_address().to_string(), ) } _ => panic!("Unexpected bench test"), diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index d84d5cec88..c647dc1c90 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -61,6 +61,7 @@ proptest = {workspace = true, optional = true} prost-types.workspace = true rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} +ripemd.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index c31734a803..7b8b65e5cc 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -8,13 +8,15 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; +use masp_primitives::transaction::TransparentAddress; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; +use ripemd::Digest as RipemdDigest; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::address::{Address, DecodeError, HASH_HEX_LEN, MASP}; +use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; use crate::hash::Hash; use crate::impl_display_and_from_str_via_format; use crate::storage::Epoch; @@ -467,6 +469,14 @@ impl TransferSource { _ => None, } } + + /// Get the contained transparent address data, if any + pub fn t_addr_data(&self) -> Option { + match self { + Self::Address(x) => Some(TAddrData::Addr(x.clone())), + _ => None, + } + } } impl Display for TransferSource { @@ -478,13 +488,69 @@ impl Display for TransferSource { } } +/// Represents the pre-image to a TransparentAddress +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshDeserializer)] +pub enum TAddrData { + /// A transparent address within Namada + Addr(Address), + /// An IBC address + Ibc(String), +} + +impl TAddrData { + /// Get the transparent address that this target would effectively go to + pub fn effective_address(&self) -> Address { + match self { + Self::Addr(x) => x.clone(), + // An IBC signer address effectively means that assets are + // associated with the IBC internal address + Self::Ibc(_) => IBC, + } + } + + /// Get the contained IBC receiver, if any + pub fn ibc_receiver_address(&self) -> Option { + match self { + Self::Ibc(address) => Some(address.clone()), + _ => None, + } + } + + /// Get the contained Address, if any + pub fn address(&self) -> Option
{ + match self { + Self::Addr(x) => Some(x.clone()), + _ => None, + } + } + + /// Convert transparent address data into a transparent address + pub fn taddress(&self) -> TransparentAddress { + TransparentAddress(<[u8; 20]>::from(ripemd::Ripemd160::digest( + sha2::Sha256::digest(&self.serialize_to_vec()), + ))) + } +} + +/// Convert a receiver string to a TransparentAddress +pub fn ibc_taddr(receiver: String) -> TransparentAddress { + TAddrData::Ibc(receiver).taddress() +} + +/// Convert a Namada Address to a TransparentAddress +pub fn addr_taddr(addr: Address) -> TransparentAddress { + TAddrData::Addr(addr).taddress() +} + /// Represents a target for the funds of a transfer -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshDeserializer)] pub enum TransferTarget { /// A transfer going to a transparent address Address(Address), /// A transfer going to a shielded address PaymentAddress(PaymentAddress), + /// A transfer going to an IBC address + Ibc(String), } impl TransferTarget { @@ -492,9 +558,12 @@ impl TransferTarget { pub fn effective_address(&self) -> Address { match self { Self::Address(x) => x.clone(), - // An ExtendedSpendingKey for a source effectively means that - // assets will be drawn from the MASP + // A PaymentAddress for a target effectively means that assets will + // be sent to the MASP Self::PaymentAddress(_) => MASP, + // An IBC signer address for a target effectively means that assets + // will be sent to the IBC internal address + Self::Ibc(_) => IBC, } } @@ -513,6 +582,15 @@ impl TransferTarget { _ => None, } } + + /// Get the contained TAddrData, if any + pub fn t_addr_data(&self) -> Option { + match self { + Self::Address(x) => Some(TAddrData::Addr(x.clone())), + Self::Ibc(x) => Some(TAddrData::Ibc(x.clone())), + _ => None, + } + } } impl Display for TransferTarget { @@ -520,6 +598,7 @@ impl Display for TransferTarget { match self { Self::Address(x) => x.fmt(f), Self::PaymentAddress(address) => address.fmt(f), + Self::Ibc(x) => x.fmt(f), } } } diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 69588b9518..5c855fdb8f 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -942,9 +942,18 @@ impl From for IbcAmount { } } +impl TryFrom for Amount { + type Error = AmountParseError; + + fn try_from(amount: IbcAmount) -> Result { + let uint = Uint(primitive_types::U256::from(amount).0); + Self::from_uint(uint, 0) + } +} + impl From for IbcAmount { fn from(amount: DenominatedAmount) -> Self { - amount.canonical().amount.into() + amount.amount.into() } } diff --git a/crates/ibc/src/actions.rs b/crates/ibc/src/actions.rs index 63696343a0..8acc41623c 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -20,7 +20,6 @@ use namada_state::{ StorageResult, StorageWrite, WlState, DB, }; use namada_token as token; -use token::DenominatedAmount; use crate::event::IbcEvent; use crate::{ @@ -183,18 +182,9 @@ where D: DB + for<'iter> DBIter<'iter> + 'static, H: StorageHasher + 'static, { - let denom = token::read_denom(state, token)?.ok_or_else(|| { - StorageError::new_alloc(format!("No denomination for {token}")) - })?; - let amount = DenominatedAmount::new(target.amount, denom).canonical(); - if amount.denom().0 != 0 { - return Err(StorageError::new_alloc(format!( - "The amount for the IBC transfer should be an integer: {amount}" - ))); - } let token = PrefixedCoin { denom: token.to_string().parse().expect("invalid token"), - amount: amount.amount().into(), + amount: target.amount.into(), }; let packet_data = PacketData { token, diff --git a/crates/ibc/src/context/common.rs b/crates/ibc/src/context/common.rs index 536ccb10eb..14876f5cb2 100644 --- a/crates/ibc/src/context/common.rs +++ b/crates/ibc/src/context/common.rs @@ -494,7 +494,6 @@ pub trait IbcCommonContext: IbcStorageContext { /// Calculate the packet commitment fn compute_packet_commitment( - &self, packet_data: &[u8], timeout_height: &TimeoutHeight, timeout_timestamp: &Timestamp, diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index 4d9b5e7b80..ca1128ea68 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -14,7 +14,7 @@ use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::address::{Address, InternalAddress}; use namada_core::uint::Uint; -use namada_token::{read_denom, Amount, Denomination}; +use namada_token::Amount; use super::common::IbcCommonContext; use crate::{storage, IBC_ESCROW_ADDRESS}; @@ -58,11 +58,8 @@ where }; // Convert IBC amount to Namada amount for the token - let denom = read_denom(&*self.inner.borrow(), &token) - .map_err(ContextError::from)? - .unwrap_or(Denomination(0)); let uint_amount = Uint(primitive_types::U256::from(coin.amount).0); - let amount = Amount::from_uint(uint_amount, denom).map_err(|e| { + let amount = Amount::from_uint(uint_amount, 0).map_err(|e| { TokenTransferError::ContextError( ChannelError::Other { description: format!( diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 15a673c9c6..5bdf40bc22 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -225,7 +225,7 @@ where /// Check the result of receiving the packet by checking the packet /// acknowledgement - fn is_receiving_success( + pub fn is_receiving_success( &self, msg: &IbcMsgRecvPacket, ) -> Result { diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index 7502cf1e31..4d382cf2d4 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -10,6 +10,7 @@ use ibc::primitives::proto::Protobuf; use namada_token::ShieldingTransfer; /// The different variants of an Ibc message +#[derive(Debug, Clone)] pub enum IbcMessage { /// Ibc Envelop Envelope(Box), diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index b187b2e41c..36e6dd4458 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -521,6 +521,18 @@ pub fn is_ibc_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr) if *addr == Address::Internal(InternalAddress::Ibc)) } +/// Checks if the key is an IBC commitment key +pub fn is_ibc_commitment_key(key: &Key) -> Option { + let addr = Address::Internal(InternalAddress::Ibc); + let ibc_addr_key = Key::from(addr.to_db_key()); + let suffix = key.split_prefix(&ibc_addr_key)??; + if let Ok(Path::Commitment(path)) = Path::from_str(&suffix.to_string()) { + Some(path) + } else { + None + } +} + /// Returns the owner and the token hash if the given key is the denom key pub fn is_ibc_trace_key(key: &Key) -> Option<(String, String)> { match &key.segments[..] { diff --git a/crates/namada/src/ledger/native_vp/ibc/mod.rs b/crates/namada/src/ledger/native_vp/ibc/mod.rs index 32e92d901a..2fe5d3d0e4 100644 --- a/crates/namada/src/ledger/native_vp/ibc/mod.rs +++ b/crates/namada/src/ledger/native_vp/ibc/mod.rs @@ -428,7 +428,6 @@ mod tests { use namada_ibc::event::IbcEventType; use namada_state::testing::TestState; use namada_state::StorageRead; - use namada_token::NATIVE_MAX_DECIMAL_PLACES; use namada_tx::data::TxType; use namada_tx::{Authorization, Code, Data, Section, Tx}; use prost::Message; @@ -2196,7 +2195,7 @@ mod tests { packet_data: PacketData { token: PrefixedCoin { denom: nam().to_string().parse().unwrap(), - amount: 100.into(), + amount: amount.into(), }, sender: sender.to_string().into(), receiver: "receiver".to_string().into(), @@ -2228,12 +2227,7 @@ mod tests { keys_changed.insert(commitment_key); // withdraw let withdraw_key = withdraw_key(&nam()); - let bytes = Amount::from_str( - msg.packet_data.token.amount.to_string(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap() - .serialize_to_vec(); + let bytes = amount.serialize_to_vec(); state .write_log_mut() .write(&withdraw_key, bytes) @@ -2711,7 +2705,7 @@ mod tests { packet_data: PacketData { token: PrefixedCoin { denom: nam().to_string().parse().unwrap(), - amount: 100u64.into(), + amount: amount.into(), }, sender: established_address_1().to_string().into(), receiver: "receiver".to_string().into(), @@ -2766,12 +2760,7 @@ mod tests { let data = serde_json::from_slice::(&packet.data) .expect("decoding packet data failed"); let deposit_key = deposit_key(&nam()); - let bytes = Amount::from_str( - data.token.amount.to_string(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap() - .serialize_to_vec(); + let bytes = amount.serialize_to_vec(); state .write_log_mut() .write(&deposit_key, bytes) @@ -2874,7 +2863,7 @@ mod tests { packet_data: PacketData { token: PrefixedCoin { denom: nam().to_string().parse().unwrap(), - amount: 100u64.into(), + amount: amount.into(), }, sender: sender.to_string().into(), receiver: "receiver".to_string().into(), @@ -2929,12 +2918,7 @@ mod tests { let data = serde_json::from_slice::(&packet.data) .expect("decoding packet data failed"); let deposit_key = deposit_key(&nam()); - let bytes = Amount::from_str( - data.token.amount.to_string(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap() - .serialize_to_vec(); + let bytes = amount.serialize_to_vec(); state .write_log_mut() .write(&deposit_key, bytes) diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 9a9f127462..103045a480 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; -use borsh_ext::BorshSerializeExt; +use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::CommitmentTree; use masp_primitives::sapling::Node; @@ -13,35 +13,51 @@ use masp_primitives::transaction::components::{ }; use masp_primitives::transaction::{Transaction, TransparentAddress}; use namada_core::address::Address; -use namada_core::address::InternalAddress::Masp; use namada_core::arith::{checked, CheckedAdd, CheckedSub}; use namada_core::booleans::BoolResultUnitExt; use namada_core::collections::HashSet; -use namada_core::masp::{encode_asset_type, MaspEpoch}; +use namada_core::ibc::apps::transfer::types::is_sender_chain_source; +use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; +use namada_core::ibc::apps::transfer::types::packet::PacketData; +use namada_core::masp::{addr_taddr, encode_asset_type, ibc_taddr, MaspEpoch}; use namada_core::storage::Key; use namada_gas::GasMetering; use namada_governance::storage::is_proposal_accepted; -use namada_sdk::masp::verify_shielded_tx; +use namada_ibc::core::channel::types::msgs::MsgRecvPacket as IbcMsgRecvPacket; +use namada_ibc::core::host::types::identifiers::Sequence; +use namada_ibc::storage::ibc_token; +use namada_ibc::{IbcCommonContext, IbcMessage}; +use namada_sdk::masp::{verify_shielded_tx, TAddrData}; use namada_state::{ConversionState, OptionExt, ResultExt, StateRead}; use namada_token::read_denom; use namada_tx::BatchedTxRef; use namada_vp_env::VpEnv; -use ripemd::Digest as RipemdDigest; -use sha2::Digest as Sha2Digest; use thiserror::Error; use token::storage_key::{ - is_any_shielded_action_balance_key, is_masp_key, is_masp_nullifier_key, + balance_key, is_any_token_balance_key, is_masp_key, is_masp_nullifier_key, is_masp_token_map_key, is_masp_transfer_key, masp_commitment_anchor_key, masp_commitment_tree_key, masp_convert_anchor_key, masp_nullifier_key, - ShieldedActionOwner, }; use token::Amount; +use crate::address::{InternalAddress, IBC, MASP}; +use crate::ledger::ibc::storage; +use crate::ledger::ibc::storage::{ + ibc_trace_key, ibc_trace_key_prefix, is_ibc_commitment_key, + is_ibc_trace_key, +}; use crate::ledger::native_vp; +use crate::ledger::native_vp::ibc::context::VpValidationContext; use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::sdk::ibc::apps::transfer::types::{ack_success_b64, PORT_ID_STR}; +use crate::sdk::ibc::core::channel::types::acknowledgement::AcknowledgementStatus; +use crate::sdk::ibc::core::channel::types::commitment::{ + compute_ack_commitment, AcknowledgementCommitment, PacketCommitment, +}; +use crate::sdk::ibc::core::channel::types::packet::Packet; use crate::token; use crate::token::MaspDigitPos; -use crate::uint::I320; +use crate::uint::{Uint, I320}; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -69,10 +85,10 @@ where // the other balances maps the token address to the addresses of the // senders/receivers, their balance diff and whether this is positive or // negative diff -#[derive(Default)] +#[derive(Default, Debug, Clone)] struct ChangedBalances { tokens: BTreeMap, - decoder: BTreeMap, + decoder: BTreeMap, pre: BTreeMap>, post: BTreeMap>, } @@ -279,66 +295,346 @@ where Ok(()) } + /// Look up the IBC denomination from a IbcToken. + pub fn query_ibc_denom( + &self, + token: impl AsRef, + owner: Option<&Address>, + ) -> Result { + let hash = match Address::decode(token.as_ref()) { + Ok(Address::Internal(InternalAddress::IbcToken(hash))) => { + hash.to_string() + } + _ => return Ok(token.as_ref().to_string()), + }; + + if let Some(owner) = owner { + let ibc_trace_key = ibc_trace_key(owner.to_string(), &hash); + if let Some(ibc_denom) = self.ctx.read_pre(&ibc_trace_key)? { + return Ok(ibc_denom); + } + } + + // No owner is specified or the owner doesn't have the token + let ibc_denom_prefix = ibc_trace_key_prefix(None); + let ibc_denoms = self.ctx.iter_prefix(&ibc_denom_prefix)?; + for (key, ibc_denom, gas) in ibc_denoms { + self.ctx.charge_gas(gas)?; + if let Some((_, token_hash)) = + is_ibc_trace_key(&Key::parse(key).into_storage_result()?) + { + if token_hash == hash { + return String::try_from_slice(&ibc_denom[..]) + .into_storage_result() + .map_err(Error::NativeVpError); + } + } + } + + Ok(token.as_ref().to_string()) + } + + // Find the given IBC message in the changed keys and return the associated + // sequence number + fn find_ibc_transfer_sequence( + &self, + message: &IbcMsgTransfer, + keys_changed: &BTreeSet, + ) -> Result> { + // Compute the packet commitment for this message + let packet_data_bytes = serde_json::to_vec(&message.packet_data) + .map_err(native_vp::Error::new)?; + let packet_commitment = + VpValidationContext::<'a, 'a, S, CA>::compute_packet_commitment( + &packet_data_bytes, + &message.timeout_height_on_b, + &message.timeout_timestamp_on_b, + ); + // Try to find a key change with the same port, channel, and commitment + // as this message and note its sequence number + for key in keys_changed { + let Some(path) = is_ibc_commitment_key(key) else { + continue; + }; + if path.port_id == message.port_id_on_a + && path.channel_id == message.chan_id_on_a + { + let Some(storage_commitment): Option = + self.ctx.read_bytes_post(key)?.map(Into::into) + else { + // Ignore this key if the value does not exist + continue; + }; + if packet_commitment == storage_commitment { + return Ok(Some(path.sequence)); + } + } + } + Ok(None) + } + + // Apply the given transfer message to the changed balances structure + fn apply_transfer_msg( + &self, + mut acc: ChangedBalances, + msg: &IbcMsgTransfer, + keys_changed: &BTreeSet, + ) -> Result { + // If a key change with the same port, channel, and commitment as this + // message cannot be found, then ignore this message. Though this check + // is done in the IBC VP, the test is repeated here to avoid making + // assumptions about how the IBC VP interprets the given message. + if self + .find_ibc_transfer_sequence(msg, keys_changed)? + .is_none() + { + return Ok(acc); + }; + + // Obtain the address corresponding to the packet denomination + let denom = msg.packet_data.token.denom.to_string(); + let token = if denom.contains('/') { + ibc_token(denom) + } else { + Address::decode(denom).into_storage_result()? + }; + // Add currency units to the amount in the packet + let delta = ValueSum::from_pair( + token.clone(), + Amount::from_uint(Uint(*msg.packet_data.token.amount), 0).unwrap(), + ); + // Remove pre-existing balance increases to the IBC account since we + // want to record increases to specific receivers + if is_sender_chain_source( + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + &msg.packet_data.token.denom, + ) { + let ibc_taddr = addr_taddr(IBC); + let post_entry = acc + .post + .get(&ibc_taddr) + .cloned() + .unwrap_or(ValueSum::zero()); + acc.post.insert( + ibc_taddr, + checked!(post_entry - &delta).map_err(native_vp::Error::new)?, + ); + } + // Record an increase to the balance of a specific IBC receiver + let receiver = ibc_taddr(msg.packet_data.receiver.to_string()); + let post_entry = + acc.post.get(&receiver).cloned().unwrap_or(ValueSum::zero()); + acc.post.insert( + receiver, + checked!(post_entry + &delta).map_err(native_vp::Error::new)?, + ); + + Ok(acc) + } + + // Check if IBC message was received successfully in this state transition + fn is_receiving_success( + &self, + packet: &Packet, + keys_changed: &BTreeSet, + ) -> Result { + // Ensure that the event corresponds to the current changes to storage + let ack_key = storage::ack_key( + &packet.port_id_on_a, + &packet.chan_id_on_a, + packet.seq_on_a, + ); + if !keys_changed.contains(&ack_key) { + // Ignore packet if it was not acknowledged during this state change + return Ok(false); + } + // If the receive is a success, then the commitment is unique + let succ_ack_commitment = compute_ack_commitment( + &AcknowledgementStatus::success(ack_success_b64()).into(), + ); + Ok(match self.ctx.read_bytes_post(&ack_key)? { + // Success happens only if commitment equals the above + Some(value) => { + AcknowledgementCommitment::from(value) == succ_ack_commitment + } + // Acknowledgement key non-existence is failure + None => false, + }) + } + + // Apply the given write acknowledge to the changed balances structure + fn apply_recv_msg( + &self, + mut acc: ChangedBalances, + msg: &IbcMsgRecvPacket, + packet_data: &PacketData, + keys_changed: &BTreeSet, + ) -> Result { + // If the transfer was a failure, then enable funds to + // be withdrawn from the IBC internal address + if self.is_receiving_success(&msg.packet, keys_changed)? { + // Mirror how the IBC token is derived in + // gen_ibc_shielded_transfer in the non-refund case + let ibc_denom = self.query_ibc_denom( + packet_data.token.denom.to_string(), + Some(&Address::Internal(InternalAddress::Ibc)), + )?; + let token = namada_ibc::received_ibc_token( + ibc_denom, + &msg.packet.port_id_on_a, + &msg.packet.chan_id_on_a, + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + ) + .into_storage_result() + .map_err(Error::NativeVpError)?; + let delta = ValueSum::from_pair( + token.clone(), + Amount::from_uint(Uint(*packet_data.token.amount), 0).unwrap(), + ); + // Enable funds to be taken from the IBC internal + // address and be deposited elsewhere + // Required for the IBC internal Address to release + // funds + let ibc_taddr = addr_taddr(IBC); + let pre_entry = + acc.pre.get(&ibc_taddr).cloned().unwrap_or(ValueSum::zero()); + acc.pre.insert( + ibc_taddr, + checked!(pre_entry + &delta).map_err(native_vp::Error::new)?, + ); + } + Ok(acc) + } + + // Apply relevant IBC packets to the changed balances structure + fn apply_ibc_packet( + &self, + mut acc: ChangedBalances, + ibc_msg: &IbcMessage, + keys_changed: &BTreeSet, + ) -> Result { + match ibc_msg { + // This event is emitted on the sender + IbcMessage::Transfer(msg) => { + // Get the packet commitment from post-storage that corresponds + // to this event + let receiver = msg.message.packet_data.receiver.to_string(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = + self.apply_transfer_msg(acc, &msg.message, keys_changed)?; + } + // This event is emitted on the receiver + IbcMessage::RecvPacket(msg) + if msg.message.packet.port_id_on_b.as_str() == PORT_ID_STR => + { + let packet_data = serde_json::from_slice::( + &msg.message.packet.data, + ) + .map_err(native_vp::Error::new)?; + let receiver = packet_data.receiver.to_string(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = self.apply_recv_msg( + acc, + &msg.message, + &packet_data, + keys_changed, + )?; + } + // Ignore all other IBC events + _ => {} + } + Result::<_>::Ok(acc) + } + + // Apply the balance change to the changed balances structure + fn apply_balance_change( + &self, + mut result: ChangedBalances, + [token, counterpart]: [&Address; 2], + ) -> Result { + let denom = read_denom(&self.ctx.pre(), token)?.ok_or_err_msg( + "No denomination found in storage for the given token", + )?; + // Record the token without an epoch to facilitate later decoding + unepoched_tokens(token, denom, &mut result.tokens)?; + let counterpart_balance_key = balance_key(token, counterpart); + let pre_balance: Amount = self + .ctx + .read_pre(&counterpart_balance_key)? + .unwrap_or_default(); + let post_balance: Amount = self + .ctx + .read_post(&counterpart_balance_key)? + .unwrap_or_default(); + // Public keys must be the hash of the sources/targets + let address_hash = addr_taddr(counterpart.clone()); + // Enable the decoding of these counterpart addresses + result + .decoder + .insert(address_hash, TAddrData::Addr(counterpart.clone())); + // Finally record the actual balance change starting with the initial + // state + let pre_entry = result + .pre + .get(&address_hash) + .cloned() + .unwrap_or(ValueSum::zero()); + result.pre.insert( + address_hash, + checked!( + pre_entry + &ValueSum::from_pair((*token).clone(), pre_balance) + ) + .map_err(native_vp::Error::new)?, + ); + // And then record thee final state + let post_entry = result + .post + .get(&address_hash) + .cloned() + .unwrap_or(ValueSum::zero()); + result.post.insert( + address_hash, + checked!( + post_entry + + &ValueSum::from_pair((*token).clone(), post_balance) + ) + .map_err(native_vp::Error::new)?, + ); + Result::<_>::Ok(result) + } + // Check that transfer is pinned correctly and record the balance changes fn validate_state_and_get_transfer_data( &self, keys_changed: &BTreeSet, + ibc_msgs: &[IbcMessage], ) -> Result { // Get the changed balance keys - let mut counterparts_balances = keys_changed - .iter() - .filter_map(is_any_shielded_action_balance_key); + let mut counterparts_balances = + keys_changed.iter().filter_map(is_any_token_balance_key); - counterparts_balances.try_fold( - ChangedBalances::default(), - |mut result, (token, counterpart)| { - let denom = read_denom(&self.ctx.pre(), token)?.ok_or_err_msg( - "No denomination found in storage for the given token", - )?; - unepoched_tokens(token, denom, &mut result.tokens)?; - let counterpart_balance_key = counterpart.to_balance_key(token); - let mut pre_balance: Amount = self - .ctx - .read_pre(&counterpart_balance_key)? - .unwrap_or_default(); - let mut post_balance: Amount = self - .ctx - .read_post(&counterpart_balance_key)? - .unwrap_or_default(); - if let ShieldedActionOwner::Minted = counterpart { - // When receiving ibc transfers we mint and also shield so - // we have two credits/debits, we need - // to mock the mint balance as - // the opposite change - std::mem::swap(&mut pre_balance, &mut post_balance); - } - // Public keys must be the hash of the sources/targets - let address_hash = TransparentAddress(<[u8; 20]>::from( - ripemd::Ripemd160::digest(sha2::Sha256::digest( - &counterpart.to_address_ref().serialize_to_vec(), - )), - )); - - result - .decoder - .insert(address_hash, counterpart.to_address_ref().clone()); - let pre_entry = - result.pre.entry(address_hash).or_insert(ValueSum::zero()); - let post_entry = - result.post.entry(address_hash).or_insert(ValueSum::zero()); - *pre_entry = checked!( - pre_entry - + &ValueSum::from_pair((*token).clone(), pre_balance) - ) - .map_err(native_vp::Error::new)?; - *post_entry = checked!( - post_entry - + &ValueSum::from_pair((*token).clone(), post_balance) - ) - .map_err(native_vp::Error::new)?; - Ok(result) - }, - ) + // Apply the balance changes to the changed balances structure + let mut changed_balances = counterparts_balances + .try_fold(ChangedBalances::default(), |acc, account| { + self.apply_balance_change(acc, account) + })?; + + let ibc_addr = TAddrData::Addr(IBC); + // Enable decoding the IBC address hash + changed_balances.decoder.insert(addr_taddr(IBC), ibc_addr); + + // Go through the IBC events and note the balance changes they imply + let changed_balances = + ibc_msgs.iter().try_fold(changed_balances, |acc, ibc_msg| { + // Apply all IBC packets to the changed balances structure + self.apply_ibc_packet(acc, ibc_msg, keys_changed) + })?; + + Ok(changed_balances) } // Check that MASP Transaction and state changes are valid @@ -359,6 +655,7 @@ where Error::NativeVpError(native_vp::Error::new_const(msg)) })?; let conversion_state = self.ctx.state.in_mem().get_conversion_state(); + let ibc_msgs = self.ctx.get_ibc_message(tx_data).ok(); // Get the Transaction object from the actions let masp_section_ref = namada_tx::action::get_masp_section_ref( @@ -391,16 +688,13 @@ where // The Sapling value balance adds to the transparent tx pool let mut transparent_tx_pool = shielded_tx.sapling_value_balance(); - // Check the validity of the keys and get the transfer data - let mut changed_balances = - self.validate_state_and_get_transfer_data(keys_changed)?; + let mut changed_balances = self.validate_state_and_get_transfer_data( + keys_changed, + ibc_msgs.as_slice(), + )?; - let masp_address_hash = TransparentAddress(<[u8; 20]>::from( - ripemd::Ripemd160::digest(sha2::Sha256::digest( - &Address::Internal(Masp).serialize_to_vec(), - )), - )); + let masp_address_hash = addr_taddr(MASP); verify_sapling_balancing_value( changed_balances .pre @@ -445,21 +739,22 @@ where // Ensure that every account for which balance has gone down has // authorized this transaction for (addr, pre) in changed_balances.pre { - if changed_balances.post[&addr] < pre && addr != masp_address_hash { + if changed_balances + .post + .get(&addr) + .unwrap_or(&ValueSum::zero()) + < &pre + && addr != masp_address_hash + { signers.insert(addr); } } - let ibc_address_hash = TransparentAddress(<[u8; 20]>::from( - ripemd::Ripemd160::digest(sha2::Sha256::digest( - &Address::Internal(namada_core::address::InternalAddress::Ibc) - .serialize_to_vec(), - )), - )); - // Ensure that this transaction is authorized by all involved parties for signer in signers { - if signer == ibc_address_hash { + if let Some(TAddrData::Addr(IBC)) = + changed_balances.decoder.get(&signer) + { // If the IBC address is a signatory, then it means that either // Tx - Transaction(s) causes a decrease in the IBC balance or // one of the Transactions' transparent inputs is the IBC. We @@ -474,7 +769,9 @@ where // Transaction(s). if let Some(transp_bundle) = shielded_tx.transparent_bundle() { for vout in transp_bundle.vout.iter() { - if vout.address == ibc_address_hash { + if let Some(TAddrData::Ibc(_)) = + changed_balances.decoder.get(&vout.address) + { let error = native_vp::Error::new_const( "Simultaneous credit and debit of IBC account \ in a MASP transaction not allowed", @@ -485,7 +782,9 @@ where } } } - } else if let Some(signer) = changed_balances.decoder.get(&signer) { + } else if let Some(TAddrData::Addr(signer)) = + changed_balances.decoder.get(&signer) + { // Otherwise the signer must be decodable so that we can // manually check the signatures let public_keys_index_map = diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index bd5503f316..657147475d 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -1174,12 +1174,12 @@ impl BenchShieldedCtx { self, amount: Amount, source: TransferSource, - target: TransferTarget, + target: String, ) -> (Self, BatchedTx) { let (ctx, tx) = self.generate_masp_tx( amount, source.clone(), - TransferTarget::Address(Address::Internal(InternalAddress::Ibc)), + TransferTarget::Ibc(target.clone()), ); let token = PrefixedCoin { @@ -1207,7 +1207,7 @@ impl BenchShieldedCtx { packet_data: PacketData { token, sender: source.effective_address().to_string().into(), - receiver: target.effective_address().to_string().into(), + receiver: target.into(), memo: "".parse().unwrap(), }, timeout_height_on_b: timeout_height, diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 801ecd47dd..c8847c893e 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -122,7 +122,6 @@ prost.workspace = true rand = { workspace = true, optional = true } rand_core = { workspace = true, optional = true } regex.workspace = true -ripemd.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 7d83a93895..4b8df12e91 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -839,7 +839,6 @@ pub mod testing { use governance::ProposalType; use ibc::primitives::proto::Any; use masp_primitives::transaction::components::sapling::builder::StoredBuildParams; - use masp_primitives::transaction::TransparentAddress; use namada_account::{InitAccount, UpdateAccount}; use namada_core::address::testing::{ arb_established_address, arb_non_internal_address, @@ -847,6 +846,7 @@ pub mod testing { use namada_core::eth_bridge_pool::PendingTransfer; use namada_core::hash::testing::arb_hash; use namada_core::key::testing::arb_common_keypair; + use namada_core::masp::addr_taddr; use namada_governance::storage::proposal::testing::{ arb_init_proposal, arb_vote_proposal, }; @@ -863,8 +863,6 @@ pub mod testing { use proptest::prelude::{Just, Strategy}; use proptest::{arbitrary, collection, option, prop_compose, prop_oneof}; use prost::Message; - use ripemd::Digest as RipemdDigest; - use sha2::Digest; use token::testing::arb_vectorized_transparent_transfer; use token::{ ShieldingMultiTransfer, ShieldingTransferData, @@ -1087,14 +1085,6 @@ pub mod testing { } } - // Encode the given Address into TransparentAddress - fn encode_address(source: &Address) -> TransparentAddress { - let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( - source.serialize_to_vec().as_ref(), - )); - TransparentAddress(hash.into()) - } - // Maximum number of notes to include in a transaction const MAX_ASSETS: usize = 2; @@ -1117,8 +1107,8 @@ pub mod testing { code_hash in arb_hash(), (masp_tx_type, (shielded_transfer, asset_types, build_params)) in prop_oneof![ (Just(MaspTxType::Shielded), arb_shielded_transfer(0..MAX_ASSETS)), - (Just(MaspTxType::Shielding), arb_shielding_transfer(encode_address(&transfers.0.first().unwrap().source), 1)), - (Just(MaspTxType::Unshielding), arb_deshielding_transfer(encode_address(&transfers.0.first().unwrap().target), 1)), + (Just(MaspTxType::Shielding), arb_shielding_transfer(addr_taddr(transfers.0.first().unwrap().source.clone()), 1)), + (Just(MaspTxType::Unshielding), arb_deshielding_transfer(addr_taddr(transfers.0.first().unwrap().target.clone()), 1)), ], transfers in Just(transfers), ) -> (Tx, TxData) { diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 77f3ac6656..9caeff7b0d 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -8,7 +8,6 @@ use std::ops::Deref; use std::path::PathBuf; use borsh::{BorshDeserialize, BorshSerialize}; -use borsh_ext::BorshSerializeExt; use lazy_static::lazy_static; use masp_primitives::asset_type::AssetType; #[cfg(feature = "mainnet")] @@ -41,8 +40,7 @@ use masp_primitives::transaction::fees::fixed::FeeRule; use masp_primitives::transaction::sighash::{signature_hash, SignableInput}; use masp_primitives::transaction::txid::TxIdDigester; use masp_primitives::transaction::{ - Authorization, Authorized, Transaction, TransactionData, - TransparentAddress, Unauthorized, + Authorization, Authorized, Transaction, TransactionData, Unauthorized, }; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::bellman::groth16::VerifyingKey; @@ -54,7 +52,7 @@ use namada_core::collections::{HashMap, HashSet}; use namada_core::dec::Dec; pub use namada_core::masp::{ encode_asset_type, AssetData, BalanceOwner, ExtendedViewingKey, - PaymentAddress, TransferSource, TransferTarget, + PaymentAddress, TAddrData, TransferSource, TransferTarget, }; use namada_core::masp::{MaspEpoch, MaspTxRefs}; use namada_core::storage::{BlockHeight, TxIndex}; @@ -72,8 +70,6 @@ use namada_token::{self as token, Denomination, MaspDigitPos}; use namada_tx::{IndexedTx, Tx}; use rand::rngs::StdRng; use rand_core::{CryptoRng, OsRng, RngCore, SeedableRng}; -use ripemd::Digest as RipemdDigest; -use sha2::Digest; use smooth_operator::checked; use thiserror::Error; @@ -1707,19 +1703,15 @@ impl ShieldedContext { // We add a dummy UTXO to our transaction, but only the source // of the parent Transfer object is used to // validate fund availability - let source_enc = source - .address() + let script = source + .t_addr_data() .ok_or_else(|| { Error::Other( "source address should be transparent".to_string(), ) })? - .serialize_to_vec(); + .taddress(); - let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( - source_enc.as_ref(), - )); - let script = TransparentAddress(hash.into()); for (digit, asset_type) in MaspDigitPos::iter().zip(asset_types.iter()) { @@ -1749,24 +1741,6 @@ impl ShieldedContext { .decode_sum(context.client(), value_balance) .await; - // If we are sending to a transparent output, then we will need to - // embed the transparent target address into the - // shielded transaction so that it can be signed - let transparent_target_hash = if payment_address.is_none() { - let target_enc = target - .address() - .ok_or_else(|| { - Error::Other( - "target address should be transparent".to_string(), - ) - })? - .serialize_to_vec(); - Some(ripemd::Ripemd160::digest(sha2::Sha256::digest( - target_enc.as_ref(), - ))) - } else { - None - }; // This indicates how many more assets need to be sent to the // receiver in order to satisfy the requested transfer // amount. @@ -1812,17 +1786,11 @@ impl ShieldedContext { error: builder::Error::SaplingBuild(e), data: None, })?; - } else { + } else if let Some(t_addr_data) = target.t_addr_data() { // If there is a transparent output - let hash = transparent_target_hash - .expect( - "transparent target hash should have been \ - computed already", - ) - .into(); builder .add_transparent_output( - &TransparentAddress(hash), + &t_addr_data.taddress(), *asset_type, contr, ) @@ -1830,6 +1798,12 @@ impl ShieldedContext { error: builder::Error::TransparentBuild(e), data: None, })?; + } else { + return Result::Err(TransferErr::from(Error::Other( + "transaction target must be a payment address or \ + Namada address or IBC address" + .to_string(), + ))); } // Lower what is required of the remaining contribution *rem_amount -= contr; @@ -1843,7 +1817,7 @@ impl ShieldedContext { for (asset_type, val) in asset_types.iter().zip(rem_amount) { shortfall += I128Sum::from_pair(*asset_type, val.into()); } - // Return an insufficient funds error + // Return an insufficient ffunds error return Result::Err(TransferErr::Build { error: builder::Error::InsufficientFunds(shortfall), data: Some(MaspTransferData { @@ -2209,6 +2183,7 @@ pub mod testing { use masp_primitives::transaction::components::sapling::builder::StoredBuildParams; use masp_primitives::transaction::components::sapling::Bundle; use masp_primitives::transaction::components::GROTH_PROOF_SIZE; + use masp_primitives::transaction::TransparentAddress; use masp_proofs::bellman::groth16::{self, Proof}; use proptest::prelude::*; use proptest::sample::SizeRange; diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index f28df466a0..72a919db91 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -33,6 +33,7 @@ use namada_governance::storage::proposal::StorageProposal; use namada_governance::utils::{ compute_proposal_result, ProposalResult, ProposalVotes, Vote, }; +use namada_ibc::is_ibc_denom; use namada_ibc::storage::{ ibc_trace_key, ibc_trace_key_prefix, is_ibc_trace_key, }; @@ -1184,9 +1185,25 @@ pub async fn validate_amount( InputAmount::Unvalidated(amt) => amt.canonical(), InputAmount::Validated(amt) => return Ok(amt), }; - let denom = match convert_response::>( - RPC.vp().token().denomination(context.client(), token).await, - )? { + let base_token = + if let Address::Internal(InternalAddress::IbcToken(ibc_token_hash)) = + token + { + extract_base_token(context, ibc_token_hash.clone(), None).await + } else { + Some(token.clone()) + }; + let denom = if let Some(token) = base_token { + convert_response::>( + RPC.vp() + .token() + .denomination(context.client(), &token) + .await, + )? + } else { + None + }; + let denom = match denom { Some(denom) => Ok(denom), None => { if force { @@ -1372,6 +1389,38 @@ pub async fn query_ibc_tokens( Ok(tokens) } +/// Obtain the base token of the given IBC token hash +pub async fn extract_base_token( + context: &N, + ibc_token_hash: IbcTokenHash, + owner: Option<&Address>, +) -> Option
{ + // First obtain the IBC denomination + let ibc_denom = query_ibc_denom( + context, + Address::Internal(InternalAddress::IbcToken(ibc_token_hash)) + .to_string(), + owner, + ) + .await; + // Then try to extract the base token + if let Some((_trace_path, base_token)) = is_ibc_denom(ibc_denom) { + match Address::decode(&base_token) { + // If the base token successfully decoded into an Address + Ok(base_token) => Some(base_token), + // Otherwise find the Address associated with the base token's alias + Err(_) => context + .wallet() + .await + .find_address(&base_token) + .map(|x| x.into_owned()), + } + } else { + // Otherwise the base token Address is unknown to this client + None + } +} + /// Look up the IBC denomination from a IbcToken. pub async fn query_ibc_denom( context: &N, diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 60e6c19074..e00621815d 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -2485,12 +2485,6 @@ pub async fn build_ibc_transfer( validate_amount(context, args.amount, &args.token, args.tx.force) .await .expect("expected to validate amount"); - if validated_amount.canonical().denom().0 != 0 { - return Err(Error::Other(format!( - "The amount for the IBC transfer should be an integer: {}", - validated_amount - ))); - } // If source is transparent check the balance (MASP balance is checked when // constructing the shielded part) @@ -2520,9 +2514,8 @@ pub async fn build_ibc_transfer( .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let masp_transfer_data = MaspTransferData { source: args.source.clone(), - target: TransferTarget::Address(Address::Internal( - InternalAddress::Ibc, - )), + // The token will be escrowed to IBC address + target: TransferTarget::Ibc(args.receiver.clone()), token: args.token.clone(), amount: validated_amount, }; diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index e53c72814a..6df567e27a 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -601,7 +601,8 @@ where if !masp_reward_keys.contains(&native_token) { // Since MASP rewards are denominated in NAM tokens, ensure that clients // are able to decode them. - masp_reward_keys.push(native_token.clone()); + masp_reward_denoms + .insert(native_token.clone(), NATIVE_MAX_DECIMAL_PLACES.into()); } // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index c1781b6295..4e8dc13a2a 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -159,6 +159,7 @@ fn run_ledger_ibc() -> Result<()> { token, 50000, BERTHA_KEY, + true, )?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; @@ -233,6 +234,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -250,6 +252,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { token, 50000, BERTHA_KEY, + true, )?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; @@ -269,6 +272,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { BERTHA_KEY, &port_id_b, &channel_id_b, + true, None, None, false, @@ -285,6 +289,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { BTC, 100, ALBERT_KEY, + false, )?; shielded_sync(&test_a, AA_VIEWING_KEY)?; // Shieded transfer from Chain A to Chain B @@ -297,6 +302,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -314,6 +320,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -336,6 +343,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, Some(Duration::new(10, 0)), None, false, @@ -401,6 +409,7 @@ fn ibc_namada_gaia() -> Result<()> { ALBERT_KEY, &port_id_namada, &channel_id_namada, + false, None, None, false, @@ -410,7 +419,7 @@ fn ibc_namada_gaia() -> Result<()> { // Check the received token on Gaia let token_addr = find_address(&test, APFEL)?; let ibc_denom = format!("{port_id_gaia}/{channel_id_gaia}/{token_addr}"); - check_gaia_balance(&test_gaia, GAIA_USER, &ibc_denom, 200)?; + check_gaia_balance(&test_gaia, GAIA_USER, &ibc_denom, 200000000)?; // Transfer back from Gaia to Namada let receiver = find_address(&test, ALBERT)?.to_string(); @@ -419,7 +428,7 @@ fn ibc_namada_gaia() -> Result<()> { GAIA_USER, receiver, get_gaia_denom_hash(ibc_denom), - 100, + 100000000, &port_id_gaia, &channel_id_gaia, )?; @@ -456,6 +465,7 @@ fn ibc_namada_gaia() -> Result<()> { ALBERT_KEY, &port_id_namada, &channel_id_namada, + true, None, None, false, @@ -489,6 +499,7 @@ fn ibc_namada_gaia() -> Result<()> { &ibc_denom, 50, ALBERT_KEY, + true, )?; check_balance(&test, AA_VIEWING_KEY, &ibc_denom, 50)?; check_balance(&test, AB_VIEWING_KEY, &ibc_denom, 50)?; @@ -503,6 +514,7 @@ fn ibc_namada_gaia() -> Result<()> { BERTHA_KEY, &port_id_namada, &channel_id_namada, + true, None, None, false, @@ -560,6 +572,7 @@ fn pgf_over_ibc_with_hermes() -> Result<()> { NAM, 100, ALBERT_KEY, + false, )?; // Proposal on Chain A @@ -663,6 +676,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -681,19 +695,19 @@ fn proposal_ibc_token_inflation() -> Result<()> { #[test] fn ibc_rate_limit() -> Result<()> { // Mint limit 2 transfer/channel-0/nam, per-epoch throughput limit 1 NAM - let update_genesis = |mut genesis: templates::All< - templates::Unvalidated, - >, - base_dir: &_| { - genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(50); - genesis.parameters.ibc_params.default_mint_limit = Amount::from_u64(2); - genesis - .parameters - .ibc_params - .default_per_epoch_throughput_limit = Amount::from_u64(1_000_000); - setup::set_validators(1, genesis, base_dir, |_| 0) - }; + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(50); + genesis.parameters.ibc_params.default_mint_limit = + Amount::from_u64(2_000_000); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = + Amount::from_u64(1_000_000); + setup::set_validators(1, genesis, base_dir, |_| 0) + }; let (ledger_a, ledger_b, test_a, test_b) = run_two_nets(update_genesis)?; let _bg_ledger_a = ledger_a.background(); let _bg_ledger_b = ledger_b.background(); @@ -730,6 +744,7 @@ fn ibc_rate_limit() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -745,6 +760,7 @@ fn ibc_rate_limit() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, // expect an error of the throughput limit Some( @@ -771,6 +787,7 @@ fn ibc_rate_limit() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, None, None, false, @@ -795,6 +812,7 @@ fn ibc_rate_limit() -> Result<()> { ALBERT_KEY, &port_id_a, &channel_id_a, + false, Some(Duration::new(20, 0)), None, false, @@ -1561,6 +1579,7 @@ fn transfer_token( ALBERT_KEY, port_id_a, channel_id_a, + false, None, None, false, @@ -1626,22 +1645,8 @@ fn try_invalid_transfers( std::env::set_var(ENV_VAR_CHAIN_ID, test_b.net.chain_id.to_string()); let receiver = find_address(test_b, BERTHA)?; - // invalid amount - transfer( - test_a, - ALBERT, - receiver.to_string(), - NAM, - 10.1, - ALBERT_KEY, - port_id_a, - channel_id_a, - None, - Some("The amount for the IBC transfer should be an integer"), - false, - )?; - // invalid port + std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let nam_addr = find_address(test_a, NAM)?; transfer( test_a, @@ -1652,6 +1657,7 @@ fn try_invalid_transfers( ALBERT_KEY, &"port".parse().unwrap(), channel_id_a, + false, None, // the IBC denom can't be parsed when using an invalid port Some(&format!("Invalid IBC denom: {nam_addr}")), @@ -1668,6 +1674,7 @@ fn try_invalid_transfers( ALBERT_KEY, port_id_a, &"channel-42".parse().unwrap(), + false, None, Some("IBC token transfer error: context error: `ICS04 Channel error"), false, @@ -1676,6 +1683,7 @@ fn try_invalid_transfers( Ok(()) } +#[allow(clippy::too_many_arguments)] fn transfer_on_chain( test: &Test, kind: impl AsRef, @@ -1684,10 +1692,12 @@ fn transfer_on_chain( token: impl AsRef, amount: u64, signer: impl AsRef, + force: bool, ) -> Result<()> { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); - let tx_args = [ + let amount = amount.to_string(); + let mut tx_args = vec![ kind.as_ref(), "--source", sender.as_ref(), @@ -1696,12 +1706,15 @@ fn transfer_on_chain( "--token", token.as_ref(), "--amount", - &amount.to_string(), + &amount, "--signing-keys", signer.as_ref(), "--node", &rpc, ]; + if force { + tx_args.push("--force"); + } let mut client = run!(test, Bin::Client, tx_args, Some(120))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); @@ -1733,6 +1746,7 @@ fn transfer_back( BERTHA_KEY, port_id_b, channel_id_b, + true, None, None, false, @@ -1806,6 +1820,7 @@ fn transfer_timeout( ALBERT_KEY, port_id_a, channel_id_a, + false, Some(Duration::new(5, 0)), None, false, @@ -1940,6 +1955,7 @@ fn transfer( signer: impl AsRef, port_id: &PortId, channel_id: &ChannelId, + force: bool, timeout_sec: Option, expected_err: Option<&str>, wait_reveal_pk: bool, @@ -1969,6 +1985,9 @@ fn transfer( "--node", &rpc, ]; + if force { + tx_args.push("--force"); + } let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); if timeout_sec.is_some() { diff --git a/crates/tests/src/vm_host_env/ibc.rs b/crates/tests/src/vm_host_env/ibc.rs index 53a842e946..8f3377d707 100644 --- a/crates/tests/src/vm_host_env/ibc.rs +++ b/crates/tests/src/vm_host_env/ibc.rs @@ -76,7 +76,7 @@ use namada::ledger::{ibc, pos}; use namada::proof_of_stake::OwnedPosParams; use namada::state::testing::TestState; use namada::tendermint::time::Time as TmTime; -use namada::token::{self, Amount, DenominatedAmount}; +use namada::token::{self, Amount}; use namada::tx::BatchedTxRef; use namada::vm::{wasm, WasmCacheRwAccess}; use namada_core::collections::HashMap; @@ -87,7 +87,7 @@ use namada_tx_prelude::BorshSerializeExt; use crate::tx::*; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); -pub const ANY_DENOMINATION: u8 = 4; +pub const ANY_DENOMINATION: u8 = token::NATIVE_MAX_DECIMAL_PLACES; const COMMITMENT_PREFIX: &[u8] = b"ibc"; pub struct TestIbcVp<'a> { @@ -626,7 +626,6 @@ pub fn msg_transfer( denom: String, sender: &Address, ) -> MsgTransfer { - let amount = DenominatedAmount::native(Amount::native_whole(100)); let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); let message = IbcMsgTransfer { port_id_on_a: port_id, @@ -634,7 +633,7 @@ pub fn msg_transfer( packet_data: PacketData { token: PrefixedCoin { denom: denom.parse().expect("invalid denom"), - amount: amount.into(), + amount: Amount::native_whole(100).into(), }, sender: sender.to_string().into(), receiver: address::testing::gen_established_address() @@ -683,12 +682,11 @@ pub fn received_packet( token: String, receiver: &Address, ) -> Packet { - let amount = DenominatedAmount::native(Amount::native_whole(100)); let counterparty = dummy_channel_counterparty(); let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); let coin = PrefixedCoin { denom: token.parse().expect("invalid denom"), - amount: amount.into(), + amount: Amount::native_whole(100).into(), }; let sender = address::testing::gen_established_address(); let data = PacketData { diff --git a/crates/tests/src/vm_host_env/mod.rs b/crates/tests/src/vm_host_env/mod.rs index 6f7128c3c6..2f7b9d32d1 100644 --- a/crates/tests/src/vm_host_env/mod.rs +++ b/crates/tests/src/vm_host_env/mod.rs @@ -1278,7 +1278,7 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let ibc_token = ibc_storage::ibc_token(&denom); let balance_key = token::storage_key::balance_key(&ibc_token, &sender); - let init_bal = Amount::from_u64(100); + let init_bal = Amount::native_whole(100); writes.insert(balance_key.clone(), init_bal.serialize_to_vec()); let minted_key = token::storage_key::minted_balance_key(&ibc_token); writes.insert(minted_key.clone(), init_bal.serialize_to_vec()); @@ -1288,7 +1288,7 @@ mod tests { Address::Internal(InternalAddress::Ibc).serialize_to_vec(), ); let mint_amount_key = ibc_storage::mint_amount_key(&ibc_token); - let init_bal = Amount::from_u64(100); + let init_bal = Amount::native_whole(100); writes.insert(mint_amount_key, init_bal.serialize_to_vec()); writes.insert(minted_key.clone(), init_bal.serialize_to_vec()); writes.into_iter().for_each(|(key, val)| { @@ -1426,11 +1426,11 @@ mod tests { let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); let balance: Option = tx_host_env::with(|env| env.state.read(&key).expect("read error")); - assert_eq!(balance, Some(Amount::from_u64(100))); + assert_eq!(balance, Some(Amount::native_whole(100))); let minted: Option = tx_host_env::with(|env| { env.state.read(&minted_key).expect("read error") }); - assert_eq!(minted, Some(Amount::from_u64(100))); + assert_eq!(minted, Some(Amount::native_whole(100))); } #[test] @@ -1641,7 +1641,7 @@ mod tests { denom, &address::Address::Internal(address::InternalAddress::Ibc), ); - let val = Amount::from_u64(100); + let val = Amount::native_whole(100); tx_host_env::with(|env| { env.state.write(&escrow_key, val).expect("write error"); }); @@ -1700,7 +1700,7 @@ mod tests { let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); let balance: Option = tx_host_env::with(|env| env.state.read(&key).expect("read error")); - assert_eq!(balance, Some(Amount::from_u64(100))); + assert_eq!(balance, Some(Amount::native_whole(100))); let escrow: Option = tx_host_env::with(|env| { env.state.read(&escrow_key).expect("read error") }); diff --git a/crates/trans_token/src/storage_key.rs b/crates/trans_token/src/storage_key.rs index 79200d9890..4b5b132aef 100644 --- a/crates/trans_token/src/storage_key.rs +++ b/crates/trans_token/src/storage_key.rs @@ -192,44 +192,3 @@ pub fn is_any_minted_balance_key(key: &storage::Key) -> Option<&Address> { _ => None, } } - -/// The owner of a shielded action transfer, could be a proper address or the -/// minted subkey -pub enum ShieldedActionOwner<'key> { - /// A proper address - Owner(&'key Address), - /// The mint address - Minted, -} - -impl ShieldedActionOwner<'_> { - /// Convert the shielded action owner to an address - pub fn to_address_ref(&self) -> &Address { - match self { - Self::Owner(addr) => addr, - Self::Minted => &Address::Internal(InternalAddress::Ibc), - } - } - - /// Get the balance key corresponding to this shielded action owner - pub fn to_balance_key(&self, token: &Address) -> storage::Key { - match self { - ShieldedActionOwner::Owner(addr) => balance_key(token, addr), - ShieldedActionOwner::Minted => minted_balance_key(token), - } - } -} - -/// Check if the given storage key is a balance key for a shielded action. If it -/// is, returns the token and the owner addresses. -pub fn is_any_shielded_action_balance_key( - key: &storage::Key, -) -> Option<(&Address, ShieldedActionOwner<'_>)> { - is_any_token_balance_key(key).map_or_else( - || { - is_any_minted_balance_key(key) - .map(|token| (token, ShieldedActionOwner::Minted)) - }, - |[token, owner]| Some((token, ShieldedActionOwner::Owner(owner))), - ) -} diff --git a/crates/vp_env/src/lib.rs b/crates/vp_env/src/lib.rs index 7751632299..c65075c8a2 100644 --- a/crates/vp_env/src/lib.rs +++ b/crates/vp_env/src/lib.rs @@ -25,7 +25,8 @@ use namada_core::borsh::BorshDeserialize; use namada_core::hash::Hash; use namada_core::storage::{BlockHeight, Epoch, Epochs, Header, Key, TxIndex}; use namada_events::{Event, EventType}; -use namada_storage::StorageRead; +use namada_ibc::{decode_message, IbcMessage}; +use namada_storage::{OptionExt, ResultExt, StorageRead}; use namada_tx::BatchedTxRef; /// Validity predicate's environment is available for native VPs and WASM VPs @@ -119,6 +120,18 @@ where /// Get a tx hash fn get_tx_code_hash(&self) -> Result, namada_storage::Error>; + /// Get the IBC message from the data section + fn get_ibc_message( + &self, + batched_tx: &BatchedTxRef<'_>, + ) -> Result { + let data = batched_tx + .tx + .data(batched_tx.cmt) + .ok_or_err_msg("No transaction data")?; + decode_message(&data).into_storage_result() + } + /// Charge the provided gas for the current vp fn charge_gas(&self, used_gas: u64) -> Result<(), namada_storage::Error>; diff --git a/scripts/get_hermes.sh b/scripts/get_hermes.sh index 56135819d1..140f74585e 100755 --- a/scripts/get_hermes.sh +++ b/scripts/get_hermes.sh @@ -4,7 +4,7 @@ set -Eo pipefail HERMES_MAJORMINOR="1.8" HERMES_PATCH="2" -HERMES_SUFFIX="-namada-beta12-rc" +HERMES_SUFFIX="-namada-beta12-rc3" HERMES_REPO="https://github.com/heliaxdev/hermes" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 621c4e67d5..285f718936 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3804,6 +3804,7 @@ dependencies = [ "prost-types", "rand 0.8.5", "rand_core 0.6.4", + "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -4055,7 +4056,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "regex", - "ripemd", "serde", "serde_json", "sha2 0.9.9", diff --git a/wasm/checksums.json b/wasm/checksums.json index ae9470452a..7a7cf8ba13 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,29 +1,30 @@ { - "tx_become_validator.wasm": "tx_become_validator.9499cd491944c2a1eca4544d529c2ac133b504c4ac10b121d30d092b13fb486f.wasm", - "tx_bond.wasm": "tx_bond.732f85dd8ef096a1e358296287f2457f362663e0f23f68333fa209d8d97ae494.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e940f9695742c8967811115bc28f9eb4458d500a8522657174997d4c9d946198.wasm", - "tx_change_consensus_key.wasm": "tx_change_consensus_key.b5bf392325e332f19901eb9237cb32766cbee0e64c9cc47d65dd62882f716337.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.243491629bc622b6ca7a0e9179b635f1d269b58a3597121fab2e3d135749b016.wasm", - "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.0e7d7586c52c29c51256020058a93665203af00da985371a9df1702f208d9cd1.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.ae5b42e465e46c68c5f185321a34dceeffa8e56b0994ea680b6cb70a8880aed5.wasm", - "tx_deactivate_validator.wasm": "tx_deactivate_validator.fe46741d2440a2a5b9e01ece72a0938e6e1727ce97a2753951aac101fc667d88.wasm", - "tx_ibc.wasm": "tx_ibc.c630d1b1ca76a6c877d0d0fb7c91ebeed99f263cec1c9cd37ce9fce1513ba347.wasm", - "tx_init_account.wasm": "tx_init_account.afe7d7be1b9781f5fa66855b8195bc2bea897c9ee20d729d0728145716da5305.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.10bfbf2c4d87db3d1ae3c319f140db85f1ae44ea54c92aa3ece4b9bf8dbdefa9.wasm", - "tx_reactivate_validator.wasm": "tx_reactivate_validator.a71e116b6ab4ac37940dd32361e922ed43e8d0b9bb9129f4fd0e89dae8f3c902.wasm", - "tx_redelegate.wasm": "tx_redelegate.bcd3f7332de530df6407a6d6cb77046d43904609bea467065028c45f50e775ef.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.d60c11ddfef084421ed46444f1c0165125e29ac2305cc8d82b6cbc579a03803d.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.9cc8f2fec826ddf606b1f0244ce485f63a8928adf18174f55a656db6423cbf73.wasm", - "tx_shielded_transfer.wasm": "tx_shielded_transfer.f77c6fc4bcf121977e00a4ebc24b5c60fbb1a182062b51ec96df21701cee5ffc.wasm", - "tx_shielding_transfer.wasm": "tx_shielding_transfer.0bcb41dbc0bceafccc65bc5262510a40ee0c0450c0c08303b676b37de6514647.wasm", - "tx_transparent_transfer.wasm": "tx_transparent_transfer.cf989d4d445340e7c98e446f414a4c7c9d353638a34b40198b2b63a05fbbf69a.wasm", - "tx_unbond.wasm": "tx_unbond.16130834f9cbb300e50ce5405b3531618c733156430fa6788a086fb303eb61c9.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.088c40fa51dc5cbab896e5464daa71bb205e999ba2aa24e8775a63c2ebf10084.wasm", - "tx_unshielding_transfer.wasm": "tx_unshielding_transfer.7a7e3e048ff135642a6694437cc6e0a4c7c6943e9c149a4f81363f063fe28dbf.wasm", - "tx_update_account.wasm": "tx_update_account.bcd9bc9f531015719cfe1009af1c5bcfd6567cea55f4cec053bd45c96a2143df.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.ee6739d438764e8aaaa91cace5ca7fa70b06a4cd4d021f3764fa28d94bd0d202.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.45cda2bb54d6101206297be8e97a437806a24fb47fe7f91b61f05dbe8314bb53.wasm", - "tx_withdraw.wasm": "tx_withdraw.bca31434c620eee98fc1394a9dfb071033e87830152f0585ec82cc9c68ad310a.wasm", - "vp_implicit.wasm": "vp_implicit.1d6894f6b285a65561cf5ff0599deb5b5e7ac6227a074bf571cba28f63b4813f.wasm", - "vp_user.wasm": "vp_user.3e7c5f520227e3b49b8a66218ef9ce18976400d70a6b36e0878638ed9912e147.wasm" + "tx_become_validator.wasm": "tx_become_validator.e7df6f1c325ee806fee95c30a11246b3e2330c6002783074bd04b6a5998f245d.wasm", + "tx_bond.wasm": "tx_bond.8a6d2ae51d45d1158698771c30a17082940fce2b9a78f6a7f923b5f7e868673b.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.15accaf56e6fbd893148f79d4221bb0fdca4a87d9b0c19cdc68ede9851deb579.wasm", + "tx_change_consensus_key.wasm": "tx_change_consensus_key.28bea8969a729109dceae5297aa1bbd10f2737db01a0cb99689e325eeac4c616.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.9d3eac2909df762cad56f5637071b1176d997b346bb5b9241cb2bd848f0435e8.wasm", + "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.d5a7c0758911deb50dd4a0c0ee30cd2f012c4f2298f265b81c8a5a3099ca0cd1.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.7ce4a02d2975d0fce0184928514c9c367dea215fc85c5a57ee97eaf210e1e96e.wasm", + "tx_deactivate_validator.wasm": "tx_deactivate_validator.8f6f5656532d1d48e458d7bddf1f426e8560a86c2e91fc44847bb95f181269b2.wasm", + "tx_ibc.wasm": "tx_ibc.6b21da1fdb95f1c98cb85b2c3345476c14ff487010cc3f3bcc3cdf30c0dffc93.wasm", + "tx_init_account.wasm": "tx_init_account.60291c66452b637dbe886ee6389ac730a13b36d6839e7328be8d7addd7ef562d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.53564391242ecdab3a0b205b6bae743bc1bccbb300a68bd1e12eab0c98c9aebd.wasm", + "tx_reactivate_validator.wasm": "tx_reactivate_validator.c5dbeb071983ea2b7b87179771e65b845efa2552239437407716c21e328a470f.wasm", + "tx_redelegate.wasm": "tx_redelegate.6f65111cb6c8277a0709c204f3cd85f84cea8c07869efe4c7c821fc4341871c1.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.8791257f6474e6dd759161c202cd25943ae1f464dbaf6533fa4e7f7b1ebfd99f.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.a86d975548a13167f095223764d690aceafe9faad797bc2004d9bd5185ca4307.wasm", + "tx_shielded_transfer.wasm": "tx_shielded_transfer.2c2dccc5c5925580bf955fdbff0e8724e8381645ab76584d52fd8515fa3d8805.wasm", + "tx_shielding_transfer.wasm": "tx_shielding_transfer.6ad3817afe35fc5d1cc0f124a4bc6f02a8ec0802b97d606555c839d45a4bf1b4.wasm", + "tx_transfer.wasm": "tx_transfer.8acfe426d01adc17018c07b1c9f2eb3f77ddff4a6e452c8d5ae16832a0e79c85.wasm", + "tx_transparent_transfer.wasm": "tx_transparent_transfer.2f18f3a8895625fcd69be2aa587e9fc82eb5fd3cb7df305f70e915eb040899bd.wasm", + "tx_unbond.wasm": "tx_unbond.4f88701b831ca58847ee9d03f15a92a641781a4f3142f61648a840a43c9de59c.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.4ebc6c6dedd6587b033eaebd69725cce91f02ac47b695479d3a96eadd6b1f831.wasm", + "tx_unshielding_transfer.wasm": "tx_unshielding_transfer.e238a486605d9c38ca483f34efd8f8cf62156662adba3e9f82f63424eb749413.wasm", + "tx_update_account.wasm": "tx_update_account.26daf5e95aae1417decc5553b555e4eea80d0f66148718c23d8afc0a80b94649.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.e39ba50d5329a190812bcda5e511f357fa87e9cc53ab12598e09ed777c5e9222.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.abd17ec3f379f6255819c6a1f375e135f8e5c91a7cfa04cf655953f2e7a69553.wasm", + "tx_withdraw.wasm": "tx_withdraw.f58f27408ce18ce9cf4e1fe0f8a0acd16533bb431aa64913776acc36bbd91d2f.wasm", + "vp_implicit.wasm": "vp_implicit.6432b1d7ae9dcd3c17b7643f0ac4f799e6366a5fb08d483e369ba0994568596d.wasm", + "vp_user.wasm": "vp_user.6b9e7d5eb43e69281728376855790e0a893a9c6d39d4451f286f0d1f83a662f5.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 40250f7dd3..33979207fd 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3848,6 +3848,7 @@ dependencies = [ "prost-types", "rand 0.8.5", "rand_core 0.6.4", + "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -4078,7 +4079,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "regex", - "ripemd", "serde", "serde_json", "sha2 0.9.9", diff --git a/wasm_for_tests/tx_fail.wasm b/wasm_for_tests/tx_fail.wasm index 27e76977c8..171d9afa74 100755 Binary files a/wasm_for_tests/tx_fail.wasm and b/wasm_for_tests/tx_fail.wasm differ diff --git a/wasm_for_tests/tx_infinite_guest_gas.wasm b/wasm_for_tests/tx_infinite_guest_gas.wasm index 45b7618371..f5108f0c98 100755 Binary files a/wasm_for_tests/tx_infinite_guest_gas.wasm and b/wasm_for_tests/tx_infinite_guest_gas.wasm differ diff --git a/wasm_for_tests/tx_infinite_host_gas.wasm b/wasm_for_tests/tx_infinite_host_gas.wasm index df6e1f8c36..d83bcbfb5f 100755 Binary files a/wasm_for_tests/tx_infinite_host_gas.wasm and b/wasm_for_tests/tx_infinite_host_gas.wasm differ diff --git a/wasm_for_tests/tx_invalid_data.wasm b/wasm_for_tests/tx_invalid_data.wasm index 9438d3298d..f8a9328f66 100755 Binary files a/wasm_for_tests/tx_invalid_data.wasm and b/wasm_for_tests/tx_invalid_data.wasm differ diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 9c56cca89c..d6c0ddc94a 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 29e398474f..f47e8cb5a9 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index f0ffdc019c..c439479c7b 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index e8c56b47e3..47bae4bf23 100755 Binary files a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm and b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm differ diff --git a/wasm_for_tests/tx_proposal_masp_reward.wasm b/wasm_for_tests/tx_proposal_masp_reward.wasm index 86ccf908cd..22bf5ab0c8 100755 Binary files a/wasm_for_tests/tx_proposal_masp_reward.wasm and b/wasm_for_tests/tx_proposal_masp_reward.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index e462fc5fa0..af1e530730 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 9477574fd0..1101a4115a 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 5c4ebfa192..ac984b5fa3 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8b3c64a293..a593d81ed4 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 7dec0ba71c..2e98cc9263 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_infinite_guest_gas.wasm b/wasm_for_tests/vp_infinite_guest_gas.wasm index c88d35a500..150f46dd75 100755 Binary files a/wasm_for_tests/vp_infinite_guest_gas.wasm and b/wasm_for_tests/vp_infinite_guest_gas.wasm differ diff --git a/wasm_for_tests/vp_infinite_host_gas.wasm b/wasm_for_tests/vp_infinite_host_gas.wasm index eb71bb2fe5..bc4c215761 100755 Binary files a/wasm_for_tests/vp_infinite_host_gas.wasm and b/wasm_for_tests/vp_infinite_host_gas.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index ac3a1c0c98..90df5a10ca 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index acf222f82a..288a937905 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ