diff --git a/.changelog/unreleased/improvements/2690-multi-tx-masp-vp.md b/.changelog/unreleased/improvements/2690-multi-tx-masp-vp.md new file mode 100644 index 00000000000..974d282a019 --- /dev/null +++ b/.changelog/unreleased/improvements/2690-multi-tx-masp-vp.md @@ -0,0 +1,2 @@ +- Updated the masp vp to accept multiple transfers in a single transaction. + ([\#3264](https://github.com/anoma/namada/pull/3264)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0affa237fc5..d282a45b09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4141,7 +4141,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "borsh 1.2.1", "chacha20", @@ -4154,7 +4154,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "aes", "bip0039", @@ -4174,7 +4174,7 @@ dependencies = [ "masp_note_encryption", "memuse", "nonempty", - "num-traits 0.2.17", + "num-traits 0.2.19", "proptest", "rand 0.8.5", "rand_core 0.6.4", @@ -4186,7 +4186,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "bellman", "blake2b_simd", @@ -4435,6 +4435,7 @@ dependencies = [ "toml 0.5.11", "tracing", "tracing-subscriber", + "uint", "wasm-instrument", "wasmer", "wasmer-cache", @@ -5442,6 +5443,14 @@ dependencies = [ "libm", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "git+https://github.com/heliaxdev/num-traits?rev=db259754d33f193f02cbb65520d9ac00614be2c4#db259754d33f193f02cbb65520d9ac00614be2c4" +dependencies = [ + "autocfg", +] + [[package]] name = "num256" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index 492d2b68a6b..ee2190733ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,9 +122,9 @@ ledger-transport-hid = "0.10.0" libc = "0.2.97" libloading = "0.7.2" linkme = "0.3.24" -# branch = "main" -masp_primitives = { git = "https://github.com/anoma/masp", rev = "b5e87a01c5928ce341f2d2a63a6fa6c4033be395" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "b5e87a01c5928ce341f2d2a63a6fa6c4033be395", default-features = false, features = ["local-prover"] } +# branch = "murisi/alternative-num-traits" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "6fc4692841a2241633792c429cc66b42023e5bf3" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "6fc4692841a2241633792c429cc66b42023e5bf3", default-features = false, features = ["local-prover"] } num256 = "0.3.5" num_cpus = "1.13.0" num-derive = "0.3.3" diff --git a/crates/core/src/arith.rs b/crates/core/src/arith.rs index 83e694e9beb..96d76423922 100644 --- a/crates/core/src/arith.rs +++ b/crates/core/src/arith.rs @@ -1,23 +1,9 @@ //! Arithmetics helpers +pub use masp_primitives::num_traits::ops::checked::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, +}; +pub use masp_primitives::num_traits::ops::overflowing::{ + OverflowingAdd, OverflowingSub, +}; pub use smooth_operator::{checked, Error}; - -/// Performs addition that returns `None` instead of wrapping around on -/// overflow. -pub trait CheckedAdd: Sized + Copy { - /// Adds two numbers, checking for overflow. If overflow happens, `None` is - /// returned. - fn checked_add(&self, rhs: Self) -> Option; -} - -/// Helpers for testing. -#[cfg(feature = "testing")] -pub mod testing { - use super::*; - - impl CheckedAdd for u64 { - fn checked_add(&self, rhs: Self) -> Option { - u64::checked_add(*self, rhs) - } - } -} diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 10fd8cab190..616ec872211 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::address::Address; -use crate::arith::{self, checked, CheckedAdd}; +use crate::arith::{self, checked, CheckedAdd, CheckedSub}; use crate::dec::{Dec, POS_DECIMAL_PRECISION}; use crate::hash::Hash; use crate::ibc::apps::transfer::types::Amount as IbcAmount; @@ -336,8 +336,38 @@ impl Amount { } impl CheckedAdd for Amount { - fn checked_add(&self, rhs: Self) -> Option { - self.checked_add(rhs) + type Output = Amount; + + fn checked_add(self, rhs: Self) -> Option { + Amount::checked_add(&self, rhs) + } +} + +impl CheckedAdd for &Amount { + type Output = Amount; + + fn checked_add(self, rhs: Self) -> Option { + self.checked_add(*rhs) + } +} + +impl CheckedSub for Amount { + type Output = Amount; + + fn checked_sub(self, amount: Self) -> Option { + self.raw + .checked_sub(amount.raw) + .map(|result| Self { raw: result }) + } +} + +impl CheckedSub for &Amount { + type Output = Amount; + + fn checked_sub(self, amount: Self) -> Option { + self.raw + .checked_sub(amount.raw) + .map(|result| Amount { raw: result }) } } diff --git a/crates/core/src/uint.rs b/crates/core/src/uint.rs index cd036fad5c6..3e21dc204b5 100644 --- a/crates/core/src/uint.rs +++ b/crates/core/src/uint.rs @@ -6,6 +6,7 @@ use std::cmp::Ordering; use std::fmt; +use std::ops::Not; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -17,7 +18,10 @@ use num_integer::Integer; use uint::construct_uint; use super::dec::{Dec, POS_DECIMAL_PRECISION}; -use crate::arith::{self, checked, CheckedAdd}; +use crate::arith::{ + self, checked, CheckedAdd, CheckedNeg, CheckedSub, OverflowingAdd, + OverflowingSub, +}; use crate::token; use crate::token::{AmountParseError, MaspDigitPos}; @@ -741,16 +745,20 @@ impl I256 { } } -impl CheckedAdd for I256 { - fn checked_add(&self, rhs: Self) -> Option { - self.checked_add(rhs) +// NOTE: This is here only because MASP requires it for `ValueSum` addition +impl CheckedAdd for &I256 { + type Output = I256; + + fn checked_add(self, rhs: Self) -> Option { + self.checked_add(*rhs) } } -// NOTE: This is here only because MASP requires it for `ValueSum` addition -impl num_traits::CheckedAdd for I256 { - fn checked_add(&self, rhs: &Self) -> Option { - self.checked_add(*rhs) +impl CheckedAdd for I256 { + type Output = I256; + + fn checked_add(self, rhs: Self) -> Option { + I256::checked_add(&self, rhs) } } @@ -876,6 +884,237 @@ impl TryFrom for i128 { } } +construct_uint! { + #[derive( + BorshSerialize, + BorshDeserialize, + BorshSchema, + )] + + struct SignedAmountInt(5); +} + +/// A positive or negative amount +#[derive( + Copy, + Clone, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Default, +)] +pub struct I320(SignedAmountInt); + +impl OverflowingAdd for I320 +where + T: Into, +{ + type Output = Self; + + fn overflowing_add(self, other: T) -> (Self, bool) { + let (res, overflow) = self.0.overflowing_add(other.into().0); + (Self(res), overflow) + } +} + +impl<'a, T> OverflowingAdd for &'a I320 +where + T: Into, +{ + type Output = I320; + + fn overflowing_add(self, other: T) -> (I320, bool) { + let (res, overflow) = self.0.overflowing_add(other.into().0); + (I320(res), overflow) + } +} + +impl OverflowingSub for I320 +where + T: Into, +{ + type Output = Self; + + fn overflowing_sub(self, other: T) -> (Self, bool) { + let (res, overflow) = self.0.overflowing_sub(other.into().0); + (I320(res), overflow) + } +} + +impl<'a, T> OverflowingSub for &'a I320 +where + T: Into, +{ + type Output = I320; + + fn overflowing_sub(self, other: T) -> (I320, bool) { + let (res, overflow) = self.0.overflowing_sub(other.into().0); + (I320(res), overflow) + } +} + +impl From for I320 { + fn from(lo: Uint) -> Self { + let mut arr = [0u64; Self::N_WORDS]; + arr[..4].copy_from_slice(&lo.0); + Self(SignedAmountInt(arr)) + } +} + +impl From for I320 { + fn from(lo: token::Amount) -> Self { + let mut arr = [0u64; Self::N_WORDS]; + arr[..4].copy_from_slice(&lo.raw_amount().0); + Self(SignedAmountInt(arr)) + } +} + +impl TryInto for I320 { + type Error = std::io::Error; + + fn try_into(self) -> Result { + if self.0.0[Self::N_WORDS - 1] == 0 { + Ok(token::Amount::from_uint( + Uint([self.0.0[0], self.0.0[1], self.0.0[2], self.0.0[3]]), + 0, + ) + .unwrap()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Integer overflow when casting to Amount", + )) + } + } +} + +impl I320 { + const N_WORDS: usize = 5; + + /// Gives the one value of an SignedAmount + pub fn one() -> Self { + Self(SignedAmountInt::one()) + } + + /// Check if the amount is negative (less than zero) + pub fn is_negative(&self) -> bool { + self.0.bit(Self::N_WORDS * SignedAmountInt::WORD_BITS - 1) + } + + /// Check if the amount is positive (greater than zero) + pub fn is_positive(&self) -> bool { + !self.is_negative() && !self.0.is_zero() + } + + /// Get the absolute value + fn abs(&self) -> Self { + if self.is_negative() { + self.overflowing_neg() + } else { + *self + } + } + + /// Get the absolute value + pub fn checked_abs(&self) -> Option { + if self.is_negative() { + self.checked_neg() + } else { + Some(*self) + } + } + + /// Compute the negation of a number. + pub fn overflowing_neg(self) -> Self { + (!self).overflowing_add(Self::one()).0 + } + + /// Get a string representation of `self` as a + /// native token amount. + pub fn to_string_native(self) -> String { + let mut res = self.abs().0.to_string(); + if self.is_negative() { + res.insert(0, '-'); + } + res + } + + /// Given a u128 and [`MaspDigitPos`], construct the corresponding + /// amount. + pub fn from_masp_denominated( + val: i128, + denom: MaspDigitPos, + ) -> Result>::Error> { + let abs = val.unsigned_abs(); + #[allow(clippy::cast_possible_truncation)] + let lo = abs as u64; + let hi = (abs >> 64) as u64; + let lo_pos = denom as usize; + #[allow(clippy::arithmetic_side_effects)] + let hi_pos = lo_pos + 1; + let mut raw = [0u64; Self::N_WORDS]; + raw[lo_pos] = lo; + raw[hi_pos] = hi; + i64::try_from(raw[Self::N_WORDS - 1]).map(|_| { + let res = Self(SignedAmountInt(raw)); + if val.is_negative() { + res.checked_neg().unwrap() + } else { + res + } + }) + } +} + +impl Not for I320 { + type Output = Self; + + fn not(self) -> Self { + Self(!self.0) + } +} + +impl CheckedNeg for I320 { + type Output = I320; + + fn checked_neg(self) -> Option { + let neg = self.overflowing_neg(); + (neg != self).then_some(neg) + } +} + +impl CheckedAdd for I320 { + type Output = I320; + + fn checked_add(self, rhs: Self) -> Option { + let res = self.overflowing_add(rhs).0; + ((self.is_negative() != rhs.is_negative()) + || (self.is_negative() == res.is_negative())) + .then_some(res) + } +} + +impl CheckedSub for I320 { + type Output = I320; + + fn checked_sub(self, rhs: Self) -> Option { + let res = self.overflowing_add(rhs.overflowing_neg()).0; + ((self.is_negative() == rhs.is_negative()) + || (res.is_negative() == self.is_negative())) + .then_some(res) + } +} + +impl PartialOrd for I320 { + fn partial_cmp(&self, other: &Self) -> Option { + #[allow(clippy::arithmetic_side_effects)] + (!self.is_negative(), self.0 << 1) + .partial_cmp(&(!other.is_negative(), other.0 << 1)) + } +} + #[cfg(any(test, feature = "testing"))] /// Testing helpers pub mod testing { diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index 4ad467fc0ca..3f500eb42c5 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -149,6 +149,7 @@ tiny-bip39.workspace = true tiny-hderive.workspace = true toml.workspace = true tracing.workspace = true +uint = "0.9.5" wasm-instrument = { workspace = true, optional = true } wasmer = { workspace = true, optional = true } wasmer-cache = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } diff --git a/crates/namada/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/crates/namada/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 897c096ea70..23be647a845 100644 --- a/crates/namada/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/crates/namada/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -17,7 +17,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use borsh::BorshDeserialize; -use namada_core::arith::checked; +use namada_core::arith::{checked, CheckedAdd, CheckedNeg, CheckedSub}; use namada_core::booleans::BoolResultUnitExt; use namada_core::eth_bridge_pool::erc20_token_address; use namada_core::hints; @@ -38,6 +38,7 @@ use crate::ledger::native_vp::{self, Ctx, NativeVp, StorageReader}; use crate::storage::Key; use crate::token::storage_key::balance_key; use crate::token::Amount; +use crate::uint::I320; use crate::vm::WasmCacheAccess; #[derive(thiserror::Error, Debug)] @@ -45,31 +46,21 @@ use crate::vm::WasmCacheAccess; /// Generic error that may be returned by the validity predicate pub struct Error(#[from] native_vp::Error); -/// A positive or negative amount -#[derive(Copy, Clone)] -enum SignedAmount { - Positive(Amount), - Negative(Amount), -} - /// An [`Amount`] that has been updated with some delta value. #[derive(Copy, Clone)] struct AmountDelta { /// The base [`Amount`], before applying the delta. base: Amount, /// The delta to be applied to the base amount. - delta: SignedAmount, + delta: I320, } impl AmountDelta { /// Resolve the updated amount by applying the delta value. #[inline] - fn resolve(self) -> Result { - match self.delta { - SignedAmount::Positive(delta) => checked!(self.base + delta), - SignedAmount::Negative(delta) => checked!(self.base - delta), - } - .map_err(|e| Error(e.into())) + fn resolve(self) -> Result { + checked!(self.delta + I320::from(self.base)) + .map_err(|e| Error(e.into())) } } @@ -116,15 +107,12 @@ where }; Ok(Some(AmountDelta { base: before, - delta: if before > after { - SignedAmount::Negative( - checked!(before - after).map_err(|e| Error(e.into()))?, - ) - } else { - SignedAmount::Positive( - checked!(after - before).map_err(|e| Error(e.into()))?, - ) - }, + delta: checked!(I320::from(after) - I320::from(before)).map_err( + |error| { + tracing::warn!(?error, %account_key, "reading pre value"); + Error(error.into()) + }, + )?, })) } @@ -160,26 +148,15 @@ where match (debit, credit) { // success case ( - Some(AmountDelta { - delta: SignedAmount::Negative(debit), - .. - }), - Some( - escrow_balance @ AmountDelta { - delta: SignedAmount::Positive(credit), - .. - }, - ), - ) => Ok((debit == expected_debit && credit == expected_credit) - .then_some(escrow_balance)), + Some(AmountDelta { delta: debit, .. }), + Some(escrow_balance @ AmountDelta { delta: credit, .. }), + ) if !debit.is_positive() && !credit.is_negative() => { + Ok((Some(debit) == I320::from(expected_debit).checked_neg() + && credit == I320::from(expected_credit)) + .then_some(escrow_balance)) + } // user did not debit from their account - ( - Some(AmountDelta { - delta: SignedAmount::Positive(_), - .. - }), - _, - ) => { + (Some(AmountDelta { delta, .. }), _) if !delta.is_negative() => { tracing::debug!( "The account {} was not debited.", payer_account @@ -187,13 +164,7 @@ where Ok(None) } // user did not credit escrow account - ( - _, - Some(AmountDelta { - delta: SignedAmount::Negative(_), - .. - }), - ) => { + (_, Some(AmountDelta { delta, .. })) if !delta.is_positive() => { tracing::debug!( "The Ethereum bridge pool's escrow was not credited from \ account {}.", @@ -203,13 +174,11 @@ where } // some other error occurred while calculating // balance deltas - (None, _) | (_, None) => { - Err(native_vp::Error::AllocMessage(format!( - "Could not calculate the balance delta for {}", - payer_account - )) - .into()) - } + _ => Err(native_vp::Error::AllocMessage(format!( + "Could not calculate the balance delta for {}", + payer_account + )) + .into()), } } @@ -302,7 +271,7 @@ where None => return Ok(false), }; - let wnam_cap = { + let wnam_cap: Amount = { let key = whitelist::Key { asset: wnam_address, suffix: whitelist::KeyType::Cap, @@ -313,7 +282,7 @@ where .map_err(Error)? .unwrap_or_default() }; - if escrowed_balance > wnam_cap { + if escrowed_balance > I320::from(wnam_cap) { tracing::debug!( ?transfer, escrowed_nam = %escrowed_balance.to_string_native(), @@ -831,35 +800,35 @@ mod test_bridge_pool_vp { update_balances( write_log, Balance::new(TransferToEthereumKind::Erc20, bertha_address()), - SignedAmount::Positive(BERTHA_WEALTH.into()), - SignedAmount::Positive(BERTHA_TOKENS.into()), + I320::from(BERTHA_WEALTH), + I320::from(BERTHA_TOKENS), ); update_balances( write_log, Balance::new(TransferToEthereumKind::Nut, daewon_address()), - SignedAmount::Positive(DAEWONS_GAS.into()), - SignedAmount::Positive(DAES_NUTS.into()), + I320::from(DAEWONS_GAS), + I320::from(DAES_NUTS), ); // set up the initial balances of the bridge pool update_balances( write_log, Balance::new(TransferToEthereumKind::Erc20, BRIDGE_POOL_ADDRESS), - SignedAmount::Positive(ESCROWED_AMOUNT.into()), - SignedAmount::Positive(ESCROWED_TOKENS.into()), + I320::from(ESCROWED_AMOUNT), + I320::from(ESCROWED_TOKENS), ); update_balances( write_log, Balance::new(TransferToEthereumKind::Nut, BRIDGE_POOL_ADDRESS), - SignedAmount::Positive(ESCROWED_AMOUNT.into()), - SignedAmount::Positive(ESCROWED_NUTS.into()), + I320::from(ESCROWED_AMOUNT), + I320::from(ESCROWED_NUTS), ); // set up the initial balances of the ethereum bridge account update_balances( write_log, Balance::new(TransferToEthereumKind::Erc20, BRIDGE_ADDRESS), - SignedAmount::Positive(ESCROWED_AMOUNT.into()), + I320::from(ESCROWED_AMOUNT), // we only care about escrowing NAM - SignedAmount::Positive(0.into()), + I320::from(0), ); write_log.commit_tx(); } @@ -869,23 +838,19 @@ mod test_bridge_pool_vp { fn update_balances( write_log: &mut WriteLog, balance: Balance, - gas_delta: SignedAmount, - token_delta: SignedAmount, + gas_delta: I320, + token_delta: I320, ) -> BTreeSet { // wnam is drawn from the same account if balance.asset == wnam() && !matches!(&balance.owner, Address::Internal(_)) { - use SignedAmount::*; - // update the balance of nam let original_balance = std::cmp::max(balance.token, balance.gas); - let updated_balance = match (gas_delta, token_delta) { - (Negative(x), Negative(y)) => original_balance - x - y, - (Negative(x), Positive(y)) => original_balance - x + y, - (Positive(x), Negative(y)) => original_balance + x - y, - (Positive(x), Positive(y)) => original_balance + x + y, - }; + let updated_balance: Amount = + (I320::from(original_balance) + gas_delta + token_delta) + .try_into() + .unwrap(); // write the changes to the log let account_key = balance_key(&nam(), &balance.owner); @@ -920,16 +885,14 @@ mod test_bridge_pool_vp { let account_key = balance_key(&nam(), &balance.owner); // update the balance of nam - let new_gas_balance = match gas_delta { - SignedAmount::Positive(amount) => balance.gas + amount, - SignedAmount::Negative(amount) => balance.gas - amount, - }; + let new_gas_balance: Amount = + (I320::from(balance.gas) + gas_delta).try_into().unwrap(); // update the balance of tokens - let new_token_balance = match token_delta { - SignedAmount::Positive(amount) => balance.token + amount, - SignedAmount::Negative(amount) => balance.token - amount, - }; + let new_token_balance: Amount = (I320::from(balance.token) + + token_delta) + .try_into() + .unwrap(); // write the changes to the log write_log @@ -995,10 +958,10 @@ mod test_bridge_pool_vp { /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( - payer_gas_delta: SignedAmount, - gas_escrow_delta: SignedAmount, - payer_delta: SignedAmount, - escrow_delta: SignedAmount, + payer_gas_delta: I320, + gas_escrow_delta: I320, + payer_delta: I320, + escrow_delta: I320, insert_transfer: F, expect: Expect, ) where @@ -1085,10 +1048,10 @@ mod test_bridge_pool_vp { #[test] fn test_happy_flow() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1106,10 +1069,10 @@ mod test_bridge_pool_vp { #[test] fn test_incorrect_gas_withdrawn() { assert_bridge_pool( - SignedAmount::Negative(10.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(10), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1127,10 +1090,10 @@ mod test_bridge_pool_vp { #[test] fn test_payer_balance_must_decrease() { assert_bridge_pool( - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1148,10 +1111,10 @@ mod test_bridge_pool_vp { #[test] fn test_incorrect_gas_deposited() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(10.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(10), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1170,10 +1133,10 @@ mod test_bridge_pool_vp { #[test] fn test_incorrect_token_deltas() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(10.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(10), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1191,10 +1154,10 @@ mod test_bridge_pool_vp { #[test] fn test_incorrect_tokens_escrowed() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(10.into()), - SignedAmount::Positive(10.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(10), + I320::from(10), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1212,10 +1175,10 @@ mod test_bridge_pool_vp { #[test] fn test_escrowed_gas_must_increase() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + -I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1233,10 +1196,10 @@ mod test_bridge_pool_vp { #[test] fn test_escrowed_tokens_must_increase() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Positive(TOKENS.into()), - SignedAmount::Negative(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + I320::from(TOKENS), + -I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1254,10 +1217,10 @@ mod test_bridge_pool_vp { #[test] fn test_not_adding_transfer_rejected() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, _| BTreeSet::from([get_pending_key(transfer)]), Expect::Rejected, ); @@ -1268,10 +1231,10 @@ mod test_bridge_pool_vp { #[test] fn test_add_wrong_transfer() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -1300,10 +1263,10 @@ mod test_bridge_pool_vp { #[test] fn test_add_wrong_key() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -1332,10 +1295,10 @@ mod test_bridge_pool_vp { #[test] fn test_signed_merkle_root_changes_rejected() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { log.write( &get_pending_key(transfer), @@ -1381,8 +1344,8 @@ mod test_bridge_pool_vp { gas: BERTHA_WEALTH.into(), token: BERTHA_TOKENS.into(), }, - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), + -I320::from(GAS_FEE), + -I320::from(TOKENS), ); keys_changed.append(&mut new_keys_changed); @@ -1396,8 +1359,8 @@ mod test_bridge_pool_vp { gas: ESCROWED_AMOUNT.into(), token: ESCROWED_TOKENS.into(), }, - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Positive(TOKENS.into()), + I320::from(GAS_FEE), + I320::from(TOKENS), ); keys_changed.append(&mut new_keys_changed); let verifiers = BTreeSet::default(); @@ -1760,8 +1723,8 @@ mod test_bridge_pool_vp { gas: DAEWONS_GAS.into(), token: DAES_NUTS.into(), }, - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), + -I320::from(GAS_FEE), + -I320::from(TOKENS), ); keys_changed.append(&mut new_keys_changed); @@ -1775,8 +1738,8 @@ mod test_bridge_pool_vp { gas: ESCROWED_AMOUNT.into(), token: ESCROWED_NUTS.into(), }, - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Positive(TOKENS.into()), + I320::from(GAS_FEE), + I320::from(TOKENS), ); keys_changed.append(&mut new_keys_changed); @@ -1823,10 +1786,10 @@ mod test_bridge_pool_vp { #[test] fn test_bridge_pool_vp_rejects_wnam_nut() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { transfer.transfer.kind = TransferToEthereumKind::Nut; transfer.transfer.asset = wnam(); @@ -1845,10 +1808,10 @@ mod test_bridge_pool_vp { #[test] fn test_bridge_pool_vp_accepts_wnam_erc20() { assert_bridge_pool( - SignedAmount::Negative(GAS_FEE.into()), - SignedAmount::Positive(GAS_FEE.into()), - SignedAmount::Negative(TOKENS.into()), - SignedAmount::Positive(TOKENS.into()), + -I320::from(GAS_FEE), + I320::from(GAS_FEE), + -I320::from(TOKENS), + I320::from(TOKENS), |transfer, log| { transfer.transfer.kind = TransferToEthereumKind::Erc20; transfer.transfer.asset = wnam(); diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 0cd8d163046..edca2b17d0c 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -1,34 +1,40 @@ //! MASP native VP use std::cmp::Ordering; -use std::collections::BTreeSet; +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; -use masp_primitives::transaction::components::I128Sum; -use masp_primitives::transaction::Transaction; +use masp_primitives::transaction::components::transparent::Authorization; +use masp_primitives::transaction::components::{ + I128Sum, TxIn, TxOut, ValueSum, +}; +use masp_primitives::transaction::{Transaction, TransparentAddress}; use namada_core::address::Address; use namada_core::address::InternalAddress::Masp; -use namada_core::arith::checked; +use namada_core::arith::{checked, CheckedAdd, CheckedSub}; use namada_core::booleans::BoolResultUnitExt; -use namada_core::collections::{HashMap, HashSet}; +use namada_core::collections::HashSet; use namada_core::masp::encode_asset_type; use namada_core::storage::Key; +use namada_gas::GasMetering; +use namada_governance::storage::is_proposal_accepted; +use namada_proof_of_stake::Epoch; use namada_sdk::masp::verify_shielded_tx; -use namada_state::{OptionExt, ResultExt, StateRead}; +use namada_state::{ConversionState, OptionExt, ResultExt, StateRead}; use namada_token::read_denom; use namada_tx::BatchedTxRef; use namada_vp_env::VpEnv; -use num_traits::ops::checked::{CheckedAdd, CheckedSub}; use ripemd::Digest as RipemdDigest; use sha2::Digest as Sha2Digest; use thiserror::Error; use token::storage_key::{ - balance_key, is_any_shielded_action_balance_key, is_masp_allowed_key, - is_masp_key, is_masp_nullifier_key, masp_commitment_anchor_key, + is_any_shielded_action_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; @@ -36,6 +42,7 @@ use crate::ledger::native_vp; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::token; use crate::token::MaspDigitPos; +use crate::uint::I320; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -58,11 +65,17 @@ where pub ctx: Ctx<'a, S, CA>, } -struct TransparentTransferData { - source: Address, - target: Address, - token: Address, - amount: Amount, +// The balances changed by the transaction, split between masp and non-masp +// balances. The masp collection carries the token addresses. The collection of +// 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)] +struct ChangedBalances { + tokens: BTreeMap, + decoder: BTreeMap, + pre: BTreeMap>, + post: BTreeMap>, } impl<'a, S, CA> MaspVp<'a, S, CA> @@ -70,6 +83,32 @@ where S: StateRead, CA: 'static + WasmCacheAccess, { + /// Return if the parameter change was done via a governance proposal + pub fn is_valid_parameter_change( + &self, + tx: &BatchedTxRef<'_>, + ) -> Result<()> { + tx.tx.data(tx.cmt).map_or_else( + || { + Err(native_vp::Error::new_const( + "MASP parameter changes require tx data to be present", + ) + .into()) + }, + |data| { + is_proposal_accepted(&self.ctx.pre(), data.as_ref()) + .map_err(Error::NativeVpError)? + .ok_or_else(|| { + native_vp::Error::new_const( + "MASP parameter changes can only be performed by \ + a governance proposal that has been accepted", + ) + .into() + }) + }, + ) + } + // Check that the transaction correctly revealed the nullifiers, if needed fn valid_nullifiers_reveal( &self, @@ -184,22 +223,10 @@ where &self, transaction: &Transaction, ) -> Result<()> { - let shielded_spends = match transaction.sapling_bundle() { - Some(bundle) if !bundle.shielded_spends.is_empty() => { - &bundle.shielded_spends - } - _ => { - let error = - Error::NativeVpError(native_vp::Error::SimpleMessage( - "Missing expected spend descriptions in shielded \ - transaction", - )); - tracing::debug!("{error}"); - return Err(error); - } - }; - - for description in shielded_spends { + for description in transaction + .sapling_bundle() + .map_or(&vec![], |bundle| &bundle.shielded_spends) + { let anchor_key = masp_commitment_anchor_key(description.anchor); // Check if the provided anchor was published before @@ -253,143 +280,73 @@ where Ok(()) } + // Check that transfer is pinned correctly and record the balance changes fn validate_state_and_get_transfer_data( &self, keys_changed: &BTreeSet, - ) -> Result { - // Check that the transaction didn't write unallowed masp keys - let masp_keys_changed: Vec<&Key> = - keys_changed.iter().filter(|key| is_masp_key(key)).collect(); - - if masp_keys_changed - .iter() - .any(|key| !is_masp_allowed_key(key)) - { - return Err(Error::NativeVpError(native_vp::Error::SimpleMessage( - "Found modifications to non-allowed masp keys", - ))); - } - - // Verify the changes to balance keys and return the transparent - // transfer data Get the token from the balance key of the MASP - let balance_addresses: Vec<[&Address; 2]> = keys_changed + ) -> Result { + // Get the changed balance keys + let mut counterparts_balances = keys_changed .iter() - .filter_map(is_any_shielded_action_balance_key) - .collect(); + .filter_map(is_any_shielded_action_balance_key); - let masp_balances: Vec<&[&Address; 2]> = balance_addresses - .iter() - .filter(|addresses| addresses[1] == &Address::Internal(Masp)) - .collect(); - let token = match masp_balances.len() { - 0 => { - // No masp balance modification found, assume shielded - // transaction and return dummy transparent data - return Ok(TransparentTransferData { - source: Address::Internal(Masp), - target: Address::Internal(Masp), - token: self.ctx.get_native_token()?, - amount: Amount::zero(), - }); - } - 1 => masp_balances[0][0].to_owned(), - _ => { - // Only one transparent balance of MASP can be updated by the - // shielding or unshielding transaction - return Err(Error::NativeVpError( - native_vp::Error::SimpleMessage( - "More than one MASP transparent balance was modified", - ), - )); - } - }; - - let counterparts: Vec<&[&Address; 2]> = balance_addresses - .iter() - .filter(|addresses| addresses[1] != &Address::Internal(Masp)) - .collect(); - // NOTE: since we don't allow more than one transfer per tx in this vp, - // there's no need to check the token address in the balance key nor the - // change to the actual balance, the multitoken VP will verify these - let counterpart = match counterparts.len() { - 1 => counterparts[0][1].to_owned(), - _ => { - return Err(Error::NativeVpError( - native_vp::Error::SimpleMessage( - "An invalid number of non-MASP transparent balances \ - was modified", - ), - )); - } - }; - - let pre_masp_balance: Amount = self - .ctx - .read_pre(&balance_key(&token, &Address::Internal(Masp)))? - .unwrap_or_default(); - let post_masp_balance: Amount = self - .ctx - .read_post(&balance_key(&token, &Address::Internal(Masp)))? - .unwrap_or_default(); - let (amount, source, target) = - match pre_masp_balance.cmp(&post_masp_balance) { - Ordering::Equal => { - return Err(Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Found a MASP transaction that moves no \ - transparent funds", - ), - )); + 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); } - Ordering::Less => ( - checked!(post_masp_balance - pre_masp_balance) - .map_err(|e| Error::NativeVpError(e.into()))?, - counterpart, - Address::Internal(Masp), - ), - Ordering::Greater => ( - checked!(pre_masp_balance - post_masp_balance) - .map_err(|e| Error::NativeVpError(e.into()))?, - Address::Internal(Masp), - counterpart, - ), - }; - - Ok(TransparentTransferData { - source, - target, - token, - amount, - }) - } -} + // 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(), + )), + )); -// Make a map to help recognize asset types lacking an epoch -fn unepoched_tokens( - token: &Address, - denom: token::Denomination, -) -> Result> { - let mut unepoched_tokens = HashMap::new(); - for digit in MaspDigitPos::iter() { - let asset_type = encode_asset_type(token.clone(), denom, digit, None) - .wrap_err("unable to create asset type")?; - unepoched_tokens.insert(asset_type, (token.clone(), denom, digit)); + 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) + }, + ) } - Ok(unepoched_tokens) -} -impl<'a, S, CA> NativeVp for MaspVp<'a, S, CA> -where - S: StateRead, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - - fn validate_tx( + // Check that MASP Transaction and state changes are valid + fn is_valid_masp_transfer( &self, tx_data: &BatchedTxRef<'_>, keys_changed: &BTreeSet, - _verifiers: &BTreeSet
, ) -> Result<()> { let epoch = self.ctx.get_block_epoch()?; let conversion_state = self.ctx.state.in_mem().get_conversion_state(); @@ -409,344 +366,129 @@ where let mut transparent_tx_pool = shielded_tx.sapling_value_balance(); // Check the validity of the keys and get the transfer data - let transfer = + let mut changed_balances = self.validate_state_and_get_transfer_data(keys_changed)?; - let denom = read_denom(&self.ctx.pre(), &transfer.token)? - .ok_or_err_msg( - "No denomination found in storage for the given token", - )?; + let masp_address_hash = TransparentAddress(<[u8; 20]>::from( + ripemd::Ripemd160::digest(sha2::Sha256::digest( + &Address::Internal(Masp).serialize_to_vec(), + )), + )); + verify_sapling_balancing_value( + changed_balances + .pre + .get(&masp_address_hash) + .unwrap_or(&ValueSum::zero()), + changed_balances + .post + .get(&masp_address_hash) + .unwrap_or(&ValueSum::zero()), + &shielded_tx.sapling_value_balance(), + epoch, + &changed_balances.tokens, + conversion_state, + )?; + + // The set of addresses that are required to authorize this transaction + let mut signers = BTreeSet::new(); + + // Checks on the sapling bundle + // 1. The spend descriptions' anchors are valid + // 2. The convert descriptions's anchors are valid + // 3. The nullifiers provided by the transaction have not been + // revealed previously (even in the same tx) and no unneeded + // nullifier is being revealed by the tx + // 4. The transaction must correctly update the note commitment tree + // in storage with the new output descriptions + self.valid_spend_descriptions_anchor(&shielded_tx)?; + self.valid_convert_descriptions_anchor(&shielded_tx)?; + self.valid_nullifiers_reveal(keys_changed, &shielded_tx)?; + self.valid_note_commitment_update(&shielded_tx)?; - if transfer.source != Address::Internal(Masp) { - // No shielded spends nor shielded conversions are allowed - if shielded_tx.sapling_bundle().is_some_and(|bundle| { - !(bundle.shielded_spends.is_empty() - && bundle.shielded_converts.is_empty()) - }) { - let error = native_vp::Error::new_const( - "No shielded spends nor shielded conversions are allowed", - ) - .into(); - tracing::debug!("{error}"); - return Err(error); + // Checks on the transparent bundle, if present + validate_transparent_bundle( + &shielded_tx, + &mut changed_balances, + &mut transparent_tx_pool, + epoch, + conversion_state, + &mut signers, + )?; + + // 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 { + signers.insert(addr); } + } - let transp_bundle = - shielded_tx.transparent_bundle().ok_or_err_msg( - "Expected transparent outputs in shielding transaction", - )?; - let mut total_in_values = token::Amount::zero(); - let source_enc = transfer.source.serialize_to_vec(); - let hash = - ripemd::Ripemd160::digest(sha2::Sha256::digest(&source_enc)); - - // To help recognize asset types not in the conversion tree - let unepoched_tokens = unepoched_tokens(&transfer.token, denom)?; - // Handle transparent input - // - // The following boundary conditions must be satisfied: - // - // 1. Total of transparent input values equals containing transfer - // amount - // - // 2. Asset type must be properly derived - // - // 3. Public key must be the hash of the source - for vin in &transp_bundle.vin { - // Non-masp sources add to the transparent tx pool - transparent_tx_pool = transparent_tx_pool - .checked_add( - &I128Sum::from_nonnegative( - vin.asset_type, - i128::from(vin.value), - ) - .ok() - .ok_or_err_msg( - "invalid value or asset type for amount", - )?, - ) - .ok_or_err_msg("Overflow in input sum")?; - - // Satisfies 3. - if <[u8; 20]>::from(hash) != vin.address.0 { - let error = native_vp::Error::new_const( - "The public key of the output account does not match \ - the transfer target", - ) - .into(); - tracing::debug!("{error}"); - return Err(error); - } - match conversion_state.assets.get(&vin.asset_type) { - // Satisfies 2. Note how the asset's epoch must be equal to - // the present: users must never be allowed to backdate - // transparent inputs to a transaction for they would then - // be able to claim rewards while locking their assets for - // negligible time periods. - Some(( - (address, asset_denom, digit), - asset_epoch, - _, - _, - )) if *address == transfer.token - && *asset_denom == denom - && *asset_epoch == epoch => - { - total_in_values = total_in_values - .checked_add(token::Amount::from_masp_denominated( - vin.value, *digit, - )) - .ok_or_else(|| { - Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Overflow in total in value sum", - ), - ) - })?; - } - // Maybe the asset type has no attached epoch - None if unepoched_tokens.contains_key(&vin.asset_type) => { - let (token, denom, digit) = - &unepoched_tokens[&vin.asset_type]; - // Determine what the asset type would be if it were - // epoched - let epoched_asset_type = encode_asset_type( - token.clone(), - *denom, - *digit, - Some(epoch), - ) - .wrap_err("unable to create asset type")?; - if conversion_state - .assets - .contains_key(&epoched_asset_type) - { - // If such an epoched asset type is available in the - // conversion tree, then we must reject the - // unepoched variant + 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 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 + // can't check whether such an action has been authorized by the + // original sender since their address is not in this Namada + // instance. However, we do know that the overall changes in the + // IBC state are okay since the IBC VP does check this + // transaction. So the best we can do is just to ensure that + // funds intended for the IBC are not being siphoned from the + // Transactions inside this Tx. We achieve this by not allowing + // the IBC to be in the transparent output of any of the + // Transaction(s). + if let Some(transp_bundle) = shielded_tx.transparent_bundle() { + for vout in transp_bundle.vout.iter() { + if vout.address == ibc_address_hash { let error = native_vp::Error::new_const( - "Epoch is missing from asset type", + "Simultaneous credit and debit of IBC account \ + in a MASP transaction not allowed", ) .into(); tracing::debug!("{error}"); return Err(error); - } else { - // Otherwise note the contribution to this - // trransparent input - total_in_values = total_in_values - .checked_add( - token::Amount::from_masp_denominated( - vin.value, *digit, - ), - ) - .ok_or_else(|| { - Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Overflow in total in values sum", - ), - ) - })?; } } - // unrecognized asset - _ => { - return Err(native_vp::Error::new_alloc(format!( - "Unrecognized asset {}", - vin.asset_type - )) - .into()); - } - }; - } - // Satisfies 1. - if total_in_values != transfer.amount { - return Err(native_vp::Error::new_const( - "Total amount of transparent input values was not the \ - same as the transferred amount", - ) - .into()); - } - } else { - // Handle shielded input - // The following boundary conditions must be satisfied - // 1. Zero transparent input - // 2. At least one shielded input - // 3. The spend descriptions' anchors are valid - // 4. The convert descriptions's anchors are valid - if shielded_tx - .transparent_bundle() - .is_some_and(|bundle| !bundle.vin.is_empty()) - { - let error = native_vp::Error::new_const( - "Transparent input to a transaction from the masp must be \ - 0", - ) - .into(); - tracing::debug!("{error}"); - return Err(error); - } - - if !shielded_tx - .sapling_bundle() - .is_some_and(|bundle| !bundle.shielded_spends.is_empty()) - { - return Err(Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Missing expected shielded spends", - ), - )); - } - - self.valid_spend_descriptions_anchor(&shielded_tx)?; - self.valid_convert_descriptions_anchor(&shielded_tx)?; - } - - // The transaction must correctly update the note commitment tree - // in storage with the new output descriptions and also reveal the - // nullifiers correctly (only if needed) NOTE: these two checks - // validate the keys that the transaction write in storage and therefore - // must be done regardless of the type of transaction (shielding, - // shielded, unshielding) since a malicious tx could try to write keys - // in an invalid way - self.valid_note_commitment_update(&shielded_tx)?; - self.valid_nullifiers_reveal(keys_changed, &shielded_tx)?; - - if transfer.target != Address::Internal(Masp) { - // Handle transparent output - // - // The following boundary conditions must be satisfied: - // - // 1. Total of transparent output values equals containing transfer - // amount - // - // 2. Asset type must be properly derived - // - // 3. Public key must be the hash of the target - - let transp_bundle = - shielded_tx.transparent_bundle().ok_or_err_msg( - "Expected transparent outputs in unshielding transaction", - )?; - - let mut total_out_values = token::Amount::zero(); - let target_enc = transfer.target.serialize_to_vec(); - let hash = - ripemd::Ripemd160::digest(sha2::Sha256::digest(&target_enc)); - // To help recognize asset types not in the conversion tree - let unepoched_tokens = unepoched_tokens(&transfer.token, denom)?; - - for out in &transp_bundle.vout { - // Non-masp destinations subtract from transparent tx - // pool - transparent_tx_pool = transparent_tx_pool - .checked_sub( - &I128Sum::from_nonnegative( - out.asset_type, - i128::from(out.value), - ) - .ok() - .ok_or_err_msg( - "invalid value or asset type for amount", - )?, - ) - .ok_or_err_msg("Underflow in output subtraction")?; - - // Satisfies 3. - if <[u8; 20]>::from(hash) != out.address.0 { - let error = native_vp::Error::new_const( - "The public key of the output account does not match \ - the transfer target", - ) - .into(); - tracing::debug!("{error}"); - return Err(error); } - match conversion_state.assets.get(&out.asset_type) { - // Satisfies 2. - Some(( - (address, asset_denom, digit), - asset_epoch, - _, - _, - )) if *address == transfer.token - && *asset_denom == denom - && *asset_epoch <= epoch => - { - total_out_values = total_out_values - .checked_add(token::Amount::from_masp_denominated( - out.value, *digit, - )) - .ok_or_else(|| { - Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Overflow in total out values sum", - ), - ) - })?; - } - // Maybe the asset type has no attached epoch - None if unepoched_tokens.contains_key(&out.asset_type) => { - let (_token, _denom, digit) = - &unepoched_tokens[&out.asset_type]; - // Otherwise note the contribution to this - // trransparent input - total_out_values = total_out_values - .checked_add(token::Amount::from_masp_denominated( - out.value, *digit, - )) - .ok_or_else(|| { - Error::NativeVpError( - native_vp::Error::SimpleMessage( - "Overflow in total out values sum", - ), - ) - })?; - } - // unrecognized asset - _ => { - return Err(native_vp::Error::new_alloc(format!( - "Unrecognized asset {}", - out.asset_type - )) - .into()); - } - }; - } - // Satisfies 1. - if total_out_values != transfer.amount { - return Err(native_vp::Error::new_const( - "Total amount of transparent output values was not the \ - same as the transferred amount", - ) - .into()); - } - } else { - // Handle shielded output - // The following boundary conditions must be satisfied - // 1. Zero transparent output - // 2. At least one shielded output - - // Satisfies 1. - if shielded_tx - .transparent_bundle() - .is_some_and(|bundle| !bundle.vout.is_empty()) - { - let error = native_vp::Error::new_const( - "Transparent output to a transaction from the masp must \ - be 0", - ) - .into(); - tracing::debug!("{error}"); - return Err(error); - } - - // Staisfies 2. - if !shielded_tx - .sapling_bundle() - .is_some_and(|bundle| !bundle.shielded_outputs.is_empty()) - { + } else if let Some(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 = + crate::account::public_keys_index_map( + &self.ctx.pre(), + signer, + )?; + let threshold = + crate::account::threshold(&self.ctx.pre(), signer)? + .unwrap_or(1); + let max_signatures_per_transaction = + crate::parameters::max_signatures_per_transaction( + &self.ctx.pre(), + )?; + let mut gas_meter = self.ctx.gas_meter.borrow_mut(); + tx_data + .tx + .verify_signatures( + &[tx_data.tx.raw_header_hash()], + public_keys_index_map, + &Some(signer.clone()), + threshold, + max_signatures_per_transaction, + || gas_meter.consume(crate::gas::VERIFY_TX_SIG_GAS), + ) + .map_err(native_vp::Error::new)?; + } else { + // We are not able to decode the signer, so just fail let error = native_vp::Error::new_const( - "There were no shielded outputs in the sapling bundle", + "Unable to decode a transaction signer", ) .into(); tracing::debug!("{error}"); @@ -754,6 +496,7 @@ where } } + // Ensure that the shielded transaction exactly balances match transparent_tx_pool.partial_cmp(&I128Sum::zero()) { None | Some(Ordering::Less) => { let error = native_vp::Error::new_const( @@ -783,3 +526,327 @@ where .map_err(Error::NativeVpError) } } + +// Make a map to help recognize asset types lacking an epoch +fn unepoched_tokens( + token: &Address, + denom: token::Denomination, + tokens: &mut BTreeMap< + AssetType, + (Address, token::Denomination, MaspDigitPos), + >, +) -> Result<()> { + for digit in MaspDigitPos::iter() { + let asset_type = encode_asset_type(token.clone(), denom, digit, None) + .wrap_err("unable to create asset type")?; + tokens.insert(asset_type, (token.clone(), denom, digit)); + } + Ok(()) +} + +// Handle transparent input +fn validate_transparent_input( + vin: &TxIn, + changed_balances: &mut ChangedBalances, + transparent_tx_pool: &mut I128Sum, + epoch: Epoch, + conversion_state: &ConversionState, + signers: &mut BTreeSet, +) -> Result<()> { + // A decrease in the balance of an account needs to be + // authorized by the account of this transparent input + signers.insert(vin.address); + // Non-masp sources add to the transparent tx pool + *transparent_tx_pool = transparent_tx_pool + .checked_add( + &I128Sum::from_nonnegative(vin.asset_type, i128::from(vin.value)) + .ok() + .ok_or_err_msg("invalid value or asset type for amount")?, + ) + .ok_or_err_msg("Overflow in input sum")?; + + let bal_ref = changed_balances + .pre + .entry(vin.address) + .or_insert(ValueSum::zero()); + + match conversion_state.assets.get(&vin.asset_type) { + // Note how the asset's epoch must be equal to the present: users + // must never be allowed to backdate transparent inputs to a + // transaction for they would then be able to claim rewards while + // locking their assets for negligible time periods. + Some(asset) if asset.epoch == epoch => { + let amount = token::Amount::from_masp_denominated( + vin.value, + asset.digit_pos, + ); + *bal_ref = bal_ref + .checked_sub(&ValueSum::from_pair(asset.token.clone(), amount)) + .ok_or_else(|| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in bundle balance", + )) + })?; + } + // Maybe the asset type has no attached epoch + None if changed_balances.tokens.contains_key(&vin.asset_type) => { + let (token, denom, digit) = + &changed_balances.tokens[&vin.asset_type]; + // Determine what the asset type would be if it were epoched + let epoched_asset_type = + encode_asset_type(token.clone(), *denom, *digit, Some(epoch)) + .wrap_err("unable to create asset type")?; + if conversion_state.assets.contains_key(&epoched_asset_type) { + // If such an epoched asset type is available in the + // conversion tree, then we must reject the unepoched + // variant + let error = + Error::NativeVpError(native_vp::Error::SimpleMessage( + "epoch is missing from asset type", + )); + tracing::debug!("{error}"); + return Err(error); + } else { + // Otherwise note the contribution to this transparent input + let amount = + token::Amount::from_masp_denominated(vin.value, *digit); + *bal_ref = bal_ref + .checked_sub(&ValueSum::from_pair(token.clone(), amount)) + .ok_or_else(|| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in bundle balance", + )) + })?; + } + } + // unrecognized asset + _ => { + let error = Error::NativeVpError(native_vp::Error::SimpleMessage( + "Unable to decode asset type", + )); + tracing::debug!("{error}"); + return Err(error); + } + }; + Ok(()) +} + +// Handle transparent output +fn validate_transparent_output( + out: &TxOut, + changed_balances: &mut ChangedBalances, + transparent_tx_pool: &mut I128Sum, + epoch: Epoch, + conversion_state: &ConversionState, +) -> Result<()> { + // Non-masp destinations subtract from transparent tx pool + *transparent_tx_pool = transparent_tx_pool + .checked_sub( + &I128Sum::from_nonnegative(out.asset_type, i128::from(out.value)) + .ok() + .ok_or_err_msg("invalid value or asset type for amount")?, + ) + .ok_or_err_msg("Underflow in output subtraction")?; + + let bal_ref = changed_balances + .post + .entry(out.address) + .or_insert(ValueSum::zero()); + + match conversion_state.assets.get(&out.asset_type) { + Some(asset) if asset.epoch <= epoch => { + let amount = token::Amount::from_masp_denominated( + out.value, + asset.digit_pos, + ); + *bal_ref = bal_ref + .checked_sub(&ValueSum::from_pair(asset.token.clone(), amount)) + .ok_or_else(|| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in bundle balance", + )) + })?; + } + // Maybe the asset type has no attached epoch + None if changed_balances.tokens.contains_key(&out.asset_type) => { + // Otherwise note the contribution to this transparent output + let (token, _denom, digit) = + &changed_balances.tokens[&out.asset_type]; + let amount = + token::Amount::from_masp_denominated(out.value, *digit); + *bal_ref = bal_ref + .checked_sub(&ValueSum::from_pair(token.clone(), amount)) + .ok_or_else(|| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in bundle balance", + )) + })?; + } + // unrecognized asset + _ => { + let error = Error::NativeVpError(native_vp::Error::SimpleMessage( + "Unable to decode asset type", + )); + tracing::debug!("{error}"); + return Err(error); + } + }; + Ok(()) +} + +// Update the transaction value pool and also ensure that the Transaction is +// consistent with the balance changes. I.e. the transparent inputs are not more +// than the initial balances and that the transparent outputs are not more than +// the final balances. +fn validate_transparent_bundle( + shielded_tx: &Transaction, + changed_balances: &mut ChangedBalances, + transparent_tx_pool: &mut I128Sum, + epoch: Epoch, + conversion_state: &ConversionState, + signers: &mut BTreeSet, +) -> Result<()> { + if let Some(transp_bundle) = shielded_tx.transparent_bundle() { + for vin in transp_bundle.vin.iter() { + validate_transparent_input( + vin, + changed_balances, + transparent_tx_pool, + epoch, + conversion_state, + signers, + )?; + } + + for out in transp_bundle.vout.iter() { + validate_transparent_output( + out, + changed_balances, + transparent_tx_pool, + epoch, + conversion_state, + )?; + } + } + Ok(()) +} + +// Apply the given Sapling value balance component to the accumulator +fn apply_balance_component( + acc: &ValueSum, + val: i128, + digit: MaspDigitPos, + address: Address, +) -> Result> { + // Put val into the correct digit position + let decoded_change = + I320::from_masp_denominated(val, digit).map_err(|_| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in MASP value balance", + )) + })?; + // Tag the numerical change with the token type + let decoded_change = ValueSum::from_pair(address, decoded_change); + // Apply the change to the accumulator + acc.checked_add(&decoded_change).ok_or_else(|| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Overflow in MASP value balance", + )) + }) +} + +// Verify that the pre balance + the Sapling value balance = the post balance +// using the decodings in tokens and conversion_state for assistance. +fn verify_sapling_balancing_value( + pre: &ValueSum, + post: &ValueSum, + sapling_value_balance: &I128Sum, + target_epoch: Epoch, + tokens: &BTreeMap, + conversion_state: &ConversionState, +) -> Result<()> { + let mut acc = ValueSum::::from_sum(post.clone()); + for (asset_type, val) in sapling_value_balance.components() { + // Only assets with at most the target timestamp count + match conversion_state.assets.get(asset_type) { + Some(asset) if asset.epoch <= target_epoch => { + acc = apply_balance_component( + &acc, + *val, + asset.digit_pos, + asset.token.clone(), + )?; + } + None if tokens.contains_key(asset_type) => { + let (token, _denom, digit) = &tokens[asset_type]; + acc = + apply_balance_component(&acc, *val, *digit, token.clone())?; + } + _ => { + let error = + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Unable to decode asset type", + )); + tracing::debug!("{error}"); + return Err(error); + } + } + } + if acc == ValueSum::from_sum(pre.clone()) { + Ok(()) + } else { + let error = Error::NativeVpError(native_vp::Error::SimpleMessage( + "MASP balance change not equal to Sapling value balance", + )); + tracing::debug!("{error}"); + Err(error) + } +} + +impl<'a, S, CA> NativeVp for MaspVp<'a, S, CA> +where + S: StateRead, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + fn validate_tx( + &self, + tx_data: &BatchedTxRef<'_>, + keys_changed: &BTreeSet, + _verifiers: &BTreeSet
, + ) -> Result<()> { + let masp_keys_changed: Vec<&Key> = + keys_changed.iter().filter(|key| is_masp_key(key)).collect(); + let non_allowed_changes = masp_keys_changed.iter().any(|key| { + !is_masp_transfer_key(key) && !is_masp_token_map_key(key) + }); + let masp_token_map_changed = masp_keys_changed + .iter() + .any(|key| is_masp_token_map_key(key)); + let masp_transfer_changes = masp_keys_changed + .iter() + .any(|key| is_masp_transfer_key(key)); + // Check that the transaction didn't write unallowed masp keys + if non_allowed_changes { + Err(Error::NativeVpError(native_vp::Error::SimpleMessage( + "Found modifications to non-allowed masp keys", + ))) + } else if masp_token_map_changed && masp_transfer_changes { + Err(Error::NativeVpError(native_vp::Error::SimpleMessage( + "Cannot simultaneously do governance proposal and MASP \ + transfer", + ))) + } else if masp_token_map_changed { + // The token map can only be changed by a successful governance + // proposal + self.is_valid_parameter_change(tx_data) + } else if masp_transfer_changes { + // The MASP transfer keys can only be changed by a valid Transaction + self.is_valid_masp_transfer(tx_data, keys_changed) + } else { + // Changing no MASP keys at all is also fine + Ok(()) + } + } +} diff --git a/crates/namada/src/ledger/native_vp/mod.rs b/crates/namada/src/ledger/native_vp/mod.rs index e03f187de9d..f5b27cd0cb4 100644 --- a/crates/namada/src/ledger/native_vp/mod.rs +++ b/crates/namada/src/ledger/native_vp/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::assign_op_pattern)] //! Native validity predicate interface associated with internal accounts such //! as the PoS and IBC modules. diff --git a/crates/proof_of_stake/src/epoched.rs b/crates/proof_of_stake/src/epoched.rs index 76ffb3d9c8d..f3f3ca2c8a0 100644 --- a/crates/proof_of_stake/src/epoched.rs +++ b/crates/proof_of_stake/src/epoched.rs @@ -464,7 +464,12 @@ impl where FutureEpochs: EpochOffset, PastEpochs: EpochOffset, - Data: BorshSerialize + BorshDeserialize + CheckedAdd + 'static + Debug, + Data: BorshSerialize + + BorshDeserialize + + Copy + + CheckedAdd + + 'static + + Debug, { /// Open the handle pub fn open(key: storage::Key) -> Self { diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index 253486fe926..caaff8f7fbd 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -182,14 +182,18 @@ where .conversion_state .assets .iter() - .map( - |(&asset_type, ((ref addr, denom, digit), epoch, ref conv, _))| { + .map(|(&asset_type, asset)| { + ( + asset_type, ( - asset_type, - (addr.clone(), *denom, *digit, *epoch, conv.clone().into()), - ) - }, - ) + asset.token.clone(), + asset.denom, + asset.digit_pos, + asset.epoch, + asset.conversion.clone().into(), + ), + ) + }) .collect()) } @@ -203,18 +207,22 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some(((addr, denom, digit), epoch, conv, pos)) = + if let Some(asset) = ctx.state.in_mem().conversion_state.assets.get(&asset_type) { Ok(Some(( - addr.clone(), - *denom, - *digit, - *epoch, + asset.token.clone(), + asset.denom, + asset.digit_pos, + asset.epoch, Into::::into( - conv.clone(), + asset.conversion.clone(), ), - ctx.state.in_mem().conversion_state.tree.path(*pos), + ctx.state + .in_mem() + .conversion_state + .tree + .path(asset.leaf_pos), ))) } else { Ok(None) diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 0949febfad0..e109b93dc2f 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -256,6 +256,7 @@ where use masp_primitives::transaction::components::I128Sum as MaspAmount; use namada_core::masp::encode_asset_type; use namada_core::storage::Epoch; + use namada_storage::conversion_state::ConversionLeaf; use namada_storage::{Error, ResultExt}; use namada_trans_token::storage_key::balance_key; use namada_trans_token::{MaspDigitPos, NATIVE_MAX_DECIMAL_PLACES}; @@ -487,12 +488,14 @@ where // Add a conversion from the previous asset type storage.conversion_state_mut().assets.insert( old_asset, - ( - (token.clone(), denom, digit), - prev_epoch, - MaspAmount::zero().into(), - 0, - ), + ConversionLeaf { + token: token.clone(), + denom, + digit_pos: digit, + epoch: prev_epoch, + conversion: MaspAmount::zero().into(), + leaf_pos: 0, + }, ); } } @@ -516,16 +519,18 @@ where .into_par_iter() .with_min_len(notes_per_thread_min) .with_max_len(notes_per_thread_max) - .map(|(idx, (asset, _epoch, conv, pos))| { - if let Some(current_conv) = current_convs.get(asset) { + .map(|(idx, leaf)| { + // Try to get the applicable conversion delta + let cur_conv_key = (leaf.token.clone(), leaf.denom, leaf.digit_pos); + if let Some(current_conv) = current_convs.get(&cur_conv_key) { // Use transitivity to update conversion - *conv += current_conv.clone(); + leaf.conversion += current_conv.clone(); } // Update conversion position to leaf we are about to create - *pos = idx; + leaf.leaf_pos = idx; // The merkle tree need only provide the conversion commitment, // the remaining information is provided through the storage API - Node::new(conv.cmu().to_repr()) + Node::new(leaf.conversion.cmu().to_repr()) }) .collect(); @@ -581,12 +586,14 @@ where let tree_size = storage.conversion_state().tree.size(); storage.conversion_state_mut().assets.insert( new_asset, - ( - (addr.clone(), denom, digit), + ConversionLeaf { + token: addr.clone(), + denom, + digit_pos: digit, epoch, - MaspAmount::zero().into(), - tree_size, - ), + conversion: MaspAmount::zero().into(), + leaf_pos: tree_size, + }, ); } } diff --git a/crates/shielded_token/src/lib.rs b/crates/shielded_token/src/lib.rs index c8b43dd3578..b14965fc0ed 100644 --- a/crates/shielded_token/src/lib.rs +++ b/crates/shielded_token/src/lib.rs @@ -27,7 +27,7 @@ use std::str::FromStr; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::dec::Dec; pub use namada_storage::conversion_state::{ - ConversionState, WithConversionState, + ConversionLeaf, ConversionState, WithConversionState, }; use serde::{Deserialize, Serialize}; pub use storage::*; diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index 0fb6b68e840..f584ab7fff2 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -74,7 +74,7 @@ pub fn is_masp_key(key: &storage::Key) -> bool { ) } /// Check if the given storage key is allowed to be touched by a masp transfer -pub fn is_masp_allowed_key(key: &storage::Key) -> bool { +pub fn is_masp_transfer_key(key: &storage::Key) -> bool { match &key.segments[..] { [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if *addr == address::MASP @@ -110,6 +110,14 @@ pub fn is_masp_commitment_anchor_key(key: &storage::Key) -> bool { ] if *addr == address::MASP && prefix == MASP_NOTE_COMMITMENT_ANCHOR_PREFIX) } +/// Check if the given storage key is a masp token map key +pub fn is_masp_token_map_key(key: &storage::Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + ] if *addr == address::MASP && prefix == MASP_TOKEN_MAP_KEY) +} + /// Get a key for a masp nullifier pub fn masp_nullifier_key(nullifier: &Nullifier) -> storage::Key { storage::Key::from(address::MASP.to_db_key()) diff --git a/crates/storage/src/conversion_state.rs b/crates/storage/src/conversion_state.rs index c106eb91557..43ad69037d2 100644 --- a/crates/storage/src/conversion_state.rs +++ b/crates/storage/src/conversion_state.rs @@ -14,6 +14,23 @@ use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; +/// A representation of a leaf in the conversion tree +#[derive(Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] +pub struct ConversionLeaf { + /// The token associated with this asset type + pub token: Address, + /// The denomination associated with the above toke + pub denom: Denomination, + /// The digit position covered by this asset type + pub digit_pos: MaspDigitPos, + /// The epoch of the asset type + pub epoch: Epoch, + /// The actual conversion and generator + pub conversion: AllowedConversion, + /// The position of this leaf in the conversion tree + pub leaf_pos: usize, +} + /// A representation of the conversion state #[derive( Debug, Default, BorshSerialize, BorshDeserialize, BorshDeserializer, @@ -24,16 +41,7 @@ pub struct ConversionState { /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, /// Map assets to their latest conversion and position in Merkle tree - #[allow(clippy::type_complexity)] - pub assets: BTreeMap< - AssetType, - ( - (Address, Denomination, MaspDigitPos), - Epoch, - AllowedConversion, - usize, - ), - >, + pub assets: BTreeMap, } /// Able to borrow mutable conversion state. diff --git a/crates/trans_token/src/storage_key.rs b/crates/trans_token/src/storage_key.rs index 9f0d2247c30..c282696750d 100644 --- a/crates/trans_token/src/storage_key.rs +++ b/crates/trans_token/src/storage_key.rs @@ -185,22 +185,43 @@ pub fn is_any_minted_balance_key(key: &storage::Key) -> Option<&Address> { } } +/// 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; 2]> { +) -> Option<(&Address, ShieldedActionOwner<'_>)> { is_any_token_balance_key(key).map_or_else( || { - is_any_minted_balance_key(key).map(|token| { - [ - token, - &Address::Internal( - namada_core::address::InternalAddress::Ibc, - ), - ] - }) + is_any_minted_balance_key(key) + .map(|token| (token, ShieldedActionOwner::Minted)) }, - Some, + |[token, owner]| Some((token, ShieldedActionOwner::Owner(owner))), ) } diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 5428f6741b7..ec08cd717a3 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -8,15 +8,12 @@ use namada_parameters::storage; use namada_sdk::address::Address; use namada_sdk::hash::Hash as CodeHash; use namada_sdk::masp_primitives::asset_type::AssetType; -use namada_sdk::masp_primitives::convert::AllowedConversion; use namada_sdk::masp_primitives::merkle_tree::FrozenCommitmentTree; use namada_sdk::masp_primitives::sapling; use namada_sdk::migrations; -use namada_sdk::proof_of_stake::Epoch; use namada_sdk::storage::{DbColFam, Key}; -use namada_sdk::token::{Denomination, MaspDigitPos}; use namada_shielded_token::storage_key::masp_token_map_key; -use namada_shielded_token::ConversionState; +use namada_shielded_token::{ConversionLeaf, ConversionState}; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; @@ -33,15 +30,7 @@ pub struct NewConversionState { pub tree: FrozenCommitmentTree, /// Map assets to their latest conversion and position in Merkle tree #[allow(clippy::type_complexity)] - pub assets: BTreeMap< - AssetType, - ( - (Address, Denomination, MaspDigitPos), - Epoch, - AllowedConversion, - usize, - ), - >, + pub assets: BTreeMap, } impl From for NewConversionState { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 242d8933b03..26cd012189e 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -110,7 +110,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-traits", + "num-traits 0.2.17", "zeroize", ] @@ -126,7 +126,7 @@ dependencies = [ "ark-std", "derivative", "num-bigint", - "num-traits", + "num-traits 0.2.17", "paste", "rustc_version 0.3.3", "zeroize", @@ -149,7 +149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ "num-bigint", - "num-traits", + "num-traits 0.2.17", "quote", "syn 1.0.109", ] @@ -170,7 +170,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "num-traits", + "num-traits 0.2.17", "rand 0.8.5", ] @@ -762,7 +762,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits", + "num-traits 0.2.17", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -1974,7 +1974,7 @@ dependencies = [ "libm", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -3017,7 +3017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" dependencies = [ "integer-sqrt", - "num-traits", + "num-traits 0.2.17", "uint", ] @@ -3150,7 +3150,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" dependencies = [ - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -3384,7 +3384,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "borsh 1.4.0", "chacha20", @@ -3397,7 +3397,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "aes", "bip0039", @@ -3417,7 +3417,7 @@ dependencies = [ "masp_note_encryption", "memuse", "nonempty", - "num-traits", + "num-traits 0.2.19", "proptest", "rand 0.8.5", "rand_core 0.6.4", @@ -3429,7 +3429,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "bellman", "blake2b_simd", @@ -3606,7 +3606,7 @@ dependencies = [ "namada_tx_env", "namada_vote_ext", "namada_vp_env", - "num-traits", + "num-traits 0.2.17", "num256", "orion", "owo-colors", @@ -3631,6 +3631,7 @@ dependencies = [ "tokio", "toml 0.5.11", "tracing", + "uint", "wasm-instrument", "wasmer", "wasmer-cache", @@ -3692,7 +3693,7 @@ dependencies = [ "namada_migrations", "num-integer", "num-rational", - "num-traits", + "num-traits 0.2.17", "num256", "num_enum", "primitive-types", @@ -3892,7 +3893,7 @@ dependencies = [ "namada_parameters", "namada_storage", "namada_trans_token", - "num-traits", + "num-traits 0.2.17", "once_cell", "proptest", "serde", @@ -3948,7 +3949,7 @@ dependencies = [ "namada_token", "namada_tx", "namada_vote_ext", - "num-traits", + "num-traits 0.2.17", "num256", "orion", "owo-colors", @@ -4069,7 +4070,7 @@ dependencies = [ "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", - "num-traits", + "num-traits 0.2.17", "prost", "regex", "serde", @@ -4121,7 +4122,7 @@ dependencies = [ "namada_macros", "namada_migrations", "num-derive 0.3.3", - "num-traits", + "num-traits 0.2.17", "proptest", "prost", "prost-types", @@ -4240,7 +4241,7 @@ dependencies = [ "num-integer", "num-iter", "num-rational", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4251,7 +4252,7 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4260,7 +4261,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4292,7 +4293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4303,7 +4304,7 @@ checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4315,7 +4316,7 @@ dependencies = [ "autocfg", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4328,6 +4329,14 @@ dependencies = [ "libm", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "git+https://github.com/heliaxdev/num-traits?rev=db259754d33f193f02cbb65520d9ac00614be2c4#db259754d33f193f02cbb65520d9ac00614be2c4" +dependencies = [ + "autocfg", +] + [[package]] name = "num256" version = "0.3.5" @@ -4337,7 +4346,7 @@ dependencies = [ "lazy_static", "num", "num-derive 0.3.3", - "num-traits", + "num-traits 0.2.17", "serde", "serde_derive", ] @@ -4823,7 +4832,7 @@ dependencies = [ "bit-vec", "bitflags 2.5.0", "lazy_static", - "num-traits", + "num-traits 0.2.17", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", @@ -5297,7 +5306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -5741,7 +5750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", - "num-traits", + "num-traits 0.2.17", "thiserror", "time", ] @@ -6027,7 +6036,7 @@ dependencies = [ "ed25519", "flex-error", "futures", - "num-traits", + "num-traits 0.2.17", "once_cell", "prost", "prost-types", @@ -6056,7 +6065,7 @@ dependencies = [ "flex-error", "futures", "k256", - "num-traits", + "num-traits 0.2.17", "once_cell", "prost", "prost-types", @@ -6110,7 +6119,7 @@ dependencies = [ "bytes", "flex-error", "num-derive 0.4.2", - "num-traits", + "num-traits 0.2.17", "prost", "prost-types", "serde", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 9ce0a2ff404..5e6476438fa 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -110,7 +110,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-traits", + "num-traits 0.2.17", "zeroize", ] @@ -126,7 +126,7 @@ dependencies = [ "ark-std", "derivative", "num-bigint", - "num-traits", + "num-traits 0.2.17", "paste", "rustc_version 0.3.3", "zeroize", @@ -149,7 +149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ "num-bigint", - "num-traits", + "num-traits 0.2.17", "quote", "syn 1.0.109", ] @@ -170,7 +170,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "num-traits", + "num-traits 0.2.17", "rand 0.8.5", ] @@ -762,7 +762,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits", + "num-traits 0.2.17", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -1974,7 +1974,7 @@ dependencies = [ "libm", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -3017,7 +3017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" dependencies = [ "integer-sqrt", - "num-traits", + "num-traits 0.2.17", "uint", ] @@ -3150,7 +3150,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" dependencies = [ - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -3364,7 +3364,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "borsh 1.2.1", "chacha20", @@ -3377,7 +3377,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "aes", "bip0039", @@ -3397,7 +3397,7 @@ dependencies = [ "masp_note_encryption", "memuse", "nonempty", - "num-traits", + "num-traits 0.2.19", "proptest", "rand 0.8.5", "rand_core 0.6.4", @@ -3409,7 +3409,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=b5e87a01c5928ce341f2d2a63a6fa6c4033be395#b5e87a01c5928ce341f2d2a63a6fa6c4033be395" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "bellman", "blake2b_simd", @@ -3584,7 +3584,7 @@ dependencies = [ "namada_tx_env", "namada_vote_ext", "namada_vp_env", - "num-traits", + "num-traits 0.2.17", "num256", "orion", "owo-colors", @@ -3609,6 +3609,7 @@ dependencies = [ "tokio", "toml 0.5.11", "tracing", + "uint", "wasm-instrument", "wasmer", "wasmer-cache", @@ -3666,7 +3667,7 @@ dependencies = [ "namada_macros", "num-integer", "num-rational", - "num-traits", + "num-traits 0.2.17", "num256", "num_enum", "primitive-types", @@ -3847,7 +3848,7 @@ dependencies = [ "namada_parameters", "namada_storage", "namada_trans_token", - "num-traits", + "num-traits 0.2.17", "once_cell", "proptest", "serde", @@ -3901,7 +3902,7 @@ dependencies = [ "namada_token", "namada_tx", "namada_vote_ext", - "num-traits", + "num-traits 0.2.17", "num256", "orion", "owo-colors", @@ -4018,7 +4019,7 @@ dependencies = [ "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", - "num-traits", + "num-traits 0.2.17", "prost", "regex", "serde", @@ -4068,7 +4069,7 @@ dependencies = [ "namada_gas", "namada_macros", "num-derive 0.3.3", - "num-traits", + "num-traits 0.2.17", "proptest", "prost", "prost-types", @@ -4185,7 +4186,7 @@ dependencies = [ "num-integer", "num-iter", "num-rational", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4196,7 +4197,7 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4205,7 +4206,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4237,7 +4238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4248,7 +4249,7 @@ checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4260,7 +4261,7 @@ dependencies = [ "autocfg", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -4273,6 +4274,14 @@ dependencies = [ "libm", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "git+https://github.com/heliaxdev/num-traits?rev=db259754d33f193f02cbb65520d9ac00614be2c4#db259754d33f193f02cbb65520d9ac00614be2c4" +dependencies = [ + "autocfg", +] + [[package]] name = "num256" version = "0.3.5" @@ -4282,7 +4291,7 @@ dependencies = [ "lazy_static", "num", "num-derive 0.3.3", - "num-traits", + "num-traits 0.2.17", "serde", "serde_derive", ] @@ -4760,7 +4769,7 @@ dependencies = [ "bit-vec", "bitflags 2.5.0", "lazy_static", - "num-traits", + "num-traits 0.2.17", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", @@ -5234,7 +5243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", - "num-traits", + "num-traits 0.2.17", ] [[package]] @@ -5678,7 +5687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", - "num-traits", + "num-traits 0.2.17", "thiserror", "time", ] @@ -5964,7 +5973,7 @@ dependencies = [ "ed25519", "flex-error", "futures", - "num-traits", + "num-traits 0.2.17", "once_cell", "prost", "prost-types", @@ -5993,7 +6002,7 @@ dependencies = [ "flex-error", "futures", "k256", - "num-traits", + "num-traits 0.2.17", "once_cell", "prost", "prost-types", @@ -6047,7 +6056,7 @@ dependencies = [ "bytes", "flex-error", "num-derive 0.4.2", - "num-traits", + "num-traits 0.2.17", "prost", "prost-types", "serde", diff --git a/wasm_for_tests/tx_fail.wasm b/wasm_for_tests/tx_fail.wasm index ca9da6b34bf..841dd2f7b90 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 b2ccfdafe60..7347844346c 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 8c7b32a95ec..c38e9a068f2 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 381b774c493..58ccea53382 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 c7795018e7a..c9d1627d9ae 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 2d8e32ebbe6..6f6d7cb18c5 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 ed7e2745cab..aa0fa63a044 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 68120d506e7..a7d5c982814 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 25023a172c8..b3b7f5974e8 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 74e7c33afb0..f97addb4d7a 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 24b588a3113..6458b85882f 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 a97453caf18..d818c0b02a4 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 6fd38923e53..9105a550de7 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 502dc460528..7404595c462 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 157571b0822..b3f5c11ef81 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 e2b5e17e14d..081a754e7e2 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 edc3b999931..f1860392e13 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 4877ffbe1e4..fd8151bb4d0 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ