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/.changelog/unreleased/improvements/3422-refine-ibc-masp-replay-protection.md b/.changelog/unreleased/improvements/3422-refine-ibc-masp-replay-protection.md new file mode 100644 index 0000000000..776d5a1fe7 --- /dev/null +++ b/.changelog/unreleased/improvements/3422-refine-ibc-masp-replay-protection.md @@ -0,0 +1,2 @@ +- Refine IBC parts for masp ibc replay protection + ([\#3422](https://github.com/anoma/namada/issues/3422)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c5f83204df..9c2d7a8224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,9 +971,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", @@ -982,18 +982,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]] @@ -1034,7 +1034,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -1049,18 +1048,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_derive" -version = "4.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "clap_lex" version = "0.6.0" @@ -3012,16 +2999,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" @@ -4066,13 +4043,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]] @@ -4894,6 +4871,7 @@ dependencies = [ "prost-types 0.12.3", "rand 0.8.5", "rand_core 0.6.4", + "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -5054,6 +5032,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", + "smooth-operator", "thiserror", "tracing", ] @@ -5278,7 +5257,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "regex", - "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -6099,9 +6077,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", @@ -6110,9 +6088,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", @@ -6120,9 +6098,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", @@ -6133,9 +6111,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", @@ -6218,9 +6196,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", @@ -6231,15 +6209,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", ] @@ -7322,9 +7300,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", ] @@ -7373,15 +7351,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", @@ -9098,9 +9076,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", @@ -9127,9 +9105,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", @@ -9139,9 +9117,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", @@ -9167,9 +9145,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", @@ -9186,9 +9164,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", @@ -9227,9 +9205,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", @@ -9239,9 +9217,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", @@ -9260,9 +9238,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", @@ -9354,15 +9332,14 @@ dependencies = [ [[package]] name = "webc" -version = "6.0.0-alpha9" +version = "6.0.0-rc1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b4e8dd987046eede4348d660404ff990412631b7d493f9e547adcf2862cd5" +checksum = "c1fc686c7b43c9bc630a499f6ae1f0a4c4bd656576a53ae8a147b0cc9bc983ad" dependencies = [ "anyhow", "base64 0.21.7", "bytes", "cfg-if", - "clap", "document-features", "flate2", "indexmap 1.9.3", @@ -9763,9 +9740,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/apps_lib/src/cli/context.rs b/crates/apps_lib/src/cli/context.rs index db94b7ad9a..58b5862960 100644 --- a/crates/apps_lib/src/cli/context.rs +++ b/crates/apps_lib/src/cli/context.rs @@ -11,9 +11,8 @@ use namada::core::chain::ChainId; use namada::core::ethereum_events::EthAddress; use namada::core::key::*; use namada::core::masp::*; -use namada::ibc::{is_ibc_denom, is_nft_trace}; +use namada::ibc::trace::{ibc_token, is_ibc_denom, is_nft_trace}; use namada::io::Io; -use namada::ledger::ibc::storage::ibc_token; use namada_sdk::masp::fs::FsShieldedUtils; use namada_sdk::masp::ShieldedContext; use namada_sdk::wallet::Wallet; diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 298c37fcf5..2ee9986fb1 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/Cargo.toml b/crates/ibc/Cargo.toml index 6822e0b40f..9d15fa70ce 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -45,6 +45,7 @@ prost.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true +smooth-operator.workspace = true thiserror.workspace = true tracing.workspace = true 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 bc0e1d92b7..912206e558 100644 --- a/crates/ibc/src/context/common.rs +++ b/crates/ibc/src/context/common.rs @@ -11,7 +11,6 @@ use ibc::core::channel::types::commitment::{ }; use ibc::core::channel::types::error::{ChannelError, PacketError}; use ibc::core::channel::types::packet::Receipt; -use ibc::core::channel::types::timeout::TimeoutHeight; use ibc::core::client::types::error::ClientError; use ibc::core::client::types::Height; use ibc::core::connection::types::error::ConnectionError; @@ -27,14 +26,14 @@ use namada_core::storage::{BlockHeight, Key}; use namada_core::tendermint::Time as TmTime; use namada_core::time::DurationSecs; use namada_parameters::storage::get_max_expected_time_per_block_key; +use namada_storage::{Error as StorageError, StorageRead}; use namada_token::storage_key::balance_key; use namada_token::Amount; use prost::Message; -use sha2::Digest; use super::client::{AnyClientState, AnyConsensusState}; use super::storage::IbcStorageContext; -use crate::{storage, NftClass, NftMetadata}; +use crate::{storage, trace, NftClass, NftMetadata}; /// Result of IBC common function call pub type Result = std::result::Result; @@ -421,7 +420,7 @@ pub trait IbcCommonContext: IbcStorageContext { channel_id: &ChannelId, ) -> Result { let key = storage::next_sequence_send_key(port_id, channel_id); - self.read_sequence(&key) + read_sequence(self, &key).map_err(ContextError::from) } /// Store the NextSequenceSend @@ -442,7 +441,7 @@ pub trait IbcCommonContext: IbcStorageContext { channel_id: &ChannelId, ) -> Result { let key = storage::next_sequence_recv_key(port_id, channel_id); - self.read_sequence(&key) + read_sequence(self, &key).map_err(ContextError::from) } /// Store the NextSequenceRecv @@ -463,7 +462,7 @@ pub trait IbcCommonContext: IbcStorageContext { channel_id: &ChannelId, ) -> Result { let key = storage::next_sequence_ack_key(port_id, channel_id); - self.read_sequence(&key) + read_sequence(self, &key).map_err(ContextError::from) } /// Store the NextSequenceAck @@ -477,58 +476,12 @@ pub trait IbcCommonContext: IbcStorageContext { self.store_sequence(&key, seq) } - /// Read a sequence - fn read_sequence(&self, key: &Key) -> Result { - match self.read_bytes(key)? { - Some(value) => { - let value: [u8; 8] = - value.try_into().map_err(|_| ChannelError::Other { - description: format!( - "The sequence value wasn't u64: Key {key}", - ), - })?; - Ok(u64::from_be_bytes(value).into()) - } - // when the sequence has never been used, returns the initial value - None => Ok(1.into()), - } - } - /// Store the sequence fn store_sequence(&mut self, key: &Key, sequence: Sequence) -> Result<()> { let bytes = u64::from(sequence).to_be_bytes().to_vec(); self.write_bytes(key, bytes).map_err(ContextError::from) } - /// Calculate the hash - fn hash(value: &[u8]) -> Vec { - sha2::Sha256::digest(value).to_vec() - } - - /// Calculate the packet commitment - fn compute_packet_commitment( - &self, - packet_data: &[u8], - timeout_height: &TimeoutHeight, - timeout_timestamp: &Timestamp, - ) -> PacketCommitment { - let mut hash_input = - timeout_timestamp.nanoseconds().to_be_bytes().to_vec(); - - let revision_number = - timeout_height.commitment_revision_number().to_be_bytes(); - hash_input.append(&mut revision_number.to_vec()); - - let revision_height = - timeout_height.commitment_revision_height().to_be_bytes(); - hash_input.append(&mut revision_height.to_vec()); - - let packet_data_hash = Self::hash(packet_data); - hash_input.append(&mut packet_data_hash.to_vec()); - - Self::hash(&hash_input).into() - } - /// Get the packet commitment fn packet_commitment( &self, @@ -717,7 +670,7 @@ pub trait IbcCommonContext: IbcStorageContext { token_id: &TokenId, owner: &Address, ) -> Result { - let ibc_token = storage::ibc_token_for_nft(class_id, token_id); + let ibc_token = trace::ibc_token_for_nft(class_id, token_id); let balance_key = balance_key(&ibc_token, owner); let amount = self.read::(&balance_key)?; Ok(amount == Some(Amount::from_u64(1))) @@ -767,3 +720,22 @@ pub trait IbcCommonContext: IbcStorageContext { self.write(&key, amount).map_err(ContextError::from) } } + +/// Read and decode the IBC sequence +pub fn read_sequence( + storage: &S, + key: &Key, +) -> std::result::Result { + match storage.read_bytes(key)? { + Some(value) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + StorageError::new_alloc(format!( + "The sequence value wasn't u64: Key {key}", + )) + })?; + Ok(u64::from_be_bytes(value).into()) + } + // when the sequence has never been used, returns the initial value + None => Ok(1.into()), + } +} diff --git a/crates/ibc/src/context/nft_transfer.rs b/crates/ibc/src/context/nft_transfer.rs index 85d9aad44e..e7317ba94e 100644 --- a/crates/ibc/src/context/nft_transfer.rs +++ b/crates/ibc/src/context/nft_transfer.rs @@ -17,7 +17,7 @@ use namada_core::address::Address; use namada_core::token::Amount; use super::common::IbcCommonContext; -use crate::{storage, NftClass, NftMetadata, IBC_ESCROW_ADDRESS}; +use crate::{trace, NftClass, NftMetadata, IBC_ESCROW_ADDRESS}; /// NFT transfer context to handle tokens #[derive(Debug)] @@ -95,8 +95,8 @@ where class_id: &PrefixedClassId, token_id: &TokenId, ) -> Result<(), NftTransferError> { - let ibc_trace = format!("{class_id}/{token_id}"); - let trace_hash = storage::calc_hash(&ibc_trace); + let ibc_trace = trace::ibc_trace_for_nft(class_id, token_id); + let trace_hash = trace::calc_hash(&ibc_trace); self.inner .borrow_mut() @@ -241,7 +241,7 @@ where class_id: &PrefixedClassId, token_id: &TokenId, ) -> Option { - Some(storage::calc_hash(format!("{class_id}/{token_id}"))) + Some(trace::calc_hash(format!("{class_id}/{token_id}"))) } /// Returns the NFT @@ -300,7 +300,7 @@ where token_id: &TokenId, _memo: &Memo, ) -> Result<(), NftTransferError> { - let ibc_token = storage::ibc_token_for_nft(class_id, token_id); + let ibc_token = trace::ibc_token_for_nft(class_id, token_id); self.add_withdraw(&ibc_token)?; @@ -324,7 +324,7 @@ where class_id: &PrefixedClassId, token_id: &TokenId, ) -> Result<(), NftTransferError> { - let ibc_token = storage::ibc_token_for_nft(class_id, token_id); + let ibc_token = trace::ibc_token_for_nft(class_id, token_id); self.add_deposit(&ibc_token)?; @@ -347,7 +347,7 @@ where token_uri: Option<&TokenUri>, token_data: Option<&TokenData>, ) -> Result<(), NftTransferError> { - let ibc_token = storage::ibc_token_for_nft(class_id, token_id); + let ibc_token = trace::ibc_token_for_nft(class_id, token_id); // create or update the metadata let metadata = NftMetadata { @@ -378,7 +378,7 @@ where token_id: &TokenId, _memo: &Memo, ) -> Result<(), NftTransferError> { - let ibc_token = storage::ibc_token_for_nft(class_id, token_id); + let ibc_token = trace::ibc_token_for_nft(class_id, token_id); self.update_mint_amount(&ibc_token, false)?; self.add_withdraw(&ibc_token)?; diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index 4d9b5e7b80..7d0ebee13e 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -14,10 +14,10 @@ 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}; +use crate::{trace, IBC_ESCROW_ADDRESS}; /// Token transfer context to handle tokens #[derive(Debug)] @@ -54,15 +54,12 @@ where ) -> Result<(Address, Amount), TokenTransferError> { let token = match Address::decode(coin.denom.base_denom.as_str()) { Ok(token_addr) if coin.denom.trace_path.is_empty() => token_addr, - _ => storage::ibc_token(coin.denom.to_string()), + _ => trace::ibc_token(coin.denom.to_string()), }; // 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!( @@ -149,7 +146,7 @@ where return Ok(()); } let ibc_denom = coin.denom.to_string(); - let trace_hash = storage::calc_hash(&ibc_denom); + let trace_hash = trace::calc_hash(&ibc_denom); self.inner .borrow_mut() @@ -227,7 +224,7 @@ where } fn denom_hash_string(&self, denom: &PrefixedDenom) -> Option { - Some(storage::calc_hash(denom.to_string())) + Some(trace::calc_hash(denom.to_string())) } } diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 15a673c9c6..05b095de70 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -24,12 +24,12 @@ mod msg; mod nft; pub mod parameters; pub mod storage; +pub mod trace; use std::cell::RefCell; use std::collections::BTreeSet; use std::fmt::Debug; use std::rc::Rc; -use std::str::FromStr; pub use actions::transfer_over_ibc; use borsh::BorshDeserialize; @@ -48,16 +48,13 @@ use ibc::apps::nft_transfer::handler::{ use ibc::apps::nft_transfer::types::error::NftTransferError; use ibc::apps::nft_transfer::types::{ ack_success_b64, is_receiver_chain_source as is_nft_receiver_chain_source, - PrefixedClassId, TokenId, TracePath as NftTracePath, - TracePrefix as NftTracePrefix, + PrefixedClassId, TokenId, TracePrefix as NftTracePrefix, }; use ibc::apps::transfer::handler::{ send_transfer_execute, send_transfer_validate, }; use ibc::apps::transfer::types::error::TokenTransferError; -use ibc::apps::transfer::types::{ - is_receiver_chain_source, PrefixedDenom, TracePath, TracePrefix, -}; +use ibc::apps::transfer::types::{is_receiver_chain_source, TracePrefix}; use ibc::core::channel::types::acknowledgement::{ Acknowledgement, AcknowledgementStatus, }; @@ -70,12 +67,14 @@ use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::events::Error as RawIbcEventError; use ibc::core::handler::types::msgs::MsgEnvelope; use ibc::core::host::types::error::IdentifierError; -use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use ibc::core::host::types::identifiers::{ChannelId, PortId, Sequence}; use ibc::core::router::types::error::RouterError; use ibc::primitives::proto::Any; pub use ibc::*; pub use msg::*; use namada_core::address::{self, Address}; +use namada_core::arith::checked; +use namada_storage::{Error as StorageError, StorageRead}; use namada_token::ShieldingTransfer; pub use nft::*; use prost::Message; @@ -225,7 +224,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 { @@ -358,6 +357,25 @@ pub fn decode_message(tx_data: &[u8]) -> Result { Err(Error::DecodingData) } +/// Return the last sequence send +pub fn get_last_sequence_send( + storage: &S, + port_id: &PortId, + channel_id: &ChannelId, +) -> Result { + let seq_key = storage::next_sequence_send_key(port_id, channel_id); + let next_seq: u64 = + context::common::read_sequence(storage, &seq_key)?.into(); + if next_seq <= 1 { + // No transfer heppened + return Err(StorageError::new_alloc(format!( + "No IBC transfer happened: Port ID {port_id}, Channel ID \ + {channel_id}", + ))); + } + Ok(checked!(next_seq - 1)?.into()) +} + fn received_ibc_trace( base_trace: impl AsRef, src_port_id: &PortId, @@ -385,7 +403,7 @@ fn received_ibc_trace( } if let Some((trace_path, base_class_id, token_id)) = - is_nft_trace(&base_trace) + trace::is_nft_trace(&base_trace) { let mut class_id = PrefixedClassId { trace_path, @@ -433,43 +451,8 @@ pub fn received_ibc_token( dest_port_id, dest_channel_id, )?; - if ibc_trace.contains('/') { - Ok(storage::ibc_token(ibc_trace)) - } else { - Address::decode(ibc_trace) - .map_err(|e| Error::Trace(format!("Invalid base token: {e}"))) - } -} - -/// Returns the trace path and the token string if the denom is an IBC -/// denom. -pub fn is_ibc_denom(denom: impl AsRef) -> Option<(TracePath, String)> { - let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; - let base_denom = prefixed_denom.base_denom.to_string(); - if prefixed_denom.trace_path.is_empty() || base_denom.contains('/') { - // The denom is just a token or an NFT trace - return None; - } - // The base token isn't decoded because it could be non Namada token - Some((prefixed_denom.trace_path, base_denom)) -} - -/// Returns the trace path and the token string if the trace is an NFT one -pub fn is_nft_trace( - trace: impl AsRef, -) -> Option<(NftTracePath, String, String)> { - // The trace should be {port}/{channel}/.../{class_id}/{token_id} - if let Some((class_id, token_id)) = trace.as_ref().rsplit_once('/') { - let prefixed_class_id = PrefixedClassId::from_str(class_id).ok()?; - // The base token isn't decoded because it could be non Namada token - Some(( - prefixed_class_id.trace_path, - prefixed_class_id.base_class_id.to_string(), - token_id.to_string(), - )) - } else { - None - } + trace::convert_to_address(ibc_trace) + .map_err(|e| Error::Trace(format!("Invalid base token: {e}"))) } #[cfg(any(test, feature = "testing"))] 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..44b6da1ff0 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -12,19 +12,18 @@ use ibc::core::host::types::path::{ ClientStatePath, CommitmentPath, ConnectionPath, Path, PortPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; -use namada_core::address::{Address, InternalAddress, HASH_LEN, SHA_HASH_LEN}; -use namada_core::ibc::IbcTokenHash; +use namada_core::address::{Address, InternalAddress}; use namada_core::storage::{DbKeySeg, Key, KeySeg}; use namada_core::token::Amount; use namada_events::{EmitEvents, EventLevel}; use namada_state::{StorageRead, StorageResult, StorageWrite}; use namada_token as token; use namada_token::event::{TokenEvent, TokenOperation, UserAccount}; -use sha2::{Digest, Sha256}; use thiserror::Error; use crate::event::TOKEN_EVENT_DESCRIPTOR; use crate::parameters::IbcParameters; +use crate::trace::{ibc_token, ibc_token_for_nft}; const CLIENTS_COUNTER_PREFIX: &str = "clients"; const CONNECTIONS_COUNTER_PREFIX: &str = "connections"; @@ -47,8 +46,8 @@ pub enum Error { StorageKey(namada_core::storage::Error), #[error("Invalid Key: {0}")] InvalidKey(String), - #[error("Port capability error: {0}")] - InvalidPortCapability(String), + #[error("Invalid IBC trace: {0}")] + InvalidIbcTrace(String), } /// IBC storage functions result @@ -480,47 +479,24 @@ pub fn ibc_trace_key( .expect("Cannot obtain a storage key") } -/// Hash the denom -#[inline] -pub fn calc_hash(denom: impl AsRef) -> String { - calc_ibc_token_hash(denom).to_string() -} - -/// Hash the denom -pub fn calc_ibc_token_hash(denom: impl AsRef) -> IbcTokenHash { - let hash = { - let mut hasher = Sha256::new(); - hasher.update(denom.as_ref()); - hasher.finalize() - }; - - let input: &[u8; SHA_HASH_LEN] = hash.as_ref(); - let mut output = [0; HASH_LEN]; - - output.copy_from_slice(&input[..HASH_LEN]); - IbcTokenHash(output) -} - -/// Obtain the IbcToken with the hash from the given denom -pub fn ibc_token(denom: impl AsRef) -> Address { - let hash = calc_ibc_token_hash(&denom); - Address::Internal(InternalAddress::IbcToken(hash)) -} - -/// Obtain the IbcToken with the hash from the given NFT class ID and NFT ID -pub fn ibc_token_for_nft( - class_id: &PrefixedClassId, - token_id: &TokenId, -) -> Address { - ibc_token(format!("{class_id}/{token_id}")) -} - /// Returns true if the given key is for IBC pub fn is_ibc_key(key: &Key) -> bool { matches!(&key.segments[0], 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/ibc/src/trace.rs b/crates/ibc/src/trace.rs new file mode 100644 index 0000000000..6bb9d3bea8 --- /dev/null +++ b/crates/ibc/src/trace.rs @@ -0,0 +1,120 @@ +//! Functions for IBC token + +use std::str::FromStr; + +use ibc::apps::nft_transfer::types::{ + PrefixedClassId, TokenId, TracePath as NftTracePath, +}; +use ibc::apps::transfer::types::{PrefixedDenom, TracePath}; +use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use namada_core::address::{Address, InternalAddress, HASH_LEN, SHA_HASH_LEN}; +use namada_core::ibc::IbcTokenHash; +use sha2::{Digest, Sha256}; + +use crate::storage::Error; + +/// Hash the denom +#[inline] +pub fn calc_hash(trace: impl AsRef) -> String { + calc_ibc_token_hash(trace).to_string() +} + +/// Hash the denom +pub fn calc_ibc_token_hash(trace: impl AsRef) -> IbcTokenHash { + let hash = { + let mut hasher = Sha256::new(); + hasher.update(trace.as_ref()); + hasher.finalize() + }; + + let input: &[u8; SHA_HASH_LEN] = hash.as_ref(); + let mut output = [0; HASH_LEN]; + + output.copy_from_slice(&input[..HASH_LEN]); + IbcTokenHash(output) +} + +/// Obtain the IbcToken with the hash from the given denom +pub fn ibc_token(trace: impl AsRef) -> Address { + let hash = calc_ibc_token_hash(&trace); + Address::Internal(InternalAddress::IbcToken(hash)) +} + +/// Obtain the IbcToken with the hash from the given NFT class ID and NFT ID +pub fn ibc_token_for_nft( + class_id: &PrefixedClassId, + token_id: &TokenId, +) -> Address { + ibc_token(ibc_trace_for_nft(class_id, token_id)) +} + +/// Obtain the IBC trace from the given NFT class ID and NFT ID +pub fn ibc_trace_for_nft( + class_id: &PrefixedClassId, + token_id: &TokenId, +) -> String { + format!("{class_id}/{token_id}") +} + +/// Convert the given IBC trace to [`Address`] +pub fn convert_to_address( + ibc_trace: impl AsRef, +) -> Result { + if ibc_trace.as_ref().contains('/') { + // validation + if is_ibc_denom(&ibc_trace).is_none() + && is_nft_trace(&ibc_trace).is_none() + { + return Err(Error::InvalidIbcTrace(format!( + "This is not IBC denom and NFT trace: {}", + ibc_trace.as_ref() + ))); + } + Ok(ibc_token(ibc_trace.as_ref())) + } else { + Address::decode(ibc_trace.as_ref()) + .map_err(|e| Error::InvalidIbcTrace(e.to_string())) + } +} + +/// Returns the trace path and the token string if the denom is an IBC +/// denom. +pub fn is_ibc_denom(denom: impl AsRef) -> Option<(TracePath, String)> { + let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; + let base_denom = prefixed_denom.base_denom.to_string(); + if prefixed_denom.trace_path.is_empty() || base_denom.contains('/') { + // The denom is just a token or an NFT trace + return None; + } + // The base token isn't decoded because it could be non Namada token + Some((prefixed_denom.trace_path, base_denom)) +} + +/// Returns the trace path and the token string if the trace is an NFT one +pub fn is_nft_trace( + trace: impl AsRef, +) -> Option<(NftTracePath, String, String)> { + // The trace should be {port}/{channel}/.../{class_id}/{token_id} + if let Some((class_id, token_id)) = trace.as_ref().rsplit_once('/') { + let prefixed_class_id = PrefixedClassId::from_str(class_id).ok()?; + // The base token isn't decoded because it could be non Namada token + Some(( + prefixed_class_id.trace_path, + prefixed_class_id.base_class_id.to_string(), + token_id.to_string(), + )) + } else { + None + } +} + +/// Return true if the source of the given IBC trace is this chain +pub fn is_sender_chain_source( + trace: impl AsRef, + src_port_id: &PortId, + src_channel_id: &ChannelId, +) -> bool { + !trace + .as_ref() + .starts_with(&format!("{src_port_id}/{src_channel_id}")) +} diff --git a/crates/namada/src/ledger/ibc/mod.rs b/crates/namada/src/ledger/ibc/mod.rs index 4076a1de92..be7f4f5144 100644 --- a/crates/namada/src/ledger/ibc/mod.rs +++ b/crates/namada/src/ledger/ibc/mod.rs @@ -6,7 +6,7 @@ use namada_ibc::storage::{ channel_counter_key, client_counter_key, connection_counter_key, deposit_prefix, withdraw_prefix, }; -pub use namada_ibc::{parameters, storage}; +pub use namada_ibc::{parameters, storage, trace}; use namada_state::{ DBIter, Key, State, StorageError, StorageHasher, StorageRead, StorageWrite, WlState, DB, diff --git a/crates/namada/src/ledger/native_vp/ibc/mod.rs b/crates/namada/src/ledger/native_vp/ibc/mod.rs index 3f5bc24cc0..14de956f35 100644 --- a/crates/namada/src/ledger/native_vp/ibc/mod.rs +++ b/crates/namada/src/ledger/native_vp/ibc/mod.rs @@ -28,9 +28,10 @@ use thiserror::Error; use crate::ibc::core::host::types::identifiers::ChainId as IbcChainId; use crate::ledger::ibc::storage::{ - calc_hash, deposit_key, get_limits, is_ibc_key, is_ibc_trace_key, - mint_amount_key, withdraw_key, + deposit_key, get_limits, is_ibc_key, is_ibc_trace_key, mint_amount_key, + withdraw_key, }; +use crate::ledger::ibc::trace::calc_hash; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::parameters::read_epoch_duration_parameter; use crate::token::storage_key::is_any_token_balance_key; @@ -426,7 +427,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; @@ -507,14 +507,14 @@ mod tests { use crate::ibc::primitives::proto::{Any, Protobuf}; use crate::ibc::primitives::{Timestamp, ToProto}; use crate::ibc::storage::{ - ack_key, calc_hash, channel_counter_key, channel_key, - client_connections_key, client_counter_key, client_state_key, - client_update_height_key, client_update_timestamp_key, commitment_key, - connection_counter_key, connection_key, consensus_state_key, ibc_token, - ibc_trace_key, mint_amount_key, next_sequence_ack_key, - next_sequence_recv_key, next_sequence_send_key, nft_class_key, - nft_metadata_key, receipt_key, + ack_key, channel_counter_key, channel_key, client_connections_key, + client_counter_key, client_state_key, client_update_height_key, + client_update_timestamp_key, commitment_key, connection_counter_key, + connection_key, consensus_state_key, ibc_trace_key, mint_amount_key, + next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, + nft_class_key, nft_metadata_key, receipt_key, }; + use crate::ibc::trace::{calc_hash, ibc_token}; use crate::ibc::{MsgNftTransfer, MsgTransfer, NftClass, NftMetadata}; use crate::key::testing::keypair_1; use crate::ledger::gas::VpGasMeter; @@ -2203,7 +2203,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(), @@ -2235,12 +2235,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) @@ -2718,7 +2713,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(), @@ -2773,12 +2768,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) @@ -2881,7 +2871,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(), @@ -2936,12 +2926,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) @@ -3032,7 +3017,7 @@ mod tests { let class_id = get_nft_class_id(); let token_id = get_nft_id(); let sender = established_address_1(); - let ibc_token = ibc::storage::ibc_token_for_nft(&class_id, &token_id); + let ibc_token = ibc::trace::ibc_token_for_nft(&class_id, &token_id); let balance_key = balance_key(&ibc_token, &sender); let amount = Amount::from_u64(1); state diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 51e86f018b..a02902b80c 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -3,7 +3,6 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; -use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::CommitmentTree; use masp_primitives::sapling::Node; @@ -13,32 +12,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::nft_transfer::types::msgs::transfer::MsgTransfer as IbcMsgNftTransfer; +use namada_core::ibc::apps::nft_transfer::types::packet::PacketData as NftPacketData; +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::commitment::{ + compute_packet_commitment, PacketCommitment, +}; +use namada_ibc::core::channel::types::msgs::MsgRecvPacket as IbcMsgRecvPacket; +use namada_ibc::core::channel::types::timeout::TimeoutHeight; +use namada_ibc::core::host::types::identifiers::{ChannelId, PortId, Sequence}; +use namada_ibc::primitives::Timestamp; +use namada_ibc::trace::{ + convert_to_address, ibc_trace_for_nft, is_sender_chain_source, +}; +use namada_ibc::{get_last_sequence_send, 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::{IBC, MASP}; +use crate::ledger::ibc::storage; +use crate::ledger::ibc::storage::{commitment_key, receipt_key}; use crate::ledger::native_vp; 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, +}; use crate::token; use crate::token::MaspDigitPos; use crate::uint::I320; @@ -69,14 +87,86 @@ 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>, } +/// IBC transfer info +struct IbcTransferInfo { + src_port_id: PortId, + src_channel_id: ChannelId, + timeout_height: TimeoutHeight, + timeout_timestamp: Timestamp, + packet_data: Vec, + ibc_traces: Vec, + amount: Amount, + receiver: String, +} + +impl TryFrom for IbcTransferInfo { + type Error = Error; + + fn try_from( + message: IbcMsgTransfer, + ) -> std::result::Result { + let packet_data = serde_json::to_vec(&message.packet_data) + .map_err(native_vp::Error::new)?; + let ibc_traces = vec![message.packet_data.token.denom.to_string()]; + let amount = message + .packet_data + .token + .amount + .try_into() + .into_storage_result()?; + let receiver = message.packet_data.receiver.to_string(); + Ok(Self { + src_port_id: message.port_id_on_a, + src_channel_id: message.chan_id_on_a, + timeout_height: message.timeout_height_on_b, + timeout_timestamp: message.timeout_timestamp_on_b, + packet_data, + ibc_traces, + amount, + receiver, + }) + } +} + +impl TryFrom for IbcTransferInfo { + type Error = Error; + + fn try_from( + message: IbcMsgNftTransfer, + ) -> std::result::Result { + let packet_data = serde_json::to_vec(&message.packet_data) + .map_err(native_vp::Error::new)?; + let ibc_traces = message + .packet_data + .token_ids + .0 + .iter() + .map(|token_id| { + ibc_trace_for_nft(&message.packet_data.class_id, token_id) + }) + .collect(); + let receiver = message.packet_data.receiver.to_string(); + Ok(Self { + src_port_id: message.port_id_on_a, + src_channel_id: message.chan_id_on_a, + timeout_height: message.timeout_height_on_b, + timeout_timestamp: message.timeout_timestamp_on_b, + packet_data, + ibc_traces, + amount: Amount::from_u64(1), + receiver, + }) + } +} + impl<'a, S, CA> MaspVp<'a, S, CA> where S: StateRead, @@ -279,66 +369,377 @@ where Ok(()) } + fn check_ibc_transfer( + &self, + ibc_transfer: &IbcTransferInfo, + keys_changed: &BTreeSet, + ) -> Result<()> { + let IbcTransferInfo { + src_port_id, + src_channel_id, + timeout_height, + timeout_timestamp, + packet_data, + .. + } = ibc_transfer; + let sequence = get_last_sequence_send( + &self.ctx.post(), + src_port_id, + src_channel_id, + )?; + let commitment_key = + commitment_key(src_port_id, src_channel_id, sequence); + + if !keys_changed.contains(&commitment_key) { + return Err(Error::NativeVpError(native_vp::Error::AllocMessage( + format!( + "Expected IBC transfer didn't happen: Port ID \ + {src_port_id}, Channel ID {src_channel_id}, Sequence \ + {sequence}" + ), + ))); + } + + // The commitment is also validated in IBC VP. Make sure that for when + // IBC VP isn't triggered. + let actual: PacketCommitment = self + .ctx + .read_bytes_post(&commitment_key)? + .ok_or(Error::NativeVpError(native_vp::Error::AllocMessage( + format!( + "Packet commitment doesn't exist: Port ID {src_port_id}, \ + Channel ID {src_channel_id}, Sequence {sequence}" + ), + )))? + .into(); + let expected = compute_packet_commitment( + packet_data, + timeout_height, + timeout_timestamp, + ); + if actual != expected { + return Err(Error::NativeVpError(native_vp::Error::AllocMessage( + format!( + "Packet commitment mismatched: Port ID {src_port_id}, \ + Channel ID {src_channel_id}, Sequence {sequence}" + ), + ))); + } + + Ok(()) + } + + fn check_packet_receiving( + &self, + msg: &IbcMsgRecvPacket, + keys_changed: &BTreeSet, + ) -> Result<()> { + let receipt_key = receipt_key( + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ); + if !keys_changed.contains(&receipt_key) { + return Err(Error::NativeVpError(native_vp::Error::AllocMessage( + format!( + "The packet has not been received: Port ID {}, Channel \ + ID {}, Sequence {}", + msg.packet.port_id_on_b, + msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ), + ))); + } + Ok(()) + } + + // Apply the given transfer message to the changed balances structure + fn apply_transfer_msg( + &self, + mut acc: ChangedBalances, + ibc_transfer: &IbcTransferInfo, + keys_changed: &BTreeSet, + ) -> Result { + self.check_ibc_transfer(ibc_transfer, keys_changed)?; + + let IbcTransferInfo { + ibc_traces, + src_port_id, + src_channel_id, + amount, + receiver, + .. + } = ibc_transfer; + + let receiver = ibc_taddr(receiver.clone()); + for ibc_trace in ibc_traces { + let token = convert_to_address(ibc_trace).into_storage_result()?; + let delta = ValueSum::from_pair(token, *amount); + // If there is a transfer to the IBC account, then deduplicate the + // balance increase since we already accounted for it above + if is_sender_chain_source(ibc_trace, src_port_id, src_channel_id) { + 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 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, + dst_port_id: &PortId, + dst_channel_id: &ChannelId, + sequence: Sequence, + ) -> Result { + // Ensure that the event corresponds to the current changes to storage + let ack_key = storage::ack_key(dst_port_id, dst_channel_id, sequence); + // 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, + ibc_traces: Vec, + amount: Amount, + keys_changed: &BTreeSet, + ) -> Result { + self.check_packet_receiving(msg, keys_changed)?; + + // If the transfer was a failure, then enable funds to + // be withdrawn from the IBC internal address + if self.is_receiving_success( + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + )? { + for ibc_trace in ibc_traces { + // Get the received token + let token = namada_ibc::received_ibc_token( + ibc_trace, + &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); + // 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 ibc_transfer = IbcTransferInfo::try_from(msg.message)?; + let receiver = ibc_transfer.receiver.clone(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = + self.apply_transfer_msg(acc, &ibc_transfer, keys_changed)?; + } + IbcMessage::NftTransfer(msg) => { + let ibc_transfer = IbcTransferInfo::try_from(msg.message)?; + let receiver = ibc_transfer.receiver.clone(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = + self.apply_transfer_msg(acc, &ibc_transfer, 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); + let ibc_denom = packet_data.token.denom.to_string(); + let amount = packet_data + .token + .amount + .try_into() + .into_storage_result()?; + acc = self.apply_recv_msg( + acc, + &msg.message, + vec![ibc_denom], + amount, + keys_changed, + )?; + } else { + 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); + let ibc_traces = packet_data + .token_ids + .0 + .iter() + .map(|token_id| { + ibc_trace_for_nft(&packet_data.class_id, token_id) + }) + .collect(); + acc = self.apply_recv_msg( + acc, + &msg.message, + ibc_traces, + Amount::from_u64(1), + 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_msg: Option, ) -> 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); + + // Note the balance changes they imply + match ibc_msg { + Some(ibc_msg) => { + self.apply_ibc_packet(changed_balances, ibc_msg, keys_changed) + } + None => Ok(changed_balances), + } } // Check that MASP Transaction and state changes are valid @@ -359,6 +760,7 @@ where Error::NativeVpError(native_vp::Error::new_const(msg)) })?; let conversion_state = self.ctx.state.in_mem().get_conversion_state(); + let ibc_msg = 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 +793,11 @@ 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)?; + self.validate_state_and_get_transfer_data(keys_changed, ibc_msg)?; - 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 +842,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 +872,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 +885,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/namada/src/ledger/native_vp/multitoken.rs b/crates/namada/src/ledger/native_vp/multitoken.rs index 1d88396dd2..e55df8a957 100644 --- a/crates/namada/src/ledger/native_vp/multitoken.rs +++ b/crates/namada/src/ledger/native_vp/multitoken.rs @@ -418,7 +418,7 @@ mod tests { }; use crate::key::testing::keypair_1; use crate::ledger::gas::VpGasMeter; - use crate::ledger::ibc::storage::ibc_token; + use crate::ledger::ibc::trace::ibc_token; use crate::storage::TxIndex; use crate::token::storage_key::{balance_key, minted_balance_key}; use crate::vm::wasm::compilation_cache::common::testing::cache as wasm_cache; diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index ef80036715..31343c7c70 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -1166,12 +1166,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 { @@ -1199,7 +1199,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 6a3244a914..c58da446e1 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -121,7 +121,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 7832b23d33..ad1c4ce4a8 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -859,7 +859,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, @@ -867,14 +866,14 @@ 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_core::token::testing::arb_denominated_amount; use namada_governance::storage::proposal::testing::{ arb_init_proposal, arb_vote_proposal, }; use namada_governance::{InitProposalData, VoteProposalData}; use namada_ibc::testing::arb_ibc_any; - use namada_token::testing::{ - arb_denominated_amount, arb_transparent_transfer, - }; + use namada_token::testing::arb_transparent_transfer; use namada_token::{ ShieldedTransfer, ShieldingTransfer, TransparentTransfer, UnshieldingTransfer, @@ -888,8 +887,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 super::*; use crate::account::tests::{arb_init_account, arb_update_account}; @@ -1104,14 +1101,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; @@ -1134,8 +1123,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(&transfer.source), 1)), - (Just(MaspTxType::Unshielding), arb_deshielding_transfer(encode_address(&transfer.target), 1)), + (Just(MaspTxType::Shielding), arb_shielding_transfer(addr_taddr(transfer.source.clone()), 1)), + (Just(MaspTxType::Unshielding), arb_deshielding_transfer(addr_taddr(transfer.target.clone()), 1)), ], transfer in Just(transfer), ) -> (Tx, TxData) { diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 516a7ad611..b128c7da66 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; @@ -1681,19 +1677,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()) { @@ -1720,24 +1712,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. let mut rem_amount = amount.amount().raw_amount().0; @@ -1777,21 +1751,21 @@ impl ShieldedContext { memo.clone(), ) .map_err(builder::Error::SaplingBuild)?; - } 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, ) .map_err(builder::Error::TransparentBuild)?; + } 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; @@ -2152,6 +2126,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/tx.rs b/crates/sdk/src/tx.rs index b9daabeca2..d123f04456 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -50,8 +50,9 @@ use namada_governance::storage::proposal::{ InitProposalData, ProposalType, VoteProposalData, }; use namada_governance::storage::vote::ProposalVote; -use namada_ibc::storage::{channel_key, ibc_token}; -use namada_ibc::{is_nft_trace, MsgNftTransfer, MsgTransfer}; +use namada_ibc::storage::channel_key; +use namada_ibc::trace::{convert_to_address, is_nft_trace}; +use namada_ibc::{MsgNftTransfer, MsgTransfer}; use namada_proof_of_stake::parameters::{ PosParams, MAX_VALIDATOR_METADATA_LEN, }; @@ -2480,12 +2481,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) @@ -2519,7 +2514,7 @@ pub async fn build_ibc_transfer( context, &args.source, // The token will be escrowed to IBC address - &TransferTarget::Address(Address::Internal(InternalAddress::Ibc)), + &TransferTarget::Ibc(args.receiver.clone()), &args.token, validated_amount, !(args.tx.dry_run || args.tx.dry_run_wrapper), @@ -3494,13 +3489,8 @@ pub async fn gen_ibc_shielding_transfer( let ibc_denom = rpc::query_ibc_denom(context, &args.token, Some(&source)).await; let token = if args.refund { - if ibc_denom.contains('/') { - ibc_token(ibc_denom) - } else { - // the token is a base token - Address::decode(&ibc_denom) - .map_err(|e| Error::Other(format!("Invalid token: {e}")))? - } + convert_to_address(ibc_denom) + .map_err(|e| Error::Other(format!("Invalid token: {e}")))? } else { // Need to check the prefix namada_ibc::received_ibc_token( diff --git a/crates/sdk/src/wallet/mod.rs b/crates/sdk/src/wallet/mod.rs index d071af5ccc..c05afcda6b 100644 --- a/crates/sdk/src/wallet/mod.rs +++ b/crates/sdk/src/wallet/mod.rs @@ -22,7 +22,7 @@ use namada_core::masp::{ ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, }; use namada_core::time::DateTimeUtc; -use namada_ibc::is_ibc_denom; +use namada_ibc::trace::is_ibc_denom; pub use pre_genesis::gen_key_to_store; use rand::CryptoRng; use rand_core::RngCore; diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index a8e009ac78..7e84957fa9 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -604,7 +604,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..26006b3243 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -248,7 +248,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { BERTHA, ALBERT, token, - 50000, + 50_000_000_000, BERTHA_KEY, )?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; @@ -265,7 +265,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { BERTHA, receiver.to_string(), ibc_denom, - 50000.0, + 50_000_000_000.0, BERTHA_KEY, &port_id_b, &channel_id_b, @@ -410,7 +410,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 +419,7 @@ fn ibc_namada_gaia() -> Result<()> { GAIA_USER, receiver, get_gaia_denom_hash(ibc_denom), - 100, + 100000000, &port_id_gaia, &channel_id_gaia, )?; @@ -681,19 +681,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(); @@ -1626,22 +1626,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, @@ -1676,6 +1662,7 @@ fn try_invalid_transfers( Ok(()) } +#[allow(clippy::too_many_arguments)] fn transfer_on_chain( test: &Test, kind: impl AsRef, @@ -1687,7 +1674,8 @@ fn transfer_on_chain( ) -> 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 tx_args = vec![ kind.as_ref(), "--source", sender.as_ref(), @@ -1696,7 +1684,7 @@ fn transfer_on_chain( "--token", token.as_ref(), "--amount", - &amount.to_string(), + &amount, "--signing-keys", signer.as_ref(), "--node", @@ -1729,7 +1717,7 @@ fn transfer_back( BERTHA, receiver.to_string(), ibc_denom, - 50000.0, + 50_000_000_000.0, BERTHA_KEY, port_id_b, channel_id_b, @@ -2460,7 +2448,7 @@ fn check_shielded_balances( // Check the shielded balance on Chain B let ibc_denom = format!("{dest_port_id}/{dest_channel_id}/btc"); - check_balance(test_b, AB_VIEWING_KEY, ibc_denom, 10)?; + check_balance(test_b, AB_VIEWING_KEY, ibc_denom, 1_000_000_000)?; Ok(()) } diff --git a/crates/tests/src/vm_host_env/ibc.rs b/crates/tests/src/vm_host_env/ibc.rs index 4bee157c12..4baf4dd6d5 100644 --- a/crates/tests/src/vm_host_env/ibc.rs +++ b/crates/tests/src/vm_host_env/ibc.rs @@ -59,9 +59,10 @@ pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, client_state_key, client_update_height_key, client_update_timestamp_key, commitment_key, connection_counter_key, connection_key, - consensus_state_key, ibc_token, next_sequence_ack_key, - next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, + consensus_state_key, next_sequence_ack_key, next_sequence_recv_key, + next_sequence_send_key, port_key, receipt_key, }; +pub use namada::ledger::ibc::trace::ibc_token; use namada::ledger::native_vp::ibc::{ get_dummy_genesis_validator, get_dummy_header as tm_dummy_header, Ibc, }; @@ -78,7 +79,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; @@ -89,7 +90,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> { @@ -636,7 +637,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, @@ -644,7 +644,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() @@ -693,12 +693,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 da322d3fa0..5a46738f76 100644 --- a/crates/tests/src/vm_host_env/mod.rs +++ b/crates/tests/src/vm_host_env/mod.rs @@ -33,7 +33,7 @@ mod tests { use namada::ibc::context::transfer_mod::testing::DummyTransferModule; use namada::ibc::primitives::ToProto; use namada::ibc::Error as IbcActionError; - use namada::ledger::ibc::storage as ibc_storage; + use namada::ledger::ibc::{storage as ibc_storage, trace as ibc_trace}; use namada::ledger::native_vp::ibc::{ get_dummy_header as tm_dummy_header, Error as IbcError, }; @@ -1281,9 +1281,9 @@ mod tests { writes.extend(channel_writes); // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); - let ibc_token = ibc_storage::ibc_token(&denom); + let ibc_token = ibc_trace::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()); @@ -1293,7 +1293,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)| { @@ -1431,11 +1431,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] @@ -1646,7 +1646,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"); }); @@ -1705,7 +1705,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/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 678e27ccf8..b11625656a 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -8,9 +8,9 @@ use namada_core::address::Address; use namada_core::token::Amount; pub use namada_ibc::event::{IbcEvent, IbcEventType}; pub use namada_ibc::storage::{ - burn_tokens, ibc_token, is_ibc_key, mint_limit_key, mint_tokens, - throughput_limit_key, + burn_tokens, is_ibc_key, mint_limit_key, mint_tokens, throughput_limit_key, }; +pub use namada_ibc::trace::ibc_token; pub use namada_ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, NftTransferModule, ProofSpec, TransferModule, 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/wasm/Cargo.lock b/wasm/Cargo.lock index 7c491ea3d2..0648d060fb 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3773,6 +3773,7 @@ dependencies = [ "prost-types", "rand 0.8.5", "rand_core 0.6.4", + "ripemd", "serde", "serde_json", "sha2 0.9.9", @@ -3891,6 +3892,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", + "smooth-operator", "thiserror", "tracing", ] @@ -4022,7 +4024,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 44008f9c09..90fb2acbfd 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", @@ -3958,6 +3959,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", + "smooth-operator", "thiserror", "tracing", ] @@ -4076,7 +4078,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