From 932156c4b31a09b438f899180ddb467de1a2a316 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 29 Mar 2023 09:23:10 +0200 Subject: [PATCH 01/69] Drafting --- core/Cargo.toml | 3 + core/src/ledger/governance/parameters.rs | 2 +- core/src/ledger/ibc/actions.rs | 6 +- core/src/ledger/parameters/mod.rs | 2 +- core/src/ledger/storage/masp_conversions.rs | 2 +- core/src/proto/types.rs | 2 +- core/src/types/governance.rs | 6 +- core/src/types/mod.rs | 1 + core/src/types/token.rs | 352 +++++++++++++------- core/src/types/uint.rs | 119 +++++++ 10 files changed, 364 insertions(+), 131 deletions(-) create mode 100644 core/src/types/uint.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 2519cafa87..32ab5a970d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -68,6 +68,7 @@ chrono = {version = "0.4.22", default-features = false, features = ["clock", "st data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" +ethabi = "18.0.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} @@ -77,6 +78,7 @@ ibc-proto = {version = "0.17.1", default-features = false, optional = true} ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ics23 = "0.7.0" +impl-num-traits = "0.1.2" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} @@ -99,6 +101,7 @@ tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.c thiserror = "1.0.30" tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} +uint = "0.9.5" [dev-dependencies] namada_tests = {path = "../tests", default-features = false, features = ["wasm-runtime"]} diff --git a/core/src/ledger/governance/parameters.rs b/core/src/ledger/governance/parameters.rs index 9ae820d96c..2d247bc24f 100644 --- a/core/src/ledger/governance/parameters.rs +++ b/core/src/ledger/governance/parameters.rs @@ -79,7 +79,7 @@ impl GovParams { } = self; let min_proposal_fund_key = gov_storage::get_min_proposal_fund_key(); - let amount = Amount::whole(*min_proposal_fund); + let amount = Amount::native_whole(*min_proposal_fund); storage.write(&min_proposal_fund_key, amount)?; let max_proposal_code_size_key = diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs index 4e09f269c2..e925a98d92 100644 --- a/core/src/ledger/ibc/actions.rs +++ b/core/src/ledger/ibc/actions.rs @@ -956,7 +956,7 @@ pub trait IbcActions { data.denom = denom.to_string(); } let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { Error::SendingToken(format!( "Invalid amount: amount {}, error {}", data.amount, e @@ -1037,7 +1037,7 @@ pub trait IbcActions { data: &FungibleTokenPacketData, ) -> std::result::Result<(), Self::Error> { let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { Error::ReceivingToken(format!( "Invalid amount: amount {}, error {}", data.amount, e @@ -1122,7 +1122,7 @@ pub trait IbcActions { data: &FungibleTokenPacketData, ) -> std::result::Result<(), Self::Error> { let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { Error::ReceivingToken(format!( "Invalid amount: amount {}, error {}", data.amount, e diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index 442a614d68..6b1ff7adb4 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -187,7 +187,7 @@ impl Parameters { { let wrapper_tx_fees_key = storage::get_wrapper_tx_fees_key(); let wrapper_tx_fees = - wrapper_tx_fees.unwrap_or(token::Amount::whole(100)); + wrapper_tx_fees.unwrap_or(token::Amount::native_whole(100)); storage.write(&wrapper_tx_fees_key, wrapper_tx_fees)?; } Ok(()) diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 0834d7bb53..7b5e788801 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -49,7 +49,7 @@ where let masp_rewards = address::masp_rewards(); // The total transparent value of the rewards being distributed - let mut total_reward = token::Amount::from(0); + let mut total_reward = token::Amount::native_whole(0); // Construct MASP asset type for rewards. Always timestamp reward tokens // with the zeroth epoch to minimize the number of convert notes clients diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 40e343d1bf..cd5f73dc2e 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -321,7 +321,7 @@ impl From for ResponseDeliverTx { EventAttribute { key: encode_str("amount"), value: encode_string( - transfer.amount.to_string(), + transfer.amount.to_string_precise(), ), index: true, }, diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 438017a370..d58f4b489d 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -14,7 +14,7 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; -use crate::types::token::SCALE; +use crate::types::token::NATIVE_SCALE; /// Type alias for vote power pub type VotePower = u128; @@ -111,8 +111,8 @@ impl Display for ProposalResult { f, "{} with {} yay votes over {} ({:.2}%)", self.result, - self.total_yay_power / SCALE as u128, - self.total_voting_power / SCALE as u128, + self.total_yay_power / NATIVE_SCALE as u128, + self.total_voting_power / NATIVE_SCALE as u128, percentage.checked_mul(100.into()).unwrap_or_default() ) } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 0550060498..b414efc89a 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -12,4 +12,5 @@ pub mod storage; pub mod time; pub mod token; pub mod transaction; +pub mod uint; pub mod validity_predicate; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index d95d944b8c..4ef2f3677b 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,17 +1,18 @@ //! A basic fungible token -use std::fmt::Display; +//use std::fmt::Display; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use masp_primitives::transaction::Transaction; -use rust_decimal::prelude::{Decimal, ToPrimitive}; +use rust_decimal::prelude::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::types::address::{masp, Address, DecodeError as AddressError}; use crate::types::storage::{DbKeySeg, Key, KeySeg}; +use crate::types::uint::{self, SignedUint, Uint}; /// Amount in micro units. For different granularity another representation /// might be more appropriate. @@ -30,77 +31,219 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; Hash, )] pub struct Amount { - micro: u64, + raw: Uint, } -/// Maximum decimal places in a token [`Amount`] and [`Change`]. -pub const MAX_DECIMAL_PLACES: u32 = 6; -/// Decimal scale of token [`Amount`] and [`Change`]. -pub const SCALE: u64 = 1_000_000; +/// A number of decimal places for a token [`Amount`]. +pub type Denom = u8; -/// The largest value that can be represented by this integer type -pub const MAX_AMOUNT: Amount = Amount { micro: u64::MAX }; +/// Maximum decimal places in a native token [`Amount`] and [`Change`]. +/// For non-native (e.g. ERC20 tokens) one must read the `denom_key` storage +/// key. +pub const NATIVE_MAX_DECIMAL_PLACES: u8 = 6; -/// A change in tokens amount -pub type Change = i128; +/// Decimal scale of a native token [`Amount`] and [`Change`]. +/// For non-native (e.g. ERC20 tokens) one must read the `denom_key` storage +/// key. +pub const NATIVE_SCALE: u64 = 1_000_000; + +pub type Change = SignedUint; impl Amount { /// Get the amount as a [`Change`] pub fn change(&self) -> Change { - self.micro as Change + self.raw.try_into().unwrap() } /// Spend a given amount. - /// Panics when given `amount` > `self.micro` amount. + /// Panics when given `amount` > `self.raw` amount. pub fn spend(&mut self, amount: &Amount) { - self.micro = self.micro.checked_sub(amount.micro).unwrap(); + self.raw = self.raw.checked_sub(amount.raw).unwrap(); } /// Receive a given amount. - /// Panics on overflow. + /// Panics on overflow and when [`uint::MAX_VALUE`] is exceeded. pub fn receive(&mut self, amount: &Amount) { - self.micro = self.micro.checked_add(amount.micro).unwrap(); + self.raw = self.raw.checked_add(amount.raw).unwrap(); } - /// Create a new amount from whole number of tokens - pub const fn whole(amount: u64) -> Self { + /// Create a new amount of native token from whole number of tokens + pub fn native_whole(amount: u64) -> Self { Self { - micro: amount * SCALE, + raw: Uint::from(amount) * NATIVE_SCALE, } } /// Create a new amount with the maximum value pub fn max() -> Self { - Self { micro: u64::MAX } + Self { + raw: uint::MAX_VALUE, + } } - /// Checked addition. Returns `None` on overflow. + /// Checked addition. Returns `None` on overflow or if + /// the amount exceed [`uint::MAX_VALUE`] pub fn checked_add(&self, amount: Amount) -> Option { - self.micro - .checked_add(amount.micro) - .map(|result| Self { micro: result }) + self.raw.checked_add(amount.raw).and_then(|result| { + if result < uint::MAX_VALUE { + Some(Self { raw: result }) + } else { + None + } + }) } /// Checked subtraction. Returns `None` on underflow pub fn checked_sub(&self, amount: Amount) -> Option { - self.micro - .checked_sub(amount.micro) - .map(|result| Self { micro: result }) + self.raw + .checked_sub(amount.raw) + .map(|result| Self { raw: result }) } - /// Create amount from Change - /// - /// # Panics - /// - /// Panics if the change is negative or overflows `u64`. + /// Create amount from the absolute value of `Change`. pub fn from_change(change: Change) -> Self { - Self { - micro: change as u64, + Self { raw: change.abs() } + } + + /// Attempt to convert a `Decimal` to an `DenominatedAmount` with the + /// specified precision. + pub fn from_decimal( + decimal: Decimal, + denom: impl Into, + ) -> Result { + let denom = denom.into(); + if (denom as u32) < decimal.scale() { + Err(AmountParseError::ScaleTooLarge(decimal.scale(), denom)) + } else { + let value = Uint::from(decimal.mantissa().unsigned_abs()); + match Uint::from(10) + .checked_pow(Uint::from((denom as u32) - decimal.scale())) + .and_then(|scaling| scaling.checked_mul(value)) + { + Some(amount) => Ok(Self { raw: amount }), + None => Err(AmountParseError::ConvertToDecimal), + } + } + } + + /// Given a string and a denomination, parse an amount from string. + pub fn from_str( + string: impl AsRef, + denom: impl Into, + ) -> Result { + match Decimal::from_str(string.as_ref()) { + Ok(dec) => Ok(Self::from_decimal(dec, denom)?), + Err(err) => Err(AmountParseError::InvalidDecimal(err)), + } + } + + /// Attempt to convert a float to an `Erc20Amount` with the specified + /// precision. + pub fn from_float( + float: impl Into, + denom: impl Into, + ) -> Result { + match Decimal::try_from(float.into()) { + Err(e) => Err(AmountParseError::InvalidDecimal(e)), + Ok(decimal) => Self::from_decimal(decimal, denom), + } + } + + /// Attempt to convert an unsigned interger to an `Erc20Amount` with the + /// specified precision. + pub fn from_int( + uint: impl Into, + denom: impl Into, + ) -> Result { + Self::from_decimal(Decimal::try_from(uint.into()).unwrap(), denom) + } +} + +/// The number of decimal places in base 10 of an amount. +#[derive( + Debug, + Copy, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct Denomination(pub u8); + +impl From for Denomination { + fn from(denom: u8) -> Self { + Self(denom) + } +} + +impl From for u8 { + fn from(denom: Denomination) -> Self { + denom.0 + } +} + +/// An amount with its denomination. +#[derive( + Debug, + Copy, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct DenominatedAmount { + /// The mantissa + pub amount: Amount, + /// The number of decimal plces in base ten. + pub denom: Denomination, +} + +impl DenominatedAmount { + /// A precise string representation. The number of + /// decimal places in this string gives the denomination. + /// This not true of the string produced by the `Display` + /// trait. + pub fn to_string_precise(&self) -> String { + let decimals = self.denom.0 as usize; + let mut string = self.amount.raw.to_string(); + if string.len() > decimals { + string.insert(string.len() - decimals, '.'); + } else { + for _ in string.len()..decimals { + string.insert(0, '0'); + } + string.insert(0, '.'); + string.insert(0, '0'); } + string + } +} + +impl FromStr for DenominatedAmount { + type Err = AmountParseError; + + fn from_str(s: &str) -> Result { + let decimal = Decimal::from_str(s) + .or_else(|err| Err(AmountParseError::InvalidDecimal(err)))?; + let denom = Denomination(decimal.scale() as u8); + Ok(Self { + amount: Amount::from_decimal(decimal, denom)?, + denom, + }) } } -impl serde::Serialize for Amount { +impl serde::Serialize for DenominatedAmount { fn serialize( &self, serializer: S, @@ -108,12 +251,12 @@ impl serde::Serialize for Amount { where S: serde::Serializer, { - let amount_string = self.to_string(); + let amount_string = self.to_string_precise(); serde::Serialize::serialize(&amount_string, serializer) } } -impl<'de> serde::Deserialize<'de> for Amount { +impl<'de> serde::Deserialize<'de> for DenominatedAmount { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, @@ -125,34 +268,22 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for Decimal { - fn from(amount: Amount) -> Self { - Into::::into(amount.micro) / Into::::into(SCALE) - } -} - -impl From for Amount { - fn from(micro: Decimal) -> Self { - let res = (micro * Into::::into(SCALE)).to_u64().unwrap(); - Self { micro: res } - } -} +impl TryFrom for Decimal { + type Error = AmountParseError; -impl From for Amount { - fn from(micro: u64) -> Self { - Self { micro } - } -} - -impl From for u64 { - fn from(amount: Amount) -> Self { - amount.micro + fn try_from(amount: Amount) -> Result { + if amount.raw > Uint([u64::MAX, u64::MAX, 0, 0]) { + Err(AmountParseError::ConvertToDecimal) + } else { + Ok(Into::::into(amount.raw.as_u128()) + / Into::::into(NATIVE_SCALE)) + } } } impl From for u128 { fn from(amount: Amount) -> Self { - u128::from(amount.micro) + amount.raw.as_u128() } } @@ -160,7 +291,7 @@ impl Add for Amount { type Output = Amount; fn add(mut self, rhs: Self) -> Self::Output { - self.micro += rhs.micro; + self.raw += rhs.raw; self } } @@ -169,35 +300,28 @@ impl Mul for Amount { type Output = Amount; fn mul(mut self, rhs: u64) -> Self::Output { - self.micro *= rhs; + self.raw *= rhs; self } } /// A combination of Euclidean division and fractions: -/// x*(a,b) = (a*(x//b), x%b) +/// x*(a,b) = (a*(x//b), x%b). impl Mul<(u64, u64)> for Amount { type Output = (Amount, Amount); fn mul(mut self, rhs: (u64, u64)) -> Self::Output { - let ant = Amount::from((self.micro / rhs.1) * rhs.0); - self.micro %= rhs.1; - (ant, self) - } -} - -impl Mul for u64 { - type Output = Amount; - - fn mul(mut self, rhs: Amount) -> Self::Output { - self *= rhs.micro; - Self::Output::from(self) + let amt = Amount { + raw: (self.raw / rhs.1) * rhs.0, + }; + self.raw %= rhs.1; + (amt, self) } } impl AddAssign for Amount { fn add_assign(&mut self, rhs: Self) { - self.micro += rhs.micro + self.raw += rhs.raw } } @@ -205,14 +329,14 @@ impl Sub for Amount { type Output = Amount; fn sub(mut self, rhs: Self) -> Self::Output { - self.micro -= rhs.micro; + self.raw -= rhs.raw; self } } impl SubAssign for Amount { fn sub_assign(&mut self, rhs: Self) { - self.micro -= rhs.micro + self.raw -= rhs.raw } } @@ -221,16 +345,17 @@ impl KeySeg for Amount { where Self: Sized, { - let micro = u64::parse(string)?; - Ok(Self { micro }) + let raw = Uint::from_str(&string) + .map_err(|e| super::storage::Error::InvalidKeySeg(e.to_string()))?; + Ok(Self { raw }) } fn raw(&self) -> String { - self.micro.raw() + self.raw.to_string() } fn to_db_key(&self) -> DbKeySeg { - self.micro.to_db_key() + DbKeySeg::StringSeg(self.raw()) } } @@ -241,49 +366,34 @@ pub enum AmountParseError { InvalidDecimal(rust_decimal::Error), #[error( "Error decoding token amount, too many decimal places: {0}. Maximum \ - {MAX_DECIMAL_PLACES}" + {1}" + )] + ScaleTooLarge(u32, u8), + #[error( + "Error decoding token amount, the value is not within invalid range." )] - ScaleTooLarge(u32), - #[error("Error decoding token amount, the value is within invalid range.")] InvalidRange, + #[error("Error converting amount to decimal, number too large.")] + ConvertToDecimal, } -impl FromStr for Amount { - type Err = AmountParseError; +// impl Display for DenominatedAmount { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// let string = self.to_string_precise(); +// let string = string.trim_end_matches(&['0', '.']); +// f.write_str(string) +// } +// } - fn from_str(s: &str) -> Result { - match rust_decimal::Decimal::from_str(s) { - Ok(decimal) => { - let scale = decimal.scale(); - if scale > MAX_DECIMAL_PLACES { - return Err(AmountParseError::ScaleTooLarge(scale)); - } - let whole = - decimal * rust_decimal::Decimal::new(SCALE as i64, 0); - let micro: u64 = - rust_decimal::prelude::ToPrimitive::to_u64(&whole) - .ok_or(AmountParseError::InvalidRange)?; - Ok(Self { micro }) - } - Err(err) => Err(AmountParseError::InvalidDecimal(err)), - } - } -} - -impl Display for Amount { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let decimal = rust_decimal::Decimal::from_i128_with_scale( - self.micro as i128, - MAX_DECIMAL_PLACES, - ) - .normalize(); - write!(f, "{}", decimal) +impl From for Change { + fn from(amount: Amount) -> Self { + amount.raw.try_into().unwrap() } } -impl From for Change { - fn from(amount: Amount) -> Self { - amount.micro as i128 +impl From for Amount { + fn from(amt: DenominatedAmount) -> Self { + amt.amount } } @@ -439,7 +549,7 @@ pub struct Transfer { /// Source token's sub prefix pub sub_prefix: Option, /// The amount of tokens - pub amount: Amount, + pub amount: DenominatedAmount, /// The unused storage location at which to place TxId pub key: Option, /// Shielded transaction part @@ -473,7 +583,7 @@ impl TryFrom for Transfer { let token = Address::decode(token_str).map_err(TransferError::Address)?; let amount = - Amount::from_str(&data.amount).map_err(TransferError::Amount)?; + DenominatedAmount::from_str(&data.amount).map_err(TransferError::Amount)?; Ok(Self { source, target, @@ -561,12 +671,12 @@ pub mod testing { /// Generate an arbitrary token amount pub fn arb_amount() -> impl Strategy { - any::().prop_map(Amount::from) + any::().prop_map(Amount::native_whole) } /// Generate an arbitrary token amount up to and including given `max` value pub fn arb_amount_ceiled(max: u64) -> impl Strategy { - (0..=max).prop_map(Amount::from) + (0..=max).prop_map(Amount::native_whole) } /// Generate an arbitrary non-zero token amount up to and including given @@ -574,6 +684,6 @@ pub mod testing { pub fn arb_amount_non_zero_ceiled( max: u64, ) -> impl Strategy { - (1..=max).prop_map(Amount::from) + (1..=max).prop_map(Amount::native_whole) } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs new file mode 100644 index 0000000000..0fbcc42061 --- /dev/null +++ b/core/src/types/uint.rs @@ -0,0 +1,119 @@ +#![allow(clippy::assign_op_pattern)] +//! An unsigned 256 integer type. Used for, among other things, +//! the backing type of token amounts. +use std::cmp::Ordering; +use std::ops::{BitXor, Neg}; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use impl_num_traits::impl_uint_num_traits; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use uint::construct_uint; + +construct_uint! { + /// Namada native type to replace for unsigned 256 bit + /// integers. + #[derive( + Serialize, + Deserialize, + BorshSerialize, + BorshDeserialize, + BorshSchema, + )] + + pub struct Uint(4); +} + +impl_uint_num_traits!(Uint, 4); + +impl Uint { + /// Compute the two's complement of a number. + fn negate(&self) -> Option { + Self( + self.0 + .into_iter() + .map(|byte| byte.bitxor(u64::MAX)) + .try_collect() + .expect("This cannot fail"), + ) + .checked_add(Uint::from(1u64)) + } +} + +/// The maximum absolute value a [`SignedUint`] may have. +/// Note the the last digit is 2^63 - 1. We add this cap so +/// we can use two's complement. +pub const MAX_VALUE: Uint = + Uint([u64::MAX, u64::MAX, u64::MAX, 9223372036854775807]); + +/// A signed 256 big integer. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct SignedUint(Uint); + +impl SignedUint { + /// Check if the amount is not negative (greater + /// than or equal to zero) + pub fn non_negative(&self) -> bool { + self.0.0[3].leading_zeros() > 0 + } + + /// Get the absolute value + pub fn abs(&self) -> Uint { + if self.non_negative() { + self.0 + } else { + self.0.negate().unwrap() + } + } +} + +impl TryFrom for SignedUint { + type Error = Box; + + fn try_from(value: Uint) -> Result { + if value.0 <= MAX_VALUE.0 { + Ok(Self(value)) + } else { + Err("The given integer is too large to be represented asa \ + SignedUint" + .into()) + } + } +} + +impl Neg for SignedUint { + type Output = Self; + + fn neg(self) -> Self::Output { + Self( + self.0 + .into_iter() + .map(|byte| byte.bitxor(u64::MAX)) + .try_collect() + .expect("This cannot fail") + .0 + .checked_add(Uint::from(1u64)) + .unwrap(), + ) + } +} + +impl PartialOrd for SignedUint { + fn partial_cmp(&self, other: &Self) -> Option { + match (self.non_negative(), other.non_negative()) { + (true, false) => Some(Ordering::Greater), + (false, true) => Some(Ordering::Less), + _ => { + let this = self.abs(); + let that = other.abs(); + this.0.partial_cmp(&that.0) + } + } + } +} + +impl Ord for SignedUint { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} From 3aa2856a7027208282025764177744e4e70aec7e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 29 Mar 2023 10:03:23 +0200 Subject: [PATCH 02/69] Fixed errors in core --- core/src/types/token.rs | 34 +++++++++++++++++++++++---- core/src/types/transaction/wrapper.rs | 11 ++++----- core/src/types/uint.rs | 15 +++--------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 4ef2f3677b..a91b49af5c 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -47,6 +47,7 @@ pub const NATIVE_MAX_DECIMAL_PLACES: u8 = 6; /// key. pub const NATIVE_SCALE: u64 = 1_000_000; +/// A change in tokens amount pub type Change = SignedUint; impl Amount { @@ -137,7 +138,7 @@ impl Amount { } } - /// Attempt to convert a float to an `Erc20Amount` with the specified + /// Attempt to convert a float to an `Amount` with the specified /// precision. pub fn from_float( float: impl Into, @@ -149,7 +150,7 @@ impl Amount { } } - /// Attempt to convert an unsigned interger to an `Erc20Amount` with the + /// Attempt to convert an unsigned interger to an `Amount` with the /// specified precision. pub fn from_int( uint: impl Into, @@ -204,7 +205,7 @@ impl From for u8 { pub struct DenominatedAmount { /// The mantissa pub amount: Amount, - /// The number of decimal plces in base ten. + /// The number of decimal places in base ten. pub denom: Denomination, } @@ -234,7 +235,7 @@ impl FromStr for DenominatedAmount { fn from_str(s: &str) -> Result { let decimal = Decimal::from_str(s) - .or_else(|err| Err(AmountParseError::InvalidDecimal(err)))?; + .map_err(AmountParseError::InvalidDecimal)?; let denom = Denomination(decimal.scale() as u8); Ok(Self { amount: Amount::from_decimal(decimal, denom)?, @@ -243,6 +244,31 @@ impl FromStr for DenominatedAmount { } } +impl serde::Serialize for Amount { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let amount_string = self.raw.to_string(); + serde::Serialize::serialize(&amount_string, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Amount { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let amount_string: String = + serde::Deserialize::deserialize(deserializer)?; + Ok(Self{raw: Uint::from_str(&amount_string).map_err(D::Error::custom)?}) + } +} + impl serde::Serialize for DenominatedAmount { fn serialize( &self, diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 70ef2827bc..d3b3b5dce0 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -90,7 +90,7 @@ pub mod wrapper_tx { impl GasLimit { /// We refund unused gas up to GAS_LIMIT_RESOLUTION pub fn refund_amount(&self, used_gas: u64) -> Amount { - if used_gas < (u64::from(self) - GAS_LIMIT_RESOLUTION) { + Amount::native_whole(if used_gas < (u64::from(self) - GAS_LIMIT_RESOLUTION) { // we refund only up to GAS_LIMIT_RESOLUTION GAS_LIMIT_RESOLUTION } else if used_gas >= u64::from(self) { @@ -99,8 +99,7 @@ pub mod wrapper_tx { } else { // compute refund u64::from(self) - used_gas - } - .into() + }) } } @@ -108,8 +107,6 @@ pub mod wrapper_tx { /// of GAS_LIMIT_RESOLUTION impl From for GasLimit { fn from(amount: u64) -> GasLimit { - // we could use the ceiling function but this way avoids casts to - // floats if GAS_LIMIT_RESOLUTION * (amount / GAS_LIMIT_RESOLUTION) < amount { GasLimit { multiplier: (amount / GAS_LIMIT_RESOLUTION) + 1, @@ -126,7 +123,7 @@ pub mod wrapper_tx { /// of GAS_LIMIT_RESOLUTION impl From for GasLimit { fn from(amount: Amount) -> GasLimit { - GasLimit::from(u64::from(amount)) + GasLimit::from(u128::from(amount) as u64) } } @@ -147,7 +144,7 @@ pub mod wrapper_tx { /// Get back the gas limit as a raw number, viewed as an Amount impl From for Amount { fn from(limit: GasLimit) -> Amount { - Amount::from(limit.multiplier * GAS_LIMIT_RESOLUTION) + Amount::native_whole(limit.multiplier * GAS_LIMIT_RESOLUTION) } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 0fbcc42061..079c20bd09 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -6,7 +6,6 @@ use std::ops::{BitXor, Neg}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; -use itertools::Itertools; use serde::{Deserialize, Serialize}; use uint::construct_uint; @@ -33,7 +32,8 @@ impl Uint { self.0 .into_iter() .map(|byte| byte.bitxor(u64::MAX)) - .try_collect() + .collect::>() + .try_into() .expect("This cannot fail"), ) .checked_add(Uint::from(1u64)) @@ -85,16 +85,7 @@ impl Neg for SignedUint { type Output = Self; fn neg(self) -> Self::Output { - Self( - self.0 - .into_iter() - .map(|byte| byte.bitxor(u64::MAX)) - .try_collect() - .expect("This cannot fail") - .0 - .checked_add(Uint::from(1u64)) - .unwrap(), - ) + Self(self.0.negate().expect("This should not fail")) } } From d2d3665be3d193a7944262552a553af536fc898c Mon Sep 17 00:00:00 2001 From: satan Date: Sat, 1 Apr 2023 11:22:43 +0200 Subject: [PATCH 03/69] temp --- Cargo.lock | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 268 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfb800b157..8fb0dcf81e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,7 +513,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -637,10 +637,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -819,7 +831,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -861,6 +873,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byte-tools" version = "0.3.1" @@ -1901,6 +1919,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde 1.0.145", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -2006,7 +2068,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] @@ -2046,6 +2108,18 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2149,6 +2223,12 @@ name = "funty" version = "1.2.0" source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2890,7 +2970,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2921,6 +3001,55 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits 0.2.15", + "uint", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde 1.0.145", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2977,6 +3106,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits 0.2.15", +] + [[package]] name = "iovec" version = "0.1.4" @@ -3031,7 +3169,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -3343,7 +3481,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -3780,6 +3918,7 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", "ferveo", "ferveo-common", "group-threshold-cryptography", @@ -3788,6 +3927,7 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1", @@ -3816,6 +3956,7 @@ dependencies = [ "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", + "uint", "zeroize", ] @@ -4282,7 +4423,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -4348,6 +4489,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.145", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.45.0" @@ -4643,6 +4810,19 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -4652,6 +4832,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4850,6 +5040,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -5303,6 +5499,16 @@ dependencies = [ "libc", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes 1.2.1", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.19.0" @@ -5363,6 +5569,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -5851,6 +6063,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -6745,6 +6967,23 @@ dependencies = [ "serde 1.0.145", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.6.2" @@ -7990,6 +8229,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -8018,6 +8266,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "0.2.3" @@ -8075,7 +8332,7 @@ source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", From faedfed893336a822ab89dd935a129c719ab41e5 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Apr 2023 17:19:01 +0200 Subject: [PATCH 04/69] Some more fixes for amount types in the client code --- apps/src/lib/cli.rs | 4 +- apps/src/lib/client/rpc.rs | 112 ++++++---- apps/src/lib/client/tx.rs | 15 +- core/src/ledger/storage_api/token.rs | 13 ++ core/src/types/token.rs | 207 +++++++++++++++--- core/src/types/uint.rs | 62 +++++- proof_of_stake/src/lib.rs | 57 ++--- proof_of_stake/src/tests.rs | 2 +- proof_of_stake/src/types.rs | 30 ++- proof_of_stake/src/types/rev_order.rs | 8 +- shared/src/ledger/ibc/vp/token.rs | 13 +- .../src/ledger/native_vp/governance/utils.rs | 2 +- shared/src/ledger/pos/mod.rs | 2 +- shared/src/ledger/queries/router.rs | 10 +- shared/src/ledger/queries/shell.rs | 2 +- shared/src/ledger/queries/vp/mod.rs | 6 + shared/src/ledger/queries/vp/token.rs | 24 ++ tests/src/vm_host_env/ibc.rs | 2 +- tx_prelude/src/token.rs | 12 +- vp_prelude/src/token.rs | 11 +- 20 files changed, 437 insertions(+), 157 deletions(-) create mode 100644 shared/src/ledger/queries/vp/token.rs diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..dfa8fca8a2 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1624,9 +1624,9 @@ pub mod args { const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); + arg_default("gas-amount", DefaultFn(|| token::Amount::default())); const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); + arg_default("gas-limit", DefaultFn(|| token::Amount::default())); const GAS_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4dd36ea2eb..4bc15cb45e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -45,7 +45,7 @@ use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; -use namada::types::token::{balance_key, Transfer}; +use namada::types::token::{balance_key, DenominatedAmount, MaspDenom, Transfer}; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, @@ -239,21 +239,27 @@ pub async fn query_tx_deltas( // Describe how a Transfer simply subtracts from one // account and adds the same to another let mut delta = TransferDelta::default(); - let tfer_delta = Amount::from_nonnegative( - transfer.token.clone(), - u64::from(transfer.amount), - ) - .expect("invalid value for amount"); - delta.insert( - transfer.source, - Amount::zero() - &tfer_delta, - ); - delta.insert(transfer.target, tfer_delta); + for denom in MaspDenom::iter() { + let denominated = denom.denominate(&transfer.amount); + if denominated != 0 { + let tfer_delta = Amount::from_nonnegative( + (transfer.token.clone(), denom.into()), + denominated, + ) + .expect("invalid value for amount"); + delta.insert( + transfer.source.clone(), + Amount::zero() - &tfer_delta, + ); + delta.insert(transfer.target.clone(), tfer_delta); + } + } // No shielded accounts are affected by this Transfer transfers.insert( (height, idx), (epoch, delta, TransactionDelta::new()), ); + } } // An incomplete page signifies no more transactions @@ -322,8 +328,8 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - tfer_delta.values().any(|x| x[token] != 0) - || shielded_accounts.values().any(|x| x[token] != 0) + tfer_delta.values().zip(MaspDenom::iter()).any(|(x, denom)| x[&(token.clone(), denom)] != 0) + || shielded_accounts.values().zip(MaspDenom::iter()).any(|(x, denom)| x[&(token.clone(), denom)] != 0) } None => true, }; @@ -336,7 +342,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for (account, amt) in tfer_delta { if account != masp() { print!(" {}:", account); - for (addr, val) in amt.components() { + for ((addr, denom), val) in amt.components() { let addr_enc = addr.encode(); let readable = tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); @@ -348,7 +354,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - token::Amount::from(val.unsigned_abs()), + format_denominated_amount(&client, addr,token::Amount::from_masp_denominated(val.unsigned_abs(), *denom)).await, readable ); } @@ -360,7 +366,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for (account, amt) in shielded_accounts { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); - for (addr, val) in amt.components() { + for ((addr, denom), val) in amt.components() { let addr_enc = addr.encode(); let readable = tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); @@ -372,7 +378,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - token::Amount::from(val.unsigned_abs()), + format_denominated_amount(&client, addr, token::Amount::from_masp_denominated(val.unsigned_abs(), *denom)).await, readable ); } @@ -909,45 +915,48 @@ pub async fn query_shielded_balance( match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { - // Query the multi-asset balance at the given spending key - let viewing_key = - ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance: Amount = if no_conversions { - ctx.shielded - .compute_shielded_balance(&viewing_key) - .expect("context should contain viewing key") - } else { - ctx.shielded - .compute_exchanged_balance( - client.clone(), - &viewing_key, - epoch, - ) - .await - .expect("context should contain viewing key") - }; - // Compute the unique asset identifier from the token address + let mut total_balance = token::Amount::default(); let token = ctx.get(&token); - let asset_type = AssetType::new( - (token.clone(), epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + for denom in MaspDenom::iter() { + // Query the multi-asset balance at the given spending key + let viewing_key = + ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; + let balance: Amount = if no_conversions { + ctx.shielded + .compute_shielded_balance(&viewing_key) + .expect("context should contain viewing key") + } else { + ctx.shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key") + }; + // Compute the unique asset identifier from the token address + let asset_type = AssetType::new( + (token.clone(), denom, epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + total_balance += token::Amount::from_masp_denominated(balance[&asset_type] as u64, denom); + } + let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); - if balance[&asset_type] == 0 { + if total_balance.is_zero() { println!( "No shielded {} balance found for given key", currency_code ); } else { - let asset_value = - token::Amount::from(balance[&asset_type] as u64); - println!("{}: {}", currency_code, asset_value); + println!("{}: {}", currency_code, format_denominated_amount(&client, &token, total_balance).await); } } // Here the user wants to know the balance of all tokens across users @@ -2612,3 +2621,14 @@ fn unwrap_client_response(response: Result) -> T { cli::safe_exit(1) }) } + +/// Look up the denomination of a token in order to format it +/// correctly as a string. +async fn format_denominated_amount(client: &HttpClient, token: &Address, amount: token::Amount) -> String { + let denom = unwrap_client_response( RPC.vp().token().denomination(client, token).await) + .unwrap_or_else(|| { + println!("No denomination found for token: {token}, defaulting to zero decimal places"); + 0.into() + }); + DenominatedAmount{ amount, denom }.to_string() +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..921f0eaa1e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -50,9 +50,7 @@ use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{ - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; +use namada::types::token::{Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, MaspDenom}; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -473,11 +471,14 @@ pub enum PinnedBalanceError { pub type Conversions = HashMap, i64)>; +/// Represents an amount that is +pub type MaspDenominatedAmount = Amount<(Address, MaspDenom)>; + /// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = HashMap>; +pub type TransferDelta = HashMap; /// Represents the changes that were made to a list of shielded accounts -pub type TransactionDelta = HashMap; +pub type TransactionDelta = HashMap; /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. @@ -1003,7 +1004,7 @@ impl ShieldedContext { pub async fn compute_exchanged_amount( &mut self, client: HttpClient, - mut input: Amount, + mut input: MaspDenominatedAmount, target_epoch: Epoch, mut conversions: Conversions, ) -> (Amount, Conversions) { @@ -1252,7 +1253,7 @@ impl ShieldedContext { client: HttpClient, amt: Amount, target_epoch: Epoch, - ) -> Amount
{ + ) -> MaspDenominatedAmount { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index c1e6573a21..fe6348fbc4 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -20,6 +20,19 @@ where Ok(balance) } +/// Read the denomination of a given token, if any. +pub fn read_denom( + storage: &S, + token: &Address, +) -> storage_api::Result> + where + S: StorageRead, +{ + let key = token::denom_key(token); + let denom = storage.read::(&key)?; + Ok(denom.map(Into::into)) +} + /// Transfer `token` from `src` to `dest`. Returns an `Err` if `src` has /// insufficient balance or if the transfer the `dest` would overflow (This can /// only happen if the total supply does't fit in `token::Amount`). diff --git a/core/src/types/token.rs b/core/src/types/token.rs index a91b49af5c..c245c0b366 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,16 +1,20 @@ //! A basic fungible token -//use std::fmt::Display; +use std::fmt::Display; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::BASE32HEX_NOPAD; use masp_primitives::transaction::Transaction; use rust_decimal::prelude::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::ledger::storage_api::StorageRead; +use crate::ledger::storage_api::token::read_denom; use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::storage; use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::uint::{self, SignedUint, Uint}; @@ -34,9 +38,6 @@ pub struct Amount { raw: Uint, } -/// A number of decimal places for a token [`Amount`]. -pub type Denom = u8; - /// Maximum decimal places in a native token [`Amount`] and [`Change`]. /// For non-native (e.g. ERC20 tokens) one must read the `denom_key` storage /// key. @@ -63,7 +64,7 @@ impl Amount { } /// Receive a given amount. - /// Panics on overflow and when [`uint::MAX_VALUE`] is exceeded. + /// Panics on overflow and when [`uint::MAX_SIGNED_VALUE`] is exceeded. pub fn receive(&mut self, amount: &Amount) { self.raw = self.raw.checked_add(amount.raw).unwrap(); } @@ -82,11 +83,23 @@ impl Amount { } } + /// Create a new amount with the maximum signed value + pub fn max_signed() -> Self { + Self { + raw: uint::MAX_SIGNED_VALUE, + } + } + + /// Check if [`Amount`] is zero. + pub fn is_zero(&self) -> bool { + self.raw == Uint::from(0) + } + /// Checked addition. Returns `None` on overflow or if - /// the amount exceed [`uint::MAX_VALUE`] + /// the amount exceed [`uint::MAX_SIGNED_VALUE`] pub fn checked_add(&self, amount: Amount) -> Option { self.raw.checked_add(amount.raw).and_then(|result| { - if result < uint::MAX_VALUE { + if result < uint::MAX_SIGNED_VALUE { Some(Self { raw: result }) } else { None @@ -150,17 +163,60 @@ impl Amount { } } - /// Attempt to convert an unsigned interger to an `Amount` with the + /// Attempt to convert an unsigned integer to an `Amount` with the /// specified precision. - pub fn from_int( - uint: impl Into, + pub fn from_uint( + uint: impl Into, denom: impl Into, ) -> Result { - Self::from_decimal(Decimal::try_from(uint.into()).unwrap(), denom) + let denom = denom.into(); + match Uint::from(10) + .checked_pow(Uint::from(denom)) + .and_then(|scaling| scaling.checked_mul(uint.into())) + { + Some(amount) => Ok(Self { raw: amount }), + None => Err(AmountParseError::ConvertToDecimal), + } + } + + /// Given a u64 and [`MaspDenom`], construct the corresponding + /// amount. + pub fn from_masp_denominated(val: u64, denom: MaspDenom) -> Self { + let val = Uint::from(val); + let denom = Uint::from(denom as u64); + let scaling = Uint::from(2).pow(denom); + Self { raw: val * scaling } + } + + /// Get a string representation of a native token amount. + pub fn to_string_native(&self) -> String { + DenominatedAmount { + amount: *self, + denom: 6.into(), + }.to_string_precise() + } + + /// Add denomination info if it exists in storage. + pub fn denominated(&self, token: &Address, storage: &impl StorageRead) -> Option { + let denom = read_denom(storage, token).expect("Should be able to read storage"); + denom.map(|denom| + DenominatedAmount { + amount: *self, + denom, + }) + } + + /// Convert to an [`Amount`] under the assumption that the input + /// string encodes all necessary decimal places. + pub fn from_string_precise(string: &str) -> Result { + DenominatedAmount::from_str(string).map(|den| den.amount) } + } -/// The number of decimal places in base 10 of an amount. +/// Given a number represented as `M*B^D`, then +/// `M` is the matissa, `B` is the base and `D` +/// is the denomination, represented by this stuct. #[derive( Debug, Copy, @@ -230,6 +286,14 @@ impl DenominatedAmount { } } +impl Display for DenominatedAmount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = self.to_string_precise(); + let string = string.trim_end_matches(&['0', '.']); + f.write_str(string) + } +} + impl FromStr for DenominatedAmount { type Err = AmountParseError; @@ -307,6 +371,18 @@ impl TryFrom for Decimal { } } +impl<'a> From<&'a DenominatedAmount> for &'a Amount { + fn from(denom: &'a DenominatedAmount) -> Self { + &denom.amount + } +} + +impl From for Amount { + fn from(denom: DenominatedAmount) -> Self { + denom.amount + } +} + impl From for u128 { fn from(amount: Amount) -> Self { amount.raw.as_u128() @@ -331,6 +407,24 @@ impl Mul for Amount { } } +impl Mul for Amount { + type Output = Amount; + + fn mul(mut self, rhs: Uint) -> Self::Output { + self.raw *= rhs; + self + } +} + +impl Mul for Amount { + type Output = Amount; + + fn mul(mut self, rhs: Amount) -> Self::Output { + self.raw *= rhs.raw; + self + } +} + /// A combination of Euclidean division and fractions: /// x*(a,b) = (a*(x//b), x%b). impl Mul<(u64, u64)> for Amount { @@ -371,13 +465,19 @@ impl KeySeg for Amount { where Self: Sized, { - let raw = Uint::from_str(&string) - .map_err(|e| super::storage::Error::InvalidKeySeg(e.to_string()))?; - Ok(Self { raw }) + let bytes = BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { + storage::Error::ParseKeySeg(format!( + "Failed parsing {} with {}", + string, err + )) + })?; + Ok(Amount{ raw: Uint::from_big_endian(&bytes)}) } fn raw(&self) -> String { - self.raw.to_string() + let mut buf = [0u8; 32]; + self.raw.to_big_endian(&mut buf); + BASE32HEX_NOPAD.encode(&buf) } fn to_db_key(&self) -> DbKeySeg { @@ -401,31 +501,64 @@ pub enum AmountParseError { InvalidRange, #[error("Error converting amount to decimal, number too large.")] ConvertToDecimal, + #[error("Could not convert from string, expected an unsigned 256-bit integer.")] + FromString, } -// impl Display for DenominatedAmount { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// let string = self.to_string_precise(); -// let string = string.trim_end_matches(&['0', '.']); -// f.write_str(string) -// } -// } - impl From for Change { fn from(amount: Amount) -> Self { amount.raw.try_into().unwrap() } } -impl From for Amount { - fn from(amt: DenominatedAmount) -> Self { - amt.amount +impl From for Amount { + fn from(change: Change) -> Self { + Amount{raw: change.abs()} + } +} + +/// The four possible u64 words in a [`Uint`]. +/// Used for converting to MASP amounts. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum MaspDenom { + Zero = 0, + One, + Two, + Three, +} + +impl From for MaspDenom { + fn from(denom: u8) -> Self { + match denom { + 0 => Self::Zero, + 1 => Self::One, + 2 => Self::Two, + 3 => Self::Three, + _ => panic!("Possible MASP denominations must be between 0 and 3"), + } + } +} + +impl MaspDenom { + /// Iterator over the possible denominations + pub fn iter() -> impl Iterator{ + (0u8..3).into_iter().map(Self::from) + } + + /// Get the corresponding u64 word from the input uint256. + pub fn denominate<'a>(&self, amount: impl Into<&'a Amount>) -> u64 { + let amount = amount.into(); + amount.raw.0[*self as usize] } } /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; -/// Key segment for head shielded transaction pointer key +/// Key segment for a denomination key +pub const DENOM_STORAGE_KEY: &str = "balance"; +/// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key pub const TX_KEY_PREFIX: &str = "tx-"; @@ -496,6 +629,22 @@ pub fn is_any_token_balance_key(key: &Key) -> Option<&Address> { } } +/// Obtain a storage key denomination of a token. +pub fn denom_key(token_addr: &Address) -> Key { + Key::from(token_addr.to_db_key()) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Check if the given storage key is a denomination key for the given token. +pub fn is_denom_key(token_addr: &Address, key: &Key) -> bool { + matches!(&key.segments[..], + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + ] if key == DENOM_STORAGE_KEY && addr == token_addr) +} + /// Check if the given storage key is a masp key pub fn is_masp_key(key: &Key) -> bool { matches!(&key.segments[..], @@ -646,7 +795,7 @@ mod tests { let max = Amount::from(u64::MAX); assert_eq!("18446744073709.551615", max.to_string()); - let whole = Amount::from(u64::MAX / SCALE * SCALE); + let whole = Amount::from(u64::MAX / NATIVE_SCALE * NATIVE_SCALE); assert_eq!("18446744073709", whole.to_string()); let trailing_zeroes = Amount::from(123000); diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 079c20bd09..01d315829c 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -2,12 +2,13 @@ //! An unsigned 256 integer type. Used for, among other things, //! the backing type of token amounts. use std::cmp::Ordering; -use std::ops::{BitXor, Neg}; +use std::ops::{Add, AddAssign, BitXor, Neg, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; use serde::{Deserialize, Serialize}; use uint::construct_uint; +use crate::types::token; construct_uint! { /// Namada native type to replace for unsigned 256 bit @@ -25,6 +26,9 @@ construct_uint! { impl_uint_num_traits!(Uint, 4); +/// The maximum 256 bit integer +pub const MAX_VALUE: Uint = Uint([u64::MAX; 4]); + impl Uint { /// Compute the two's complement of a number. fn negate(&self) -> Option { @@ -43,11 +47,11 @@ impl Uint { /// The maximum absolute value a [`SignedUint`] may have. /// Note the the last digit is 2^63 - 1. We add this cap so /// we can use two's complement. -pub const MAX_VALUE: Uint = +pub const MAX_SIGNED_VALUE: Uint = Uint([u64::MAX, u64::MAX, u64::MAX, 9223372036854775807]); /// A signed 256 big integer. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct SignedUint(Uint); impl SignedUint { @@ -65,13 +69,36 @@ impl SignedUint { self.0.negate().unwrap() } } + + /// Check if this value is zero + pub fn is_zero(&self) -> bool { + self.0 == Uint::zero() + } + + /// Get a string representation of `self` as a + /// native token amount. + pub fn to_string_native(&self) -> String { + let mut sign = if self.non_negative() { + String::from("-") + } else { + String::new() + }; + sign.push_str(&token::Amount::from(*self).to_string_native()); + sign + } +} + +impl From for SignedUint { + fn from(val: u64) -> Self { + SignedUint::try_from(Uint::from(val)).expect("A u64 will always fit in this type") + } } impl TryFrom for SignedUint { type Error = Box; fn try_from(value: Uint) -> Result { - if value.0 <= MAX_VALUE.0 { + if value.0 <= MAX_SIGNED_VALUE.0 { Ok(Self(value)) } else { Err("The given integer is too large to be represented asa \ @@ -108,3 +135,30 @@ impl Ord for SignedUint { self.partial_cmp(other).unwrap() } } + +impl Add for SignedUint { + type Output = Self; + + fn add(self, rhs: SignedUint) -> Self::Output { + match (self.non_negative(), rhs.non_negative()) { + (true, true) => Self(self.0 + rhs.0), + (false, false) => -Self(self.abs() + rhs.abs()), + (true, false) => Self(self.0 - rhs.abs()), + (false, true) => Self(rhs.0 - self.abs()), + } + } +} + +impl AddAssign for SignedUint { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sub for SignedUint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + self + (-rhs) + } +} \ No newline at end of file diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 315de87c6d..c65a539e72 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -19,8 +19,8 @@ pub mod storage; pub mod types; // pub mod validation; -#[cfg(test)] -mod tests; +//#[cfg(test)] +//mod tests; use core::fmt::Debug; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -56,6 +56,7 @@ use storage::{ ReverseOrdTokenAmount, UnbondDetails, WeightedValidator, }; use thiserror::Error; +use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use types::{ BelowCapacityValidatorSet, BelowCapacityValidatorSets, Bonds, CommissionRates, ConsensusValidator, ConsensusValidatorSet, @@ -65,7 +66,7 @@ use types::{ ValidatorState, ValidatorStates, }; -use crate::types::{decimal_mult_i128, decimal_mult_u64, BondId}; +use crate::types::{decimal_mult_u128, BondId}; /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -117,7 +118,7 @@ pub enum UnbondError { #[error( "Trying to withdraw more tokens ({0}) than the amount bonded ({0})" )] - UnbondAmountGreaterThanBond(token::Amount, token::Amount), + UnbondAmountGreaterThanBond(String, String), #[error("No bonds found for the validator {0}")] ValidatorHasNoBonds(Address), #[error("Voting power not found for the validator {0}")] @@ -728,7 +729,7 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Bonding token amount {amount} at epoch {current_epoch}"); + tracing::debug!("Bonding token amount {} at epoch {current_epoch}", amount.to_string_native()); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; if let Some(source) = source { @@ -771,7 +772,7 @@ where tracing::debug!( "Bond remain at offset epoch {}: {}", current_epoch + offset, - cur_remain + token::Amount::from(cur_remain).to_string_native() ); bond_handle.set(storage, cur_remain + amount, current_epoch, offset)?; @@ -911,7 +912,7 @@ fn update_validator_set( where S: StorageRead + StorageWrite, { - if token_change == 0_i128 { + if token_change.is_zero() { return Ok(()); } let epoch = current_epoch + params.pipeline_len; @@ -1328,13 +1329,14 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Unbonding token amount {amount} at epoch {current_epoch}"); + tracing::debug!("Unbonding token amount {} at epoch {current_epoch}", amount.to_string_native()); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; tracing::debug!( "Current validator stake at pipeline: {}", read_validator_stake(storage, ¶ms, validator, pipeline_epoch)? .unwrap_or_default() + .to_string_native() ); if let Some(source) = source { @@ -1372,8 +1374,8 @@ where .unwrap_or_default(); if amount > remaining_at_pipeline { return Err(UnbondError::UnbondAmountGreaterThanBond( - token::Amount::from_change(amount), - token::Amount::from_change(remaining_at_pipeline), + token::Amount::from_change(amount).to_string_native(), + token::Amount::from_change(remaining_at_pipeline).to_string_native(), ) .into()); } @@ -1589,7 +1591,7 @@ where ) = unbond?; tracing::debug!( - "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {amount}", + "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", amount.to_string_native() ); // TODO: worry about updating this later after PR 740 perhaps @@ -1613,10 +1615,10 @@ where .unwrap_or_default() { let slash_rate = slash_type.get_slash_rate(¶ms); - let to_slash = token::Amount::from(decimal_mult_u64( + let to_slash = token::Amount::from_uint(decimal_mult_u128( slash_rate, - u64::from(amount), - )); + u128::from(amount), + ), NATIVE_MAX_DECIMAL_PLACES).expect("Amount out of bounds"); slashed += to_slash; } } @@ -1624,7 +1626,7 @@ where unbonds_to_remove.push((withdraw_epoch, start_epoch)); } withdrawable_amount -= slashed; - tracing::debug!("Withdrawing total {withdrawable_amount}"); + tracing::debug!("Withdrawing total {}", withdrawable_amount.to_string_native()); // Remove the unbond data from storage for (withdraw_epoch, start_epoch) in unbonds_to_remove { @@ -1723,7 +1725,10 @@ where let current_stake = read_validator_stake(storage, params, validator, current_epoch)? .unwrap_or_default(); - let slashed_amount = decimal_mult_u64(rate, u64::from(current_stake)); + let slashed_amount = Amount::from_uint( + decimal_mult_u128(rate, u128::from(current_stake)), + NATIVE_MAX_DECIMAL_PLACES + ).expect("Amount out of bounds"); let token_change = -token::Change::from(slashed_amount); // Update validator sets and deltas at the pipeline length @@ -1750,7 +1755,7 @@ where transfer_tokens( storage, &staking_token_address(), - token::Amount::from(slashed_amount), + slashed_amount, &ADDRESS, &SLASH_POOL_ADDRESS, )?; @@ -1779,8 +1784,8 @@ where tracing::error!( "PoS system transfer error, the source doesn't have \ sufficient balance. It has {}, but {} is required", - src_balance, - amount + src_balance.to_string_native(), + amount.to_string_native(), ); } src_balance.spend(&amount); @@ -1864,7 +1869,7 @@ where continue; } let current_slashed = - decimal_mult_i128(slash_type.get_slash_rate(params), delta); + mult_change_to_amount(slash_type.get_slash_rate(params), delta).change(); let delta = token::Amount::from_change(delta - current_slashed); total += delta; if bond_epoch <= epoch { @@ -1912,7 +1917,7 @@ where ) = validator.unwrap(); tracing::debug!( - "Consensus validator address {address}, stake {cur_stake}" + "Consensus validator address {address}, stake {}", cur_stake.to_string_native() ); // Check if the validator was consensus in the previous epoch with @@ -1932,13 +1937,13 @@ where .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, - prev_validator_stake, + u128::from(prev_validator_stake) as u64, ) }); let cur_tm_voting_power = Lazy::new(|| { into_tm_voting_power( params.tm_votes_per_token, - cur_stake, + u128::from(cur_stake) as u64, ) }); @@ -1975,7 +1980,7 @@ where ); Some(ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key, - bonded_stake: cur_stake.into(), + bonded_stake: u128::from(cur_stake) as u64, })) }); let cur_below_capacity_validators = @@ -1994,7 +1999,7 @@ where let cur_stake = token::Amount::from(cur_stake); tracing::debug!( - "Below-capacity validator address {address}, stake {cur_stake}" + "Below-capacity validator address {address}, stake {}", cur_stake.to_string_native() ); let prev_tm_voting_power = previous_epoch @@ -2007,7 +2012,7 @@ where .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, - prev_validator_stake, + u128::from(prev_validator_stake) as u64, ) }) .unwrap_or_default(); diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index bbbc9e1027..22149a191e 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -1599,7 +1599,7 @@ fn arb_genesis_validators( size: Range, ) -> impl Strategy> { let tokens: Vec<_> = (0..size.end) - .map(|_| (1..=10_u64).prop_map(token::Amount::from)) + .map(|_| (1..=10_u64).prop_map(token::Amount::native_whole)) .collect(); (size, tokens).prop_map(|(size, token_amounts)| { // use unique seeds to generate validators' address and consensus key diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index db0e697098..7a8af3eebb 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -21,6 +21,8 @@ use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; pub use rev_order::ReverseOrdTokenAmount; use rust_decimal::prelude::{Decimal, ToPrimitive}; +use rust_decimal::RoundingStrategy; +use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use crate::parameters::PosParams; @@ -240,7 +242,7 @@ impl Display for WeightedValidator { write!( f, "{} with bonded stake {}", - self.address, self.bonded_stake + self.address, self.bonded_stake.to_string_native() ) } } @@ -445,10 +447,10 @@ impl Display for SlashType { /// Multiply a value of type Decimal with one of type u64 and then return the /// truncated u64 -pub fn decimal_mult_u64(dec: Decimal, int: u64) -> u64 { +pub fn decimal_mult_u128(dec: Decimal, int: u128) -> u128 { let prod = dec * Decimal::from(int); // truncate the number to the floor - prod.to_u64().expect("Product is out of bounds") + prod.to_u128().expect("Product is out of bounds") } /// Multiply a value of type Decimal with one of type i128 and then return the @@ -459,23 +461,29 @@ pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { prod.to_i128().expect("Product is out of bounds") } -/// Multiply a value of type Decimal with one of type i128 and then convert it +/// Multiply a value of type Decimal with one of type Uint and then convert it /// to an Amount type pub fn mult_change_to_amount( dec: Decimal, change: token::Change, ) -> token::Amount { - let prod = dec * Decimal::from(change); - // truncate the number to the floor - token::Amount::from(prod.to_u64().expect("Product is out of bounds")) + // this function is used for slashing calculations. We want to err + // on the side of slashing more, not less. + let dec = dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); + Amount::from_decimal( + dec, + NATIVE_MAX_DECIMAL_PLACES + ).unwrap() * change.abs() } /// Multiply a value of type Decimal with one of type Amount and then return the /// truncated Amount pub fn mult_amount(dec: Decimal, amount: token::Amount) -> token::Amount { - let prod = dec * Decimal::from(amount); - // truncate the number to the floor - token::Amount::from(prod.to_u64().expect("Product is out of bounds")) + let dec = dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); + Amount::from_decimal( + dec, + NATIVE_MAX_DECIMAL_PLACES + ).unwrap() * amount } /// Calculate voting power in the tendermint context (which is stored as i64) @@ -484,7 +492,7 @@ pub fn into_tm_voting_power( votes_per_token: Decimal, tokens: impl Into, ) -> i64 { - let prod = decimal_mult_u64(votes_per_token, tokens.into()); + let prod = decimal_mult_u128(votes_per_token, tokens.into() as u128); i64::try_from(prod).expect("Invalid voting power") } diff --git a/proof_of_stake/src/types/rev_order.rs b/proof_of_stake/src/types/rev_order.rs index 807795e10e..19749fabd4 100644 --- a/proof_of_stake/src/types/rev_order.rs +++ b/proof_of_stake/src/types/rev_order.rs @@ -23,7 +23,7 @@ impl From for ReverseOrdTokenAmount { /// Invert the token amount fn invert(amount: token::Amount) -> token::Amount { - token::MAX_AMOUNT - amount + token::Amount::max_signed() - amount } impl KeySeg for ReverseOrdTokenAmount { @@ -46,15 +46,15 @@ impl KeySeg for ReverseOrdTokenAmount { impl std::fmt::Display for ReverseOrdTokenAmount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + f.write_str(&self.0.to_string_native()) } } impl std::str::FromStr for ReverseOrdTokenAmount { - type Err = ::Err; + type Err = token::AmountParseError; fn from_str(s: &str) -> Result { - let amount = token::Amount::from_str(s)?; + let amount = token::Amount::from_str(s, token::NATIVE_MAX_DECIMAL_PLACES)?; Ok(Self(amount)) } } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 455a0a49d5..e460301330 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -1,7 +1,6 @@ //! IBC token transfer validation as a native validity predicate use std::collections::{BTreeSet, HashMap, HashSet}; -use std::str::FromStr; use borsh::BorshDeserialize; use thiserror::Error; @@ -123,7 +122,7 @@ where changes.insert(sub_prefix, change + this_change); } } - if changes.iter().all(|(_, c)| *c == 0) { + if changes.iter().all(|(_, c)| c.is_zero()) { return Ok(true); } else { return Err(Error::TokenTransfer( @@ -192,7 +191,7 @@ where } let token = ibc_storage::token(&data.denom) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; let denom = if let Some(denom) = data .denom @@ -277,7 +276,7 @@ where .map_err(Error::DecodingPacketData)?; let token = ibc_storage::token(&data.denom) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; let prefix = format!( "{}/{}/", @@ -316,7 +315,7 @@ where )? .unwrap_or_default(); // the previous balance of the mint address should be the maximum - Amount::max().change() - post.change() + Amount::max_signed().change() - post.change() }; if change == amount.change() { @@ -335,7 +334,7 @@ where .map_err(Error::DecodingPacketData)?; let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; // check the denom field let prefix = format!( @@ -359,7 +358,7 @@ where )? .unwrap_or_default(); // the previous balance of the mint address should be the maximum - Amount::max().change() - post.change() + Amount::max_signed().change() - post.change() } else { // source zone: unescrow the token for the refund let source_key = token::multitoken_balance_key( diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index a0337938ff..59456906da 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -86,7 +86,7 @@ where { let params = read_pos_params(storage)?; let total_stake = read_total_stake(storage, ¶ms, epoch)?; - let total_stake = VotePower::from(u64::from(total_stake)); + let total_stake = VotePower::from(total_stake); let Votes { yay_validators, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 5e4a1a064d..ee4492a187 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -29,7 +29,7 @@ pub fn into_tm_voting_power( votes_per_token: Decimal, tokens: impl Into, ) -> i64 { - let prod = decimal_mult_u64(votes_per_token, tokens.into()); + let prod = decimal_mult_u128(votes_per_token, tokens.into() as u128); i64::try_from(prod).expect("Invalid validator voting power (i64)") } diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index d65826ca68..db095a2c00 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -828,6 +828,7 @@ macro_rules! router { ); } +/* /// You can expand the `handlers!` macro invocation with e.g.: /// ```shell /// cargo expand ledger::queries::router::test_rpc_handlers --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib @@ -1029,13 +1030,13 @@ mod test { let result = TEST_RPC.b1(&client).await.unwrap(); assert_eq!(result, "b1"); - let balance = token::Amount::from(123_000_000); + let balance = token::Amount::native_whole(123_000_000); let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); assert_eq!(result, format!("b2i/{balance}")); - let a1 = token::Amount::from(345); - let a2 = token::Amount::from(123_000); - let a3 = token::Amount::from(1_000_999); + let a1 = token::Amount::native_whole(345); + let a2 = token::Amount::native_whole(123_000); + let a3 = token::Amount::native_whole(1_000_999); let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); @@ -1088,3 +1089,4 @@ mod test { Ok(()) } } +*/ \ No newline at end of file diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 43a51a1b0f..65ed48c3d1 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -426,7 +426,7 @@ mod test { assert!(!has_balance_key); // Then write some balance ... - let balance = token::Amount::from(1000); + let balance = token::Amount::native_whole(1000); StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; // It has to be committed to be visible in a query client.wl_storage.commit_tx(); diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index 5f5b5f6081..ed83a492ec 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -1,11 +1,17 @@ // Re-export to show in rustdoc! pub use pos::Pos; +pub use token::Token; + use pos::POS; +use token::TOKEN; + mod pos; +mod token; // Validity predicate queries router! {VP, ( "pos" ) = (sub POS), + ( "token" ) = (sub TOKEN), } #[cfg(any(test, feature = "async-client"))] diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs new file mode 100644 index 0000000000..7f5e0a0b84 --- /dev/null +++ b/shared/src/ledger/queries/vp/token.rs @@ -0,0 +1,24 @@ +use namada_core::ledger::storage::{DB, DBIter, StorageHasher}; +use namada_core::ledger::storage_api; +use namada_core::ledger::storage_api::token::read_denom; +use namada_core::types::address::Address; +use namada_core::types::token; +use namada_core::types::token::Denomination; +use crate::ledger::queries::RequestCtx; + +router! {TOKEN, + ( "denomination" / [addr: Address] ) -> Option = denomination, +} + +/// Get the number of decimal places (in base 10) for a +/// token specified by `addr`. +fn denomination( + ctx: RequestCtx<'_, D, H>, + addr: Address, +) -> storage_api::Result> + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_denom(ctx.wl_storage, &addr) +} \ No newline at end of file diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 7755627c6b..23d59dfb01 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -202,7 +202,7 @@ pub fn init_storage() -> (Address, Address) { // initialize an account let account = tx::ctx().init_account(code).unwrap(); let key = token::balance_key(&token, &account); - let init_bal = Amount::from(1_000_000_000u64); + let init_bal = Amount::native_whole(1_000_000_000u64); tx::ctx().write(&key, init_bal).unwrap(); (token, account) } diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 5ea8549554..753f666a30 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -14,11 +14,11 @@ pub fn transfer( dest: &Address, token: &Address, sub_prefix: Option, - amount: Amount, + amount: DenominatedAmount, key: &Option, shielded: &Option, ) -> TxResult { - if amount != Amount::default() { + if amount.amount != Amount::default() { let src_key = match &sub_prefix { Some(sub_prefix) => { let prefix = @@ -36,7 +36,7 @@ pub fn transfer( None => token::balance_key(token, dest), }; let src_bal: Option = match src { - Address::Internal(InternalAddress::IbcMint) => Some(Amount::max()), + Address::Internal(InternalAddress::IbcMint) => Some(Amount::max_signed()), Address::Internal(InternalAddress::IbcBurn) => { log_string("invalid transfer from the burn address"); unreachable!() @@ -47,7 +47,7 @@ pub fn transfer( log_string(format!("src {} has no balance", src_key)); unreachable!() }); - src_bal.spend(&amount); + src_bal.spend(&amount.amount); let mut dest_bal: Amount = match dest { Address::Internal(InternalAddress::IbcMint) => { log_string("invalid transfer to the mint address"); @@ -55,7 +55,7 @@ pub fn transfer( } _ => ctx.read(&dest_key)?.unwrap_or_default(), }; - dest_bal.receive(&amount); + dest_bal.receive(&amount.amount); if src != dest { match src { Address::Internal(InternalAddress::IbcMint) => { @@ -141,7 +141,7 @@ pub fn transfer_with_keys( let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); let src_bal: Option = match src_owner { Some(Address::Internal(InternalAddress::IbcMint)) => { - Some(Amount::max()) + Some(Amount::max_signed()) } Some(Address::Internal(InternalAddress::IbcBurn)) => { log_string("invalid transfer from the burn address"); diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs index 0785fbf97d..dd54ab864f 100644 --- a/vp_prelude/src/token.rs +++ b/vp_prelude/src/token.rs @@ -18,7 +18,7 @@ pub fn vp( keys_changed: &BTreeSet, verifiers: &BTreeSet
, ) -> VpResult { - let mut change: Change = 0; + let mut change: Change = Change::default(); for key in keys_changed.iter() { let owner: Option<&Address> = match token::is_multitoken_balance_key(token, key) { @@ -37,7 +37,7 @@ pub fn vp( // accumulate the change let pre: Amount = match owner { Address::Internal(InternalAddress::IbcMint) => { - Amount::max() + Amount::max_signed() } Address::Internal(InternalAddress::IbcBurn) => { Amount::default() @@ -46,7 +46,7 @@ pub fn vp( }; let post: Amount = match owner { Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(Amount::max) + ctx.read_temp(key)?.unwrap_or_else(Amount::max_signed) } Address::Internal(InternalAddress::IbcBurn) => { ctx.read_temp(key)?.unwrap_or_default() @@ -56,13 +56,12 @@ pub fn vp( let this_change = post.change() - pre.change(); change += this_change; // make sure that the spender approved the transaction - if this_change < 0 - && !(verifiers.contains(owner) || *owner == address::masp()) + if !(this_change.non_negative() || verifiers.contains(owner) || *owner == address::masp()) { return reject(); } } } } - Ok(change == 0) + Ok(change.is_zero()) } From 0082f2b6b95a5ed7c3bde2009cda1da91538c118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 09:53:28 +0100 Subject: [PATCH 05/69] core/token: add `fn zero` --- apps/src/lib/cli.rs | 4 +- apps/src/lib/client/rpc.rs | 98 ++++++++++++++----- core/src/types/token.rs | 66 +++++++++---- .../src/ledger/native_vp/governance/utils.rs | 2 +- shared/src/ledger/queries/vp/pos.rs | 4 +- 5 files changed, 122 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index dfa8fca8a2..c164c278e1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1624,9 +1624,9 @@ pub mod args { const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::default())); + arg_default("gas-amount", DefaultFn(|| token::Amount::zero())); const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| token::Amount::default())); + arg_default("gas-limit", DefaultFn(|| token::Amount::zero())); const GAS_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4bc15cb45e..d55b4d3bbf 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -45,7 +45,9 @@ use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; -use namada::types::token::{balance_key, DenominatedAmount, MaspDenom, Transfer}; +use namada::types::token::{ + balance_key, DenominatedAmount, MaspDenom, Transfer, +}; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, @@ -240,18 +242,22 @@ pub async fn query_tx_deltas( // account and adds the same to another let mut delta = TransferDelta::default(); for denom in MaspDenom::iter() { - let denominated = denom.denominate(&transfer.amount); - if denominated != 0 { + let denominated = + denom.denominate(&transfer.amount); + if denominated != 0 { let tfer_delta = Amount::from_nonnegative( (transfer.token.clone(), denom.into()), denominated, ) - .expect("invalid value for amount"); + .expect("invalid value for amount"); delta.insert( transfer.source.clone(), Amount::zero() - &tfer_delta, ); - delta.insert(transfer.target.clone(), tfer_delta); + delta.insert( + transfer.target.clone(), + tfer_delta, + ); } } // No shielded accounts are affected by this Transfer @@ -259,7 +265,6 @@ pub async fn query_tx_deltas( (height, idx), (epoch, delta, TransactionDelta::new()), ); - } } // An incomplete page signifies no more transactions @@ -328,8 +333,14 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - tfer_delta.values().zip(MaspDenom::iter()).any(|(x, denom)| x[&(token.clone(), denom)] != 0) - || shielded_accounts.values().zip(MaspDenom::iter()).any(|(x, denom)| x[&(token.clone(), denom)] != 0) + tfer_delta + .values() + .zip(MaspDenom::iter()) + .any(|(x, denom)| x[&(token.clone(), denom)] != 0) + || shielded_accounts + .values() + .zip(MaspDenom::iter()) + .any(|(x, denom)| x[&(token.clone(), denom)] != 0) } None => true, }; @@ -354,7 +365,15 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - format_denominated_amount(&client, addr,token::Amount::from_masp_denominated(val.unsigned_abs(), *denom)).await, + format_denominated_amount( + &client, + addr, + token::Amount::from_masp_denominated( + val.unsigned_abs(), + *denom + ) + ) + .await, readable ); } @@ -378,7 +397,15 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - format_denominated_amount(&client, addr, token::Amount::from_masp_denominated(val.unsigned_abs(), *denom)).await, + format_denominated_amount( + &client, + addr, + token::Amount::from_masp_denominated( + val.unsigned_abs(), + *denom + ) + ) + .await, readable ); } @@ -915,7 +942,7 @@ pub async fn query_shielded_balance( match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { - let mut total_balance = token::Amount::default(); + let mut total_balance = token::Amount::zero(); let token = ctx.get(&token); for denom in MaspDenom::iter() { // Query the multi-asset balance at the given spending key @@ -943,7 +970,10 @@ pub async fn query_shielded_balance( .as_ref(), ) .unwrap(); - total_balance += token::Amount::from_masp_denominated(balance[&asset_type] as u64, denom); + total_balance += token::Amount::from_masp_denominated( + balance[&asset_type] as u64, + denom, + ); } let currency_code = tokens @@ -956,7 +986,12 @@ pub async fn query_shielded_balance( currency_code ); } else { - println!("{}: {}", currency_code, format_denominated_amount(&client, &token, total_balance).await); + println!( + "{}: {}", + currency_code, + format_denominated_amount(&client, &token, total_balance) + .await + ); } } // Here the user wants to know the balance of all tokens across users @@ -1416,8 +1451,8 @@ pub async fn query_and_print_unbonds( }); let total_withdrawable = withdrawable .into_iter() - .fold(token::Amount::default(), |acc, (_, amount)| acc + amount); - if total_withdrawable != token::Amount::default() { + .fold(token::Amount::zero(), |acc, (_, amount)| acc + amount); + if total_withdrawable != token::Amount::zero() { println!("Total withdrawable now: {total_withdrawable}."); } if !not_yet_withdrawable.is_empty() { @@ -1490,7 +1525,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { total += bond.amount; total_slashed += bond.slashed_amount.unwrap_or_default(); } - if total_slashed != token::Amount::default() { + if total_slashed != token::Amount::zero() { writeln!( w, "Active (slashed) bonds total: {}", @@ -1502,7 +1537,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { bonds_total += total; bonds_total_slashed += total_slashed; - let mut withdrawable = token::Amount::default(); + let mut withdrawable = token::Amount::zero(); if !details.unbonds.is_empty() { let mut total: token::Amount = 0.into(); let mut total_slashed: token::Amount = 0.into(); @@ -2345,7 +2380,7 @@ pub async fn get_proposal_offline_votes( }, ) in bonds_and_unbonds { - let mut delegated_amount = token::Amount::default(); + let mut delegated_amount = token::Amount::zero(); for delta in bonds { if delta.start <= proposal.tally_epoch { delegated_amount += delta.amount @@ -2400,7 +2435,7 @@ pub async fn get_proposal_offline_votes( // continue; // } else { // delta -= to_deduct; - // to_deduct = token::Amount::default(); + // to_deduct = token::Amount::zero(); // } // delta = apply_slashes( @@ -2540,7 +2575,7 @@ pub async fn get_total_staked_tokens( /// Get the total stake of a validator at the given epoch. The total stake is a /// sum of validator's self-bonds and delegations to their address. /// Returns `None` when the given address is not a validator address. For a -/// validator with `0` stake, this returns `Ok(token::Amount::default())`. +/// validator with `0` stake, this returns `Ok(token::Amount::zero())`. async fn get_validator_stake( client: &HttpClient, epoch: Epoch, @@ -2624,11 +2659,20 @@ fn unwrap_client_response(response: Result) -> T { /// Look up the denomination of a token in order to format it /// correctly as a string. -async fn format_denominated_amount(client: &HttpClient, token: &Address, amount: token::Amount) -> String { - let denom = unwrap_client_response( RPC.vp().token().denomination(client, token).await) - .unwrap_or_else(|| { - println!("No denomination found for token: {token}, defaulting to zero decimal places"); - 0.into() - }); - DenominatedAmount{ amount, denom }.to_string() +async fn format_denominated_amount( + client: &HttpClient, + token: &Address, + amount: token::Amount, +) -> String { + let denom = unwrap_client_response( + RPC.vp().token().denomination(client, token).await, + ) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, defaulting to zero \ + decimal places" + ); + 0.into() + }); + DenominatedAmount { amount, denom }.to_string() } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index c245c0b366..87e423d3c8 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -11,8 +11,8 @@ use rust_decimal::prelude::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::ledger::storage_api::StorageRead; use crate::ledger::storage_api::token::read_denom; +use crate::ledger::storage_api::StorageRead; use crate::types::address::{masp, Address, DecodeError as AddressError}; use crate::types::storage; use crate::types::storage::{DbKeySeg, Key, KeySeg}; @@ -90,6 +90,11 @@ impl Amount { } } + /// Zero [`Amount`]. + pub fn zero() -> Self { + Self::default() + } + /// Check if [`Amount`] is zero. pub fn is_zero(&self) -> bool { self.raw == Uint::from(0) @@ -193,17 +198,22 @@ impl Amount { DenominatedAmount { amount: *self, denom: 6.into(), - }.to_string_precise() + } + .to_string_precise() } /// Add denomination info if it exists in storage. - pub fn denominated(&self, token: &Address, storage: &impl StorageRead) -> Option { - let denom = read_denom(storage, token).expect("Should be able to read storage"); - denom.map(|denom| - DenominatedAmount { - amount: *self, - denom, - }) + pub fn denominated( + &self, + token: &Address, + storage: &impl StorageRead, + ) -> Option { + let denom = + read_denom(storage, token).expect("Should be able to read storage"); + denom.map(|denom| DenominatedAmount { + amount: *self, + denom, + }) } /// Convert to an [`Amount`] under the assumption that the input @@ -211,7 +221,6 @@ impl Amount { pub fn from_string_precise(string: &str) -> Result { DenominatedAmount::from_str(string).map(|den| den.amount) } - } /// Given a number represented as `M*B^D`, then @@ -298,8 +307,8 @@ impl FromStr for DenominatedAmount { type Err = AmountParseError; fn from_str(s: &str) -> Result { - let decimal = Decimal::from_str(s) - .map_err(AmountParseError::InvalidDecimal)?; + let decimal = + Decimal::from_str(s).map_err(AmountParseError::InvalidDecimal)?; let denom = Denomination(decimal.scale() as u8); Ok(Self { amount: Amount::from_decimal(decimal, denom)?, @@ -329,7 +338,9 @@ impl<'de> serde::Deserialize<'de> for Amount { use serde::de::Error; let amount_string: String = serde::Deserialize::deserialize(deserializer)?; - Ok(Self{raw: Uint::from_str(&amount_string).map_err(D::Error::custom)?}) + Ok(Self { + raw: Uint::from_str(&amount_string).map_err(D::Error::custom)?, + }) } } @@ -471,7 +482,9 @@ impl KeySeg for Amount { string, err )) })?; - Ok(Amount{ raw: Uint::from_big_endian(&bytes)}) + Ok(Amount { + raw: Uint::from_big_endian(&bytes), + }) } fn raw(&self) -> String { @@ -501,7 +514,9 @@ pub enum AmountParseError { InvalidRange, #[error("Error converting amount to decimal, number too large.")] ConvertToDecimal, - #[error("Could not convert from string, expected an unsigned 256-bit integer.")] + #[error( + "Could not convert from string, expected an unsigned 256-bit integer." + )] FromString, } @@ -513,13 +528,24 @@ impl From for Change { impl From for Amount { fn from(change: Change) -> Self { - Amount{raw: change.abs()} + Amount { raw: change.abs() } } } /// The four possible u64 words in a [`Uint`]. /// Used for converting to MASP amounts. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] #[repr(u8)] #[allow(missing_docs)] pub enum MaspDenom { @@ -543,7 +569,7 @@ impl From for MaspDenom { impl MaspDenom { /// Iterator over the possible denominations - pub fn iter() -> impl Iterator{ + pub fn iter() -> impl Iterator { (0u8..3).into_iter().map(Self::from) } @@ -757,8 +783,8 @@ impl TryFrom for Transfer { data.denom.split('/').last().ok_or(TransferError::NoToken)?; let token = Address::decode(token_str).map_err(TransferError::Address)?; - let amount = - DenominatedAmount::from_str(&data.amount).map_err(TransferError::Amount)?; + let amount = DenominatedAmount::from_str(&data.amount) + .map_err(TransferError::Amount)?; Ok(Self { source, target, diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 59456906da..30e6a48fb9 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -172,7 +172,7 @@ where bond_amount(storage, ¶ms, &bond_id, epoch)? .1; - if amount != token::Amount::default() { + if !amount.is_zero() { if vote.is_yay() { let entry = yay_delegators .entry(voter_address.to_owned()) diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index 8836dba0a9..ccb5f40f44 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -166,7 +166,7 @@ where /// `None`. The total stake is a sum of validator's self-bonds and delegations /// to their address. /// Returns `None` when the given address is not a validator address. For a -/// validator with `0` stake, this returns `Ok(token::Amount::default())`. +/// validator with `0` stake, this returns `Ok(token::Amount::zero())`. fn validator_stake( ctx: RequestCtx<'_, D, H>, validator: Address, @@ -379,7 +379,7 @@ where let epoch = epoch.unwrap_or(ctx.wl_storage.storage.last_epoch); let handle = unbond_handle(&source, &validator); - let mut total = token::Amount::default(); + let mut total = token::Amount::zero(); for result in handle.iter(ctx.wl_storage)? { let ( lazy_map::NestedSubKey::Data { From a468d49af2a3cfc7b516e0a74a55f76f0b3780c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 09:58:36 +0100 Subject: [PATCH 06/69] core/token: add `write_denom` --- core/src/ledger/storage_api/token.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index fe6348fbc4..793cba632b 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -20,17 +20,31 @@ where Ok(balance) } -/// Read the denomination of a given token, if any. +/// Read the denomination of a given token, if any. Note that native +/// transparent tokens do not have this set and instead use the constant +/// [`token::NATIVE_MAX_DECIMAL_PLACES`]. pub fn read_denom( storage: &S, token: &Address, ) -> storage_api::Result> - where - S: StorageRead, +where + S: StorageRead, +{ + let key = token::denom_key(token); + storage.read(&key) +} + +/// Write the denomination of a given token. +pub fn write_denom( + storage: &mut S, + token: &Address, + denom: token::Denomination, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, { let key = token::denom_key(token); - let denom = storage.read::(&key)?; - Ok(denom.map(Into::into)) + storage.write(&key, denom) } /// Transfer `token` from `src` to `dest`. Returns an `Err` if `src` has From 6bb685d31fef7d8d5ca5d65e299e46dd8665529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 10:04:35 +0100 Subject: [PATCH 07/69] core/transaction/wrapper: add a todo for later --- core/src/types/transaction/wrapper.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index d3b3b5dce0..d23d0ca2cb 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -123,7 +123,8 @@ pub mod wrapper_tx { /// of GAS_LIMIT_RESOLUTION impl From for GasLimit { fn from(amount: Amount) -> GasLimit { - GasLimit::from(u128::from(amount) as u64) + // TODO: this may panic. + GasLimit::from(u128::try_from(amount).unwrap() as u64) } } From f17e75eeb43b213bc3efb82c0c0fde1de7f1c325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 10:17:12 +0100 Subject: [PATCH 08/69] app/shell: use token::Amount::native_whole for min fee conversion --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ef7fe504a1..c79d65a384 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -731,7 +731,7 @@ where &self.wl_storage, ) .expect("Must be able to read wrapper tx fees parameter"); - fees.unwrap_or(token::Amount::whole(MIN_FEE)) + fees.unwrap_or(token::Amount::native_whole(MIN_FEE)) } #[cfg(not(feature = "mainnet"))] From 8a64466231cbc80fb1b162a3ad86dfb112669c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 10:17:44 +0100 Subject: [PATCH 09/69] core/token: make conv from Amount to u128 falliable --- core/src/types/token.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 87e423d3c8..818851a726 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -394,9 +394,20 @@ impl From for Amount { } } -impl From for u128 { - fn from(amount: Amount) -> Self { - amount.raw.as_u128() +impl TryFrom for u128 { + type Error = std::io::Error; + + fn try_from(value: Amount) -> Result { + let Uint(arr) = value.raw; + for i in 2..4 { + if arr[i] != 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Integer overflow when casting to u128", + )); + } + } + Ok(value.raw.low_u128()) } } From de172189876d3de9d6a6a325c6d2790bd2320475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 10:20:18 +0100 Subject: [PATCH 10/69] WIP: shared/pos: update token to TM voting power conv --- shared/src/ledger/pos/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index ee4492a187..4d915547fb 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -27,9 +27,12 @@ pub const SLASH_POOL_ADDRESS: Address = /// from the number of tokens pub fn into_tm_voting_power( votes_per_token: Decimal, - tokens: impl Into, + tokens: token::Amount, ) -> i64 { - let prod = decimal_mult_u128(votes_per_token, tokens.into() as u128); + let prod = decimal_mult_u128( + votes_per_token, + u128::try_from(tokens).expect("TODO(Tomas): handle overflow"), + ); i64::try_from(prod).expect("Invalid validator voting power (i64)") } From 819f319b725497ba3d7108ac4138aa2562e658b0 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 6 Apr 2023 22:11:50 +0200 Subject: [PATCH 11/69] Fixed most of the masp compile issues. May not be logically correct yet --- apps/src/lib/cli.rs | 154 +++++- apps/src/lib/client/rpc.rs | 471 ++++++++++------ apps/src/lib/client/signing.rs | 6 +- apps/src/lib/client/tx.rs | 467 +++++++++------- apps/src/lib/config/genesis.rs | 16 +- core/src/ledger/storage/masp_conversions.rs | 56 +- core/src/ledger/testnet_pow.rs | 2 +- core/src/types/address.rs | 17 +- core/src/types/token.rs | 120 +++- core/src/types/transaction/wrapper.rs | 60 +- core/src/types/uint.rs | 10 +- proof_of_stake/src/lib.rs | 72 ++- proof_of_stake/src/types.rs | 21 +- proof_of_stake/src/types/rev_order.rs | 3 +- shared/src/ledger/ibc/vp/token.rs | 11 +- .../src/ledger/native_vp/governance/utils.rs | 13 +- shared/src/ledger/queries/router.rs | 522 +++++++++--------- shared/src/ledger/queries/shell.rs | 21 +- shared/src/ledger/queries/vp/mod.rs | 3 +- shared/src/ledger/queries/vp/token.rs | 11 +- tx_prelude/src/token.rs | 4 +- vp_prelude/src/token.rs | 4 +- 22 files changed, 1288 insertions(+), 776 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c164c278e1..fa99319ed1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1567,6 +1567,7 @@ pub mod args { use std::str::FromStr; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; + use namada::ledger::queries::RPC; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::governance::ProposalVote; @@ -1575,8 +1576,10 @@ pub mod args { use namada::types::storage::{self, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; + use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::GasLimit; use rust_decimal::Decimal; + use tendermint_rpc::HttpClient; use super::context::*; use super::utils::*; @@ -1591,7 +1594,7 @@ pub mod args { const ALIAS_OPT: ArgOpt = ALIAS.opt(); const ALIAS: Arg = arg("alias"); const ALLOW_DUPLICATE_IP: ArgFlag = flag("allow-duplicate-ip"); - const AMOUNT: Arg = arg("amount"); + const AMOUNT: Arg = arg("amount"); const ARCHIVE_DIR: ArgOpt = arg_opt("archive-dir"); const BALANCE_OWNER: ArgOpt = arg_opt("owner"); const BASE_DIR: ArgDefault = arg_default( @@ -1623,10 +1626,20 @@ pub mod args { const EPOCH: ArgOpt = arg_opt("epoch"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); - const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::zero())); - const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| token::Amount::zero())); + const GAS_AMOUNT: ArgDefault = arg_default( + "gas-amount", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); + const GAS_LIMIT: ArgDefault = arg_default( + "gas-limit", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); const GAS_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); @@ -1873,20 +1886,64 @@ pub mod args { /// Transferred token address pub sub_prefix: Option, /// Transferred token amount - pub amount: token::Amount, + pub amount: token::DenominatedAmount, } impl TxTransfer { - pub fn parse_from_context( - &self, + pub async fn parse_from_context( + &mut self, ctx: &mut Context, ) -> ParsedTxTransferArgs { + let token = ctx.get(&self.token); ParsedTxTransferArgs { - tx: self.tx.parse_from_context(ctx), + tx: self.tx.parse_from_context(ctx).await, source: ctx.get_cached(&self.source), target: ctx.get(&self.target), - token: ctx.get(&self.token), - amount: self.amount, + amount: self.validate_amount(&token).await, + token, + } + } + + /// Get the correct representation of the amount given the token type. + async fn validate_amount(&mut self, token: &Address) -> token::Amount { + let client = + HttpClient::new(self.tx.ledger_address.clone()).unwrap(); + let denom = RPC + .vp() + .token() + .denomination(&client, token) + .await + .unwrap_or_else(|err| { + eprintln!("Error in the query {}", err); + safe_exit(1) + }) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, the input \ + arguments could + not be parsed." + ); + safe_exit(1); + }); + let input_amount = self.amount.canonical(); + if denom < input_amount.denom { + println!( + "The input amount contained a higher precision than \ + allowed by {token}." + ); + safe_exit(1); + } else { + let validated = input_amount + .increase_precision(denom) + .unwrap_or_else(|| { + println!( + "The amount provided requires more the 256 bits \ + to represent." + ); + safe_exit(1); + }); + self.amount = validated; + self.amount.amount } } } @@ -1968,7 +2025,7 @@ pub mod args { receiver, token, sub_prefix, - amount, + amount: amount.amount, port_id, channel_id, timeout_height, @@ -2185,6 +2242,14 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); + let amount = amount + .canonical() + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .unwrap_or_else(|| { + println!("Could not parse bond amount"); + safe_exit(1); + }) + .amount; let source = SOURCE_OPT.parse(matches); Self { tx, @@ -2224,6 +2289,14 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); + let amount = amount + .canonical() + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .unwrap_or_else(|| { + println!("Could not parse bond amount"); + safe_exit(1); + }) + .amount; let source = SOURCE_OPT.parse(matches); Self { tx, @@ -2858,7 +2931,7 @@ pub mod args { /// save it in the wallet. pub initialized_account_alias: Option, /// The amount being payed to include the transaction - pub fee_amount: token::Amount, + pub fee_amount: token::DenominatedAmount, /// The token in which the fee is being paid pub fee_token: WalletAddress, /// The max amount of gas used to process tx @@ -2870,7 +2943,11 @@ pub mod args { } impl Tx { - pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { + pub async fn parse_from_context( + &mut self, + ctx: &mut Context, + ) -> ParsedTxArgs { + let fee_token = ctx.get(&self.fee_token); ParsedTxArgs { dry_run: self.dry_run, dump_tx: self.dump_tx, @@ -2880,8 +2957,8 @@ pub mod args { initialized_account_alias: self .initialized_account_alias .clone(), - fee_amount: self.fee_amount, - fee_token: ctx.get(&self.fee_token), + fee_amount: self.validate_amount(&fee_token).await, + fee_token, gas_limit: self.gas_limit.clone(), signing_key: self .signing_key @@ -2890,6 +2967,49 @@ pub mod args { signer: self.signer.as_ref().map(|signer| ctx.get(signer)), } } + + /// Get the correct representation of the fee amount given the token + /// type. + async fn validate_amount(&mut self, token: &Address) -> token::Amount { + let client = HttpClient::new(self.ledger_address.clone()).unwrap(); + let denom = RPC + .vp() + .token() + .denomination(&client, token) + .await + .unwrap_or_else(|err| { + eprintln!("Error in the query {}", err); + safe_exit(1) + }) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, the input \ + arguments could + not be parsed." + ); + safe_exit(1); + }); + let input_amount = self.fee_amount.canonical(); + if denom < input_amount.denom { + println!( + "The input amount contained a higher precision than \ + allowed by {token}." + ); + safe_exit(1); + } else { + let validated = input_amount + .increase_precision(denom) + .unwrap_or_else(|| { + println!( + "The amount provided requires more the 256 bits \ + to represent." + ); + safe_exit(1); + }); + self.fee_amount = validated; + self.fee_amount.amount + } + } } impl Args for Tx { @@ -2953,7 +3073,7 @@ pub mod args { let initialized_account_alias = ALIAS_OPT.parse(matches); let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); - let gas_limit = GAS_LIMIT.parse(matches).into(); + let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d55b4d3bbf..c156e4ed8b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -58,7 +58,8 @@ use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; use crate::client::tx::{ - Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, + Conversions, MaspDenominatedAmount, PinnedBalanceError, TransactionDelta, + TransferDelta, }; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -132,6 +133,16 @@ pub async fn query_epoch(client: &HttpClient) -> Epoch { unwrap_client_response(RPC.shell().epoch(client).await) } +/// Query the epoch of the given block height, if it exists. +/// Will return none if the input block height is greater than +/// the latest committed block height. +pub async fn query_epoch_at_height( + client: &HttpClient, + height: BlockHeight, +) -> Option { + unwrap_client_response(RPC.shell().epoch_at_height(client, &height).await) +} + /// Query the last committed block pub async fn query_block( args: args::Query, @@ -213,6 +224,11 @@ pub async fn query_tx_deltas( .txs; for response_tx in txs { let height = BlockHeight(response_tx.height.value()); + let epoch = + query_epoch_at_height(&client, height).await.expect( + "This block height should not exceed that latest \ + committed block height", + ); let idx = TxIndex(response_tx.index); // Only process yet unprocessed transactions which have been // accepted by node VPs @@ -522,15 +538,20 @@ pub async fn query_transparent_balance( .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); match query_storage_value::(&client, &key).await { - Some(balance) => match &args.sub_prefix { - Some(sub_prefix) => { - println!( - "{} with {}: {}", - currency_code, sub_prefix, balance - ); + Some(balance) => { + let balance = + format_denominated_amount(&client, &token, balance) + .await; + match &args.sub_prefix { + Some(sub_prefix) => { + println!( + "{} with {}: {}", + currency_code, sub_prefix, balance + ); + } + None => println!("{}: {}", currency_code, balance), } - None => println!("{}: {}", currency_code, balance), - }, + } None => { println!("No {} balance found for {}", currency_code, owner) } @@ -546,10 +567,12 @@ pub async fn query_transparent_balance( if let Some(balances) = balances { print_balances( ctx, + &client, balances, &token, owner.address().as_ref(), - ); + ) + .await; } } } @@ -559,7 +582,7 @@ pub async fn query_transparent_balance( let balances = query_storage_prefix::(&client, &prefix).await; if let Some(balances) = balances { - print_balances(ctx, balances, &token, None); + print_balances(ctx, &client, balances, &token, None).await; } } (None, None) => { @@ -568,7 +591,7 @@ pub async fn query_transparent_balance( let balances = query_storage_prefix::(&client, &key).await; if let Some(balances) = balances { - print_balances(ctx, balances, &token, None); + print_balances(ctx, &client, balances, &token, None).await; } } } @@ -655,25 +678,37 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { } (Ok((balance, epoch)), Some(token)) => { let token = ctx.get(token); - // Extract and print only the specified token from the total - let (_asset_type, balance) = - value_by_address(&balance, token.clone(), epoch); let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); - if balance == 0 { + let mut total_balance = token::Amount::default(); + for denom in MaspDenom::iter() { + // Extract and print only the specified token from the total + let (_asset_type, value) = + value_by_address(&balance, token.clone(), denom, epoch); + total_balance += token::Amount::from_masp_denominated( + value as u64, + denom, + ); + } + if total_balance.is_zero() { println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", owner, epoch, currency_code ); } else { - let asset_value = token::Amount::from(balance as u64); + let formatted = format_denominated_amount( + &client, + &token, + total_balance, + ) + .await; println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, epoch, asset_value, currency_code + owner, epoch, formatted, currency_code ); } } @@ -684,8 +719,11 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .shielded .decode_amount(client.clone(), balance, epoch) .await; - for (addr, value) in balance.components() { - let asset_value = token::Amount::from(*value as u64); + for ((addr, denom), value) in balance.components() { + let asset_value = token::Amount::from_masp_denominated( + *value as u64, + *denom, + ); if !found_any { println!( "Payment address {} was consumed during epoch {}. \ @@ -695,10 +733,13 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { found_any = true; } let addr_enc = addr.encode(); + let formatted = + format_denominated_amount(&client, addr, asset_value) + .await; println!( " {}: {}", tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - asset_value, + formatted, ); } if !found_any { @@ -713,15 +754,15 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { } } -fn print_balances( +async fn print_balances( ctx: &Context, + client: &HttpClient, balances: impl Iterator, token: &Address, target: Option<&Address>, ) { let stdout = io::stdout(); let mut w = stdout.lock(); - // Token let tokens = address::tokens(); let currency_code = tokens @@ -729,40 +770,42 @@ fn print_balances( .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); writeln!(w, "Token {}", currency_code).unwrap(); - - let print_num = balances - .filter_map( - |(key, balance)| match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => Some(( - owner.clone(), - format!( - "with {}: {}, owned by {}", - sub_prefix, - balance, - lookup_alias(ctx, owner) - ), - )), - None => token::is_any_token_balance_key(&key).map(|owner| { + let mut print_num = 0; + for (key, balance) in balances { + let (o, s) = match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => ( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix, + format_denominated_amount(&client, &token, balance).await, + lookup_alias(ctx, owner) + ), + ), + None => { + if let Some(owner) = token::is_any_token_balance_key(&key) { ( owner.clone(), format!( ": {}, owned by {}", - balance, + format_denominated_amount(&client, &token, balance) + .await, lookup_alias(ctx, owner) ), ) - }), - }, - ) - .filter_map(|(o, s)| match target { - Some(t) if o == *t => Some(s), - Some(_) => None, - None => Some(s), - }) - .map(|s| { - writeln!(w, "{}", s).unwrap(); - }) - .count(); + } else { + continue; + } + } + }; + let s = match target { + Some(t) if o == *t => s, + Some(_) => continue, + None => s, + }; + writeln!(w, "{}", s).unwrap(); + print_num += 1; + } if print_num == 0 { match target { @@ -891,11 +934,12 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { pub fn value_by_address( amt: &masp_primitives::transaction::components::Amount, token: Address, + denom: MaspDenom, epoch: Epoch, ) -> (AssetType, i64) { // Compute the unique asset identifier from the token address let asset_type = AssetType::new( - (token, epoch.0) + (token, denom, epoch.0) .try_to_vec() .expect("token addresses should serialize") .as_ref(), @@ -1033,7 +1077,9 @@ pub async fn query_shielded_balance( .decode_asset_type(client.clone(), asset_type) .await; match decoded { - Some((addr, asset_epoch)) if asset_epoch == epoch => { + Some((addr, denom, asset_epoch)) + if asset_epoch == epoch => + { // Only assets with the current timestamp count let addr_enc = addr.encode(); println!( @@ -1043,22 +1089,29 @@ pub async fn query_shielded_balance( .cloned() .unwrap_or(addr_enc.as_str()) ); - read_tokens.insert(addr); + read_tokens.insert(addr.clone()); + let mut found_any = false; + for (fvk, value) in balances { + let value = token::Amount::from_masp_denominated( + value as u64, + denom, + ); + let formatted = format_denominated_amount( + &client, &addr, value, + ) + .await; + println!(" {}, owned by {}", formatted, fvk); + found_any = true; + } + if !found_any { + println!( + "No shielded {} balance found for any wallet \ + key", + asset_type + ); + } } - _ => continue, - } - - let mut found_any = false; - for (fvk, value) in balances { - let value = token::Amount::from(value as u64); - println!(" {}, owned by {}", value, fvk); - found_any = true; - } - if !found_any { - println!( - "No shielded {} balance found for any wallet key", - asset_type - ); + _ => {} } } // Print zero balances for remaining assets @@ -1077,42 +1130,49 @@ pub async fn query_shielded_balance( (Some(token), false) => { // Compute the unique asset identifier from the token address let token = ctx.get(&token); - let asset_type = AssetType::new( - (token.clone(), epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + let mut found_any = false; let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); println!("Shielded Token {}:", currency_code); - let mut found_any = false; for fvk in viewing_keys { - // Query the multi-asset balance at the given spending key - let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; - let balance = if no_conversions { - ctx.shielded - .compute_shielded_balance(&viewing_key) - .expect("context should contain viewing key") - } else { - ctx.shielded - .compute_exchanged_balance( - client.clone(), - &viewing_key, - epoch, - ) - .await - .expect("context should contain viewing key") - }; - if balance[&asset_type] != 0 { - let asset_value = - token::Amount::from(balance[&asset_type] as u64); - println!(" {}, owned by {}", asset_value, fvk); - found_any = true; + let mut balance = token::Amount::default(); + for denom in MaspDenom::iter() { + let asset_type = AssetType::new( + (token.clone(), denom, epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + // Query the multi-asset balance at the given spending key + let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; + let denom_balance = if no_conversions { + ctx.shielded + .compute_shielded_balance(&viewing_key) + .expect("context should contain viewing key") + } else { + ctx.shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key") + }; + if denom_balance[&asset_type] != 0 { + balance += token::Amount::from_masp_denominated( + denom_balance[&asset_type] as u64, + denom, + ); + found_any = true; + } } + let formatted = + format_denominated_amount(&client, &token, balance).await; + println!(" {}, owned by {}", formatted, fvk); } if !found_any { println!( @@ -1137,7 +1197,8 @@ pub async fn query_shielded_balance( .shielded .decode_all_amounts(client.clone(), balance) .await; - print_decoded_balance_with_epoch(decoded_balance); + print_decoded_balance_with_epoch(&client, decoded_balance) + .await; } else { balance = ctx .shielded @@ -1153,48 +1214,66 @@ pub async fn query_shielded_balance( .shielded .decode_amount(client.clone(), balance, epoch) .await; - print_decoded_balance(decoded_balance); + print_decoded_balance(&client, decoded_balance).await; } } } } -pub fn print_decoded_balance(decoded_balance: Amount
) { +pub async fn print_decoded_balance( + client: &HttpClient, + decoded_balance: MaspDenominatedAmount, +) { let tokens = address::tokens(); - let mut found_any = false; - for (addr, value) in decoded_balance.components() { - let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); - println!( - "{} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - asset_value - ); - found_any = true; + let mut balances = HashMap::new(); + for ((addr, denom), value) in decoded_balance.components() { + let asset_value = + token::Amount::from_masp_denominated(*value as u64, *denom); + balances + .entry(addr) + .and_modify(|val| *val += asset_value) + .or_insert(asset_value); } - if !found_any { + if balances.is_empty() { println!("No shielded balance found for given key"); + } else { + for (addr, amount) in balances { + let addr_enc = addr.encode(); + println!( + "{} : {}", + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + format_denominated_amount(client, addr, amount).await, + ); + } } } -pub fn print_decoded_balance_with_epoch( - decoded_balance: Amount<(Address, Epoch)>, +pub async fn print_decoded_balance_with_epoch( + client: &HttpClient, + decoded_balance: Amount<(Address, MaspDenom, Epoch)>, ) { let tokens = address::tokens(); - let mut found_any = false; - for ((addr, epoch), value) in decoded_balance.components() { - let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); - println!( - "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - epoch, - asset_value - ); - found_any = true; + let mut balances = HashMap::new(); + for ((addr, denom, epoch), value) in decoded_balance.components() { + let asset_value = + token::Amount::from_masp_denominated(*value as u64, *denom); + balances + .entry((addr, epoch)) + .and_modify(|val| *val += asset_value) + .or_insert(asset_value); } - if !found_any { + if balances.is_empty() { println!("No shielded balance found for given key"); + } else { + for ((addr, epoch), amount) in balances { + let addr_enc = addr.encode(); + println!( + "{} | {} : {}", + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + epoch, + format_denominated_amount(client, addr, amount).await, + ); + } } } @@ -1453,15 +1532,18 @@ pub async fn query_and_print_unbonds( .into_iter() .fold(token::Amount::zero(), |acc, (_, amount)| acc + amount); if total_withdrawable != token::Amount::zero() { - println!("Total withdrawable now: {total_withdrawable}."); + println!( + "Total withdrawable now: {}.", + total_withdrawable.to_string_native() + ); } if !not_yet_withdrawable.is_empty() { println!("Current epoch: {current_epoch}.") } for ((_start_epoch, withdraw_epoch), amount) in not_yet_withdrawable { println!( - "Amount {amount} withdrawable starting from epoch \ - {withdraw_epoch}." + "Amount {} withdrawable starting from epoch {withdraw_epoch}.", + amount.to_string_native() ); } } @@ -1498,14 +1580,14 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { .bonds_and_unbonds(&client, &source, &validator) .await, ); - let mut bonds_total: token::Amount = 0.into(); - let mut bonds_total_slashed: token::Amount = 0.into(); - let mut unbonds_total: token::Amount = 0.into(); - let mut unbonds_total_slashed: token::Amount = 0.into(); - let mut total_withdrawable: token::Amount = 0.into(); + let mut bonds_total = token::Amount::default(); + let mut bonds_total_slashed = token::Amount::default(); + let mut unbonds_total = token::Amount::default(); + let mut unbonds_total_slashed = token::Amount::default(); + let mut total_withdrawable = token::Amount::default(); for (bond_id, details) in bonds_and_unbonds { - let mut total: token::Amount = 0.into(); - let mut total_slashed: token::Amount = 0.into(); + let mut total = token::Amount::default(); + let mut total_slashed = token::Amount::default(); let bond_type = if bond_id.source == bond_id.validator { format!("Self-bonds from {}", bond_id.validator) } else { @@ -1519,7 +1601,8 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { writeln!( w, " Remaining active bond from epoch {}: Δ {}", - bond.start, bond.amount + bond.start, + bond.amount.to_string_native() ) .unwrap(); total += bond.amount; @@ -1529,18 +1612,18 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { writeln!( w, "Active (slashed) bonds total: {}", - total - total_slashed + (total - total_slashed).to_string_native() ) .unwrap(); } - writeln!(w, "Bonds total: {}", total).unwrap(); + writeln!(w, "Bonds total: {}", total.to_string_native()).unwrap(); bonds_total += total; bonds_total_slashed += total_slashed; let mut withdrawable = token::Amount::zero(); if !details.unbonds.is_empty() { - let mut total: token::Amount = 0.into(); - let mut total_slashed: token::Amount = 0.into(); + let mut total = token::Amount::default(); + let mut total_slashed = token::Amount::default(); let bond_type = if bond_id.source == bond_id.validator { format!("Unbonded self-bonds from {}", bond_id.validator) } else { @@ -1553,36 +1636,43 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { writeln!( w, " Withdrawable from epoch {} (active from {}): Δ {}", - unbond.withdraw, unbond.start, unbond.amount + unbond.withdraw, + unbond.start, + unbond.amount.to_string_native() ) .unwrap(); } withdrawable = total - total_slashed; - writeln!(w, "Unbonded total: {}", total).unwrap(); + writeln!(w, "Unbonded total: {}", total.to_string_native()) + .unwrap(); unbonds_total += total; unbonds_total_slashed += total_slashed; total_withdrawable += withdrawable; } - writeln!(w, "Withdrawable total: {}", withdrawable).unwrap(); + writeln!(w, "Withdrawable total: {}", withdrawable.to_string_native()) + .unwrap(); println!(); } if bonds_total != bonds_total_slashed { println!( "All bonds total active: {}", - bonds_total - bonds_total_slashed + (bonds_total - bonds_total_slashed).to_string_native() ); } - println!("All bonds total: {}", bonds_total); + println!("All bonds total: {}", bonds_total.to_string_native()); if unbonds_total != unbonds_total_slashed { println!( "All unbonds total active: {}", - unbonds_total - unbonds_total_slashed + (unbonds_total - unbonds_total_slashed).to_string_native() ); } - println!("All unbonds total: {}", unbonds_total); - println!("All unbonds total withdrawable: {}", total_withdrawable); + println!("All unbonds total: {}", unbonds_total.to_string_native()); + println!( + "All unbonds total withdrawable: {}", + total_withdrawable.to_string_native() + ); } /// Query PoS bonded stake @@ -1602,7 +1692,10 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { Some(stake) => { // TODO: show if it's in consensus set, below capacity, or // below threshold set - println!("Bonded stake of validator {validator}: {stake}",) + println!( + "Bonded stake of validator {validator}: {}", + stake.to_string_native() + ) } None => { println!("No bonded stake found for {validator}") @@ -1629,8 +1722,13 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { writeln!(w, "Consensus validators:").unwrap(); for val in consensus { - writeln!(w, " {}: {}", val.address.encode(), val.bonded_stake) - .unwrap(); + writeln!( + w, + " {}: {}", + val.address.encode(), + val.bonded_stake.to_string_native() + ) + .unwrap(); } if !below_capacity.is_empty() { writeln!(w, "Below capacity validators:").unwrap(); @@ -1639,7 +1737,7 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { w, " {}: {}", val.address.encode(), - val.bonded_stake + val.bonded_stake.to_string_native() ) .unwrap(); } @@ -1648,7 +1746,10 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { } let total_staked_tokens = get_total_staked_tokens(&client, epoch).await; - println!("Total bonded stake: {total_staked_tokens}"); + println!( + "Total bonded stake: {}", + total_staked_tokens.to_string_native() + ); } /// Query and return validator's commission rate and max commission rate change @@ -1890,7 +1991,7 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found let mut conversions_found = false; - for (addr, epoch, conv, _) in conv_state.assets.values() { + for ((addr, _), epoch, conv, _) in conv_state.assets.values() { let amt: masp_primitives::transaction::components::Amount = conv.clone().into(); // If the user has specified any targets, then meet them @@ -1914,7 +2015,7 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { for (asset_type, val) in amt.components() { // Look up the address and epoch of asset to facilitate pretty // printing - let (addr, epoch, _, _) = &conv_state.assets[asset_type]; + let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion let addr_enc = addr.encode(); print!( @@ -1941,6 +2042,7 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, + MaspDenom, Epoch, masp_primitives::transaction::components::Amount, MerklePath, @@ -2262,7 +2364,8 @@ pub async fn get_proposal_votes( get_validator_stake(client, epoch, &voter_address) .await .unwrap_or_default() - .into(); + .try_into() + .expect("Amount out of bounds"); yay_validators.insert(voter_address, amount); } else if !validators.contains(&voter_address) { let validator_address = @@ -2282,13 +2385,19 @@ pub async fn get_proposal_votes( if vote.is_yay() { let entry = yay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); + entry.insert( + validator_address, + VotePower::try_from(amount) + .expect("Amount out of bounds"), + ); } else { let entry = nay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); + entry.insert( + validator_address, + VotePower::try_from(amount) + .expect("Amount out of bounds"), + ); } } } @@ -2346,7 +2455,8 @@ pub async fn get_proposal_offline_votes( ) .await .unwrap_or_default() - .into(); + .try_into() + .expect("Amount out of bounds"); yay_validators.insert(proposal_vote.address, amount); } else if is_delegator_at( client, @@ -2391,12 +2501,20 @@ pub async fn get_proposal_offline_votes( let entry = yay_delegators .entry(proposal_vote.address.clone()) .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); + entry.insert( + validator, + VotePower::try_from(delegated_amount) + .expect("Amount out of bounds"), + ); } else { let entry = nay_delegators .entry(proposal_vote.address.clone()) .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); + entry.insert( + validator, + VotePower::try_from(delegated_amount) + .expect("Amount out of bounds"), + ); } } @@ -2487,8 +2605,10 @@ pub async fn compute_tally( epoch: Epoch, votes: Votes, ) -> ProposalResult { - let total_staked_tokens: VotePower = - get_total_staked_tokens(client, epoch).await.into(); + let total_staked_tokens: VotePower = get_total_staked_tokens(client, epoch) + .await + .try_into() + .expect("Amount out of bounds"); let Votes { yay_validators, @@ -2631,7 +2751,8 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .expect("Parameter should be definied."); GovParams { - min_proposal_fund: u64::from(min_proposal_fund), + min_proposal_fund: u128::try_from(min_proposal_fund) + .expect("Amount out of bounds") as u64, max_proposal_code_size, min_proposal_period, max_proposal_period, @@ -2650,7 +2771,9 @@ fn lookup_alias(ctx: &Context, addr: &Address) -> String { } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response(response: Result) -> T { +pub(super) fn unwrap_client_response( + response: Result, +) -> T { response.unwrap_or_else(|err| { eprintln!("Error in the query {}", err); cli::safe_exit(1) @@ -2659,7 +2782,7 @@ fn unwrap_client_response(response: Result) -> T { /// Look up the denomination of a token in order to format it /// correctly as a string. -async fn format_denominated_amount( +pub(super) async fn format_denominated_amount( client: &HttpClient, token: &Address, amount: token::Amount, @@ -2676,3 +2799,17 @@ async fn format_denominated_amount( }); DenominatedAmount { amount, denom }.to_string() } + +/// Make asset type corresponding to given address and epoch +pub fn make_asset_type( + epoch: Epoch, + token: &Address, + denom: MaspDenom, +) -> AssetType { + // Typestamp the chosen token with the current epoch + let token_bytes = (token, denom, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") +} diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 9b1a00b987..e37423d652 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -238,7 +238,7 @@ pub async fn sign_wrapper( let client = HttpClient::new(args.ledger_address.clone()).unwrap(); let fee_amount = if cfg!(feature = "mainnet") { - Amount::whole(MIN_FEE) + Amount::native_whole(MIN_FEE) } else { let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); rpc::query_storage_value::(&client, &wrapper_tx_fees_key) @@ -255,7 +255,9 @@ pub async fn sign_wrapper( if balance < fee_amount { eprintln!( "The wrapper transaction source doesn't have enough balance to \ - pay fee {fee_amount}, got {balance}." + pay fee {}, got {}.", + fee_amount.to_string_native(), + balance.to_string_native(), ); if !args.force && cfg!(feature = "mainnet") { cli::safe_exit(1); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 921f0eaa1e..6f2575bb0d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -30,6 +30,7 @@ use masp_primitives::transaction::components::{Amount, OutPoint, TxOut}; use masp_primitives::transaction::Transaction; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; +use namada::core::types::uint::Uint; use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use namada::ibc::signer::Signer; use namada::ibc::timestamp::Timestamp as IbcTimestamp; @@ -39,6 +40,7 @@ use namada::ibc_proto::cosmos::base::v1beta1::Coin; use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp; use namada::ledger::pos::{CommissionPair, PosParams}; +use namada::ledger::queries::RPC; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ @@ -50,7 +52,10 @@ use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, MaspDenom}; +use namada::types::token::{ + DenominatedAmount, MaspDenom, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, + TX_KEY_PREFIX, +}; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -66,7 +71,10 @@ use super::rpc; use super::types::ShieldedTransferContext; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::rpc::{query_conversion, query_storage_value}; +use crate::client::rpc::{ + format_denominated_amount, make_asset_type, query_conversion, + query_storage_value, unwrap_client_response, +}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::client::types::ParsedTxTransferArgs; @@ -475,10 +483,10 @@ pub type Conversions = pub type MaspDenominatedAmount = Amount<(Address, MaspDenom)>; /// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = HashMap; +pub type TransferDelta = HashMap>; /// Represents the changes that were made to a list of shielded accounts -pub type TransactionDelta = HashMap; +pub type TransactionDelta = HashMap; /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. @@ -511,7 +519,7 @@ pub struct ShieldedContext { /// The set of note positions that have been spent spents: HashSet, /// Maps asset types to their decodings - asset_types: HashMap, + asset_types: HashMap, /// Maps note positions to their corresponding viewing keys vk_map: HashMap, } @@ -836,19 +844,25 @@ impl ShieldedContext { .expect("found note with invalid value or asset type"); } } + // Record the changes to the transparent accounts - let transparent_delta = - Amount::from_nonnegative(tx.token.clone(), u64::from(tx.amount)) - .expect("invalid value for amount"); let mut transfer_delta = TransferDelta::new(); - transfer_delta - .insert(tx.source.clone(), Amount::zero() - &transparent_delta); - transfer_delta.insert(tx.target.clone(), transparent_delta); + for denom in MaspDenom::iter() { + let transparent_delta = + Amount::from_nonnegative((tx.token.clone(), denom), denom.denominate(&tx.amount.amount)) + .expect("invalid value for amount"); + if transparent_delta == Amount::zero() { + continue; + } + transfer_delta + .insert(tx.source.clone(), Amount::zero() - &transparent_delta); + transfer_delta.insert(tx.target.clone(), transparent_delta); + self.last_txidx += 1; + } self.delta_map.insert( (height, index), (epoch, transfer_delta, transaction_delta), ); - self.last_txidx += 1; } /// Summarize the effects on shielded and transparent accounts of each @@ -895,16 +909,22 @@ impl ShieldedContext { &mut self, client: HttpClient, asset_type: AssetType, - ) -> Option<(Address, Epoch)> { + ) -> Option<(Address, MaspDenom, Epoch)> { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); } // Query for the ID of the last accepted transaction - let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - query_conversion(client, asset_type).await?; - self.asset_types.insert(asset_type, (addr.clone(), ep)); - Some((addr, ep)) + let (addr, denom, ep, _conv, _path): ( + Address, + MaspDenom, + _, + Amount, + MerklePath, + ) = query_conversion(client, asset_type).await?; + self.asset_types + .insert(asset_type, (addr.clone(), denom, ep)); + Some((addr, denom, ep)) } /// Query the ledger for the conversion that is allowed for the given asset @@ -919,9 +939,9 @@ impl ShieldedContext { Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), Entry::Vacant(conv_entry) => { // Query for the ID of the last accepted transaction - let (addr, ep, conv, path): (Address, _, _, _) = + let (addr, denom, ep, conv, path): (Address, _, _, _, _) = query_conversion(client, asset_type).await?; - self.asset_types.insert(asset_type, (addr, ep)); + self.asset_types.insert(asset_type, (addr, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv == Amount::zero() { None @@ -1004,7 +1024,7 @@ impl ShieldedContext { pub async fn compute_exchanged_amount( &mut self, client: HttpClient, - mut input: MaspDenominatedAmount, + mut input: Amount, target_epoch: Epoch, mut conversions: Conversions, ) -> (Amount, Conversions) { @@ -1017,7 +1037,9 @@ impl ShieldedContext { let target_asset_type = self .decode_asset_type(client.clone(), asset_type) .await - .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) + .map(|(addr, denom, _epoch)| { + make_asset_type(target_epoch, &addr, denom) + }) .unwrap_or(asset_type); let at_target_asset_type = asset_type == target_asset_type; if let (Some((conv, _wit, usage)), false) = ( @@ -1261,8 +1283,8 @@ impl ShieldedContext { self.decode_asset_type(client.clone(), *asset_type).await; // Only assets with the target timestamp count match decoded { - Some((addr, epoch)) if epoch == target_epoch => { - res += &Amount::from_pair(addr, *val).unwrap() + Some((addr, denom, epoch)) if epoch == target_epoch => { + res += &Amount::from_pair((addr, denom), *val).unwrap() } _ => {} } @@ -1276,40 +1298,31 @@ impl ShieldedContext { &mut self, client: HttpClient, amt: Amount, - ) -> Amount<(Address, Epoch)> { + ) -> Amount<(Address, MaspDenom, Epoch)> { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client.clone(), *asset_type).await; // Only assets with the target timestamp count - if let Some((addr, epoch)) = decoded { - res += &Amount::from_pair((addr, epoch), *val).unwrap() + if let Some((addr, denom, epoch)) = decoded { + res += &Amount::from_pair((addr, denom, epoch), *val).unwrap() } } res } } -/// Make asset type corresponding to given address and epoch -fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { - // Typestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) - .try_to_vec() - .expect("token should serialize"); - // Generate the unique asset identifier from the unique token address - AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") -} - /// Convert Namada amount and token type to MASP equivalents fn convert_amount( epoch: Epoch, token: &Address, - val: token::Amount, + val: u64, + denom: MaspDenom, ) -> (AssetType, Amount) { - let asset_type = make_asset_type(epoch, token); + let asset_type = make_asset_type(epoch, token, denom); // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + let amount = Amount::from_nonnegative(asset_type, val) .expect("invalid value for amount"); (asset_type, amount) } @@ -1335,106 +1348,13 @@ where let epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; - let amt: u64 = args.amount.into(); let memo: Option = None; - - // Now we build up the transaction within this object - let mut builder = Builder::::new(0u32); - // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &args.token, args.amount); - // Transactions with transparent input and shielded output // may be affected if constructed close to epoch boundary let mut epoch_sensitive: bool = false; - // If there are shielded inputs - if let Some(sk) = spending_key { - // Transaction fees need to match the amount in the wrapper Transfer - // when MASP source is used - let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); - builder.set_fee(fee.clone())?; - // If the gas is coming from the shielded pool, then our shielded inputs - // must also cover the gas fee - let required_amt = if shielded_gas { amount + fee } else { amount }; - // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = ctx - .collect_unspent_notes( - args.tx.ledger_address.clone(), - &to_viewing_key(&sk).vk, - required_amt, - epoch, - ) - .await; - // Commit the notes found to our transaction - for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; - } - // Commit the conversion notes used during summation - for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; - } - } - } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of the - // parent Transfer object is used to validate fund availability - let secp_sk = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); - let secp_ctx = secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; - builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type, - value: amt, - script_pubkey: script, - }, - )?; - } - // Now handle the outputs of this transaction - // If there is a shielded output - if let Some(pa) = payment_address { - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - builder.add_sapling_output( - ovk_opt, - pa.into(), - asset_type, - amt, - memo.clone(), - )?; - } else { - epoch_sensitive = false; - // Embed the transparent target address into the shielded transaction so - // that it can be signed - let target_enc = args - .target - .address() - .expect("target address should be transparent") - .try_to_vec() - .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( - target_enc.as_ref(), - )); - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - asset_type, - amt, - )?; - } + + // Now we build up the transaction within this object + let mut builder = Builder::::new(0u32); let prover = if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) { let params_dir = PathBuf::from(params_dir); @@ -1446,28 +1366,76 @@ where LocalTxProver::with_default_location() .expect("unable to load MASP Parameters") }; - // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; - - // If epoch has changed, recalculate shielded outputs to match new epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &args.token, args.amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - + let fee = if spending_key.is_some() { + // Transaction fees need to match the amount in the wrapper Transfer + // when MASP source is used. This amount should be <= `u64::MAX`. + let (_, fee) = convert_amount( + epoch, + &args.tx.fee_token, + MaspDenom::Zero.denominate(&args.tx.fee_amount), + MaspDenom::Zero, + ); + builder.set_fee(fee.clone())?; + fee + } else { + // No transfer fees come from the shielded transaction for non-MASP + // sources + builder.set_fee(Amount::zero())?; + Amount::zero() + }; + let mut epoch_transitions = vec![]; + // break up a transfer into a number of transfers with suitable + // denominations + for denom in MaspDenom::iter() { + let denom_amount = denom.denominate(&args.amount); + if denom_amount == 0 { + continue; + } + // Convert transaction amount into MASP types + let (asset_type, amount) = + convert_amount(epoch, &args.token, denom_amount, denom); + + // If there are shielded inputs + if let Some(sk) = spending_key { + // If the gas is coming from the shielded pool, then our shielded + // inputs must also cover the gas fee + let required_amt = if shielded_gas { + amount + fee.clone() + } else { + amount + }; + // Locate unspent notes that can help us meet the transaction amount + let (_, unspent_notes, used_convs) = ctx + .collect_unspent_notes( + args.tx.ledger_address.clone(), + &to_viewing_key(&sk).vk, + required_amt, + epoch, + ) + .await; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { + builder.add_sapling_spend( + sk, + diversifier, + note, + merkle_path, + )?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if *value > 0 { + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; + } + } + } else { + // We add a dummy UTXO to our transaction, but only the source of + // the parent Transfer object is used to validate fund + // availability let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) .expect("secret key"); let secp_ctx = @@ -1478,33 +1446,124 @@ where let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( + epoch_sensitive = true; + builder.add_transparent_input( secp_sk, OutPoint::new([0u8; 32], 0), TxOut { - asset_type: new_asset_type, - value: amt, + asset_type, + value: denom_amount, script_pubkey: script, }, )?; + } - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); + // Now handle the outputs of this transaction + // If there is a shielded output + if let Some(pa) = payment_address { + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + builder.add_sapling_output( + ovk_opt, + pa.into(), + asset_type, + denom_amount, + memo.clone(), + )?; + } else { + epoch_sensitive = false; + // Embed the transparent target address into the shielded + // transaction so that it can be signed + let target_enc = args + .target + .address() + .expect("target address should be transparent") + .try_to_vec() + .expect("target address encoding"); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + target_enc.as_ref(), + )); + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + asset_type, + denom_amount, + )?; } - } + if epoch_sensitive { + let new_epoch = + ctx.query_epoch(args.tx.ledger_address.clone()).await; + + // If epoch has changed, recalculate shielded outputs to match new + // epoch + if new_epoch != epoch { + // Hack: build new shielded transfer with updated outputs + let mut replay_builder = + Builder::::new(0u32); + replay_builder.set_fee(Amount::zero())?; + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + let (new_asset_type, _) = + convert_amount(new_epoch, &args.token, denom_amount, denom); + replay_builder.add_sapling_output( + ovk_opt, + payment_address.unwrap().into(), + new_asset_type, + denom_amount, + memo.clone(), + )?; + + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + &secp_pk, + )); + let script = + TransparentAddress::PublicKey(hash.into()).script(); + replay_builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type: new_asset_type, + value: denom_amount, + script_pubkey: script, + }, + )?; + let (replay_tx, _) = + replay_builder.build(consensus_branch_id, &prover)?; + epoch_transitions.push(( + replay_tx, + asset_type, + new_asset_type, + denom_amount, + )); + } + } + } + // Build and return the constructed transaction + let mut tx = builder.build(consensus_branch_id, &prover); + tx = tx.map(|(t, tm)| { + let mut temp = t.deref().clone(); + let mut shielded_outputs = vec![]; + for (replay_tx, asset_type, new_asset_type, denom_amount) in + epoch_transitions + { + let mut replay_outputs = replay_tx.shielded_outputs.clone(); + shielded_outputs.append(&mut replay_outputs); + temp.value_balance = temp.value_balance.reject(asset_type) + - Amount::from_pair(new_asset_type, denom_amount).unwrap(); + } + temp.shielded_outputs = shielded_outputs; + (temp.freeze().unwrap(), tm) + }); tx.map(Some) } -pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let parsed_args = args.parse_from_context(&mut ctx); +pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { + let parsed_args = args.parse_from_context(&mut ctx).await; let source = parsed_args.source.effective_address(); let target = parsed_args.target.effective_address(); // Check that the source address exists on chain @@ -1557,12 +1616,18 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { - if balance < args.amount { + if balance < parsed_args.amount { + let balance_amount = format_denominated_amount( + &client, + &parsed_args.token, + balance, + ) + .await; eprintln!( "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, parsed_args.token, args.amount, balance + source, parsed_args.token, args.amount, balance_amount ); if !args.tx.force { safe_exit(1) @@ -1590,19 +1655,19 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { // TODO Refactor me, we shouldn't rely on any specific token here. ( TxSigningKey::SecretKey(masp_tx_key()), - 0.into(), + token::Amount::default(), ctx.native_token.clone(), ) } else if source == masp_addr { ( TxSigningKey::SecretKey(masp_tx_key()), - args.amount, + parsed_args.amount, parsed_args.token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.to_address()), - args.amount, + parsed_args.amount, parsed_args.token.clone(), ) }; @@ -1621,13 +1686,25 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { #[cfg(not(feature = "mainnet"))] let is_source_faucet = rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; - + let denom = unwrap_client_response( + RPC.vp() + .token() + .denomination(&client, &parsed_args.token) + .await, + ) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, defaulting to zero \ + decimal places" + ); + 0.into() + }); let transfer = token::Transfer { source, target, token, sub_prefix, - amount, + amount: DenominatedAmount { amount, denom }, key, shielded: { let spending_key = parsed_args.source.spending_key(); @@ -1733,11 +1810,16 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { { Some(balance) => { if balance < args.amount { + let formatted_amount = + format_denominated_amount(&client, &token, args.amount) + .await; + let formatted_balance = + format_denominated_amount(&client, &token, balance).await; eprintln!( "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, token, args.amount, balance + source, token, formatted_amount, formatted_balance ); if !args.tx.force { safe_exit(1) @@ -1763,7 +1845,7 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { }; let token = Some(Coin { denom, - amount: args.amount.to_string(), + amount: Uint::from(args.amount).to_string(), }); // this height should be that of the destination chain, not this chain @@ -1923,7 +2005,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await .unwrap_or_default(); if balance - < token::Amount::from(governance_parameters.min_proposal_fund) + < token::Amount::native_whole( + governance_parameters.min_proposal_fund, + ) { eprintln!( "Address {} doesn't have enough funds.", @@ -2329,13 +2413,15 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { - println!("Found source balance {}", balance); + println!("Found source balance {}", balance.to_string_native()); if balance < args.amount { eprintln!( "The balance of the source {} is lower than the amount to \ be transferred. Amount to transfer is {} and the balance \ is {}.", - bond_source, args.amount, balance + bond_source, + args.amount.to_string_native(), + balance.to_string_native() ); if !args.tx.force { safe_exit(1) @@ -2392,13 +2478,13 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let bond_amount = rpc::query_bond(&client, &bond_source, &validator, None).await; - println!("BOND AMOUNT REMAINING IS {}", bond_amount); - if args.amount > bond_amount { eprintln!( "The total bonds of the source {} is lower than the amount to be \ unbonded. Amount to unbond is {} and the total bonds is {}.", - bond_source, args.amount, bond_amount + bond_source, + args.amount.to_string_native(), + bond_amount.to_string_native() ); if !args.tx.force { safe_exit(1) @@ -2459,7 +2545,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { Some(epoch), ) .await; - if tokens == 0.into() { + if tokens.is_zero() { eprintln!( "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", @@ -2470,7 +2556,10 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { safe_exit(1) } } else { - println!("Found {tokens} tokens that can be withdrawn."); + println!( + "Found {} tokens that can be withdrawn.", + tokens.to_string_native() + ); println!("Submitting transaction to withdraw them..."); } diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 0dbb385208..d072395e45 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -324,7 +324,7 @@ pub mod genesis_config { pos_data: GenesisValidator { address: Address::decode(config.address.as_ref().unwrap()) .unwrap(), - tokens: token::Amount::whole(config.tokens.unwrap_or_default()), + tokens: token::Amount::native_whole(config.tokens.unwrap_or_default()), consensus_key: config .consensus_public_key .as_ref() @@ -373,7 +373,7 @@ pub mod genesis_config { .unwrap() .to_dkg_public_key() .unwrap(), - non_staked_balance: token::Amount::whole( + non_staked_balance: token::Amount::native_whole( config.non_staked_balance.unwrap_or_default(), ), validator_vp_code_path: validator_vp_config.filename.to_owned(), @@ -463,7 +463,7 @@ pub mod genesis_config { } } }, - token::Amount::whole(*amount), + token::Amount::native_whole(*amount), ) }) .collect(), @@ -895,7 +895,7 @@ pub fn genesis() -> Genesis { let validator = Validator { pos_data: GenesisValidator { address, - tokens: token::Amount::whole(200_000), + tokens: token::Amount::native_whole(200_000), consensus_key: consensus_keypair.ref_to(), commission_rate: dec!(0.05), max_commission_rate_change: dec!(0.01), @@ -903,7 +903,7 @@ pub fn genesis() -> Genesis { account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), dkg_public_key: dkg_keypair.public(), - non_staked_balance: token::Amount::whole(100_000), + non_staked_balance: token::Amount::native_whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), @@ -925,7 +925,7 @@ pub fn genesis() -> Genesis { pos_gain_d: dec!(0.1), staked_ratio: dec!(0.0), pos_inflation_amount: 0, - wrapper_tx_fees: Some(token::Amount::whole(0)), + wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), @@ -958,8 +958,8 @@ pub fn genesis() -> Genesis { let implicit_accounts = vec![ImplicitAccount { public_key: wallet::defaults::daewon_keypair().ref_to(), }]; - let default_user_tokens = token::Amount::whole(1_000_000); - let default_key_tokens = token::Amount::whole(1_000); + let default_user_tokens = token::Amount::native_whole(1_000_000); + let default_key_tokens = token::Amount::native_whole(1_000); let balances: HashMap = HashMap::from_iter([ // established accounts' balances (wallet::defaults::albert_address(), default_user_tokens), diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 7b5e788801..f6a9bb9641 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -10,6 +10,7 @@ use masp_primitives::sapling::Node; use crate::types::address::Address; use crate::types::storage::Epoch; +use crate::types::token::MaspDenom; /// A representation of the conversion state #[derive(Debug, Default, BorshSerialize, BorshDeserialize)] @@ -19,7 +20,10 @@ pub struct ConversionState { /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, /// Map assets to their latest conversion and position in Merkle tree - pub assets: BTreeMap, + pub assets: BTreeMap< + AssetType, + ((Address, MaspDenom), Epoch, AllowedConversion, usize), + >, } // This is only enabled when "wasm-runtime" is on, because we're using rayon @@ -55,16 +59,17 @@ where // with the zeroth epoch to minimize the number of convert notes clients // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. - let reward_asset_bytes = (address::nam(), 0u64) + let reward_asset_bytes = (address::nam(), MaspDenom::Zero, 0u64) .try_to_vec() .expect("unable to serialize address and epoch"); let reward_asset = AssetType::new(reward_asset_bytes.as_ref()) .expect("unable to derive asset identifier"); // Conversions from the previous to current asset for each address - let mut current_convs = BTreeMap::::new(); + let mut current_convs = + BTreeMap::<(Address, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for (addr, reward) in &masp_rewards { - // Dispence a transparent reward in parallel to the shielded rewards + for ((addr, denom), reward) in &masp_rewards { + // Dispense a transparent reward in parallel to the shielded rewards let addr_bal: token::Amount = wl_storage .read(&token::balance_key(addr, &masp_addr))? .unwrap_or_default(); @@ -76,12 +81,18 @@ where // Provide an allowed conversion from previous timestamp. The // negative sign allows each instance of the old asset to be // cancelled out/replaced with the new asset - let old_asset = - encode_asset_type(addr.clone(), wl_storage.storage.last_epoch); - let new_asset = - encode_asset_type(addr.clone(), wl_storage.storage.block.epoch); - current_convs.insert( + let old_asset = encode_asset_type( + addr.clone(), + *denom, + wl_storage.storage.last_epoch, + ); + let new_asset = encode_asset_type( addr.clone(), + *denom, + wl_storage.storage.block.epoch, + ); + current_convs.insert( + (addr.clone(), *denom), (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + MaspAmount::from_pair(new_asset, reward.1).unwrap() + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) @@ -91,7 +102,7 @@ where wl_storage.storage.conversion_state.assets.insert( old_asset, ( - addr.clone(), + (addr.clone(), *denom), wl_storage.storage.last_epoch, MaspAmount::zero().into(), 0, @@ -119,9 +130,9 @@ where .into_par_iter() .with_min_len(notes_per_thread_min) .with_max_len(notes_per_thread_max) - .map(|(idx, (addr, _epoch, conv, pos))| { + .map(|(idx, (asset, _epoch, conv, pos))| { // Use transitivity to update conversion - *conv += current_convs[addr].clone(); + *conv += current_convs[asset].clone(); // Update conversion position to leaf we are about to create *pos = idx; // The merkle tree need only provide the conversion commitment, @@ -162,15 +173,18 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for addr in masp_rewards.keys() { + for (addr, denom) in masp_rewards.keys() { // Add the decoding entry for the new asset type. An uncommited // node position is used since this is not a conversion. - let new_asset = - encode_asset_type(addr.clone(), wl_storage.storage.block.epoch); + let new_asset = encode_asset_type( + addr.clone(), + *denom, + wl_storage.storage.block.epoch, + ); wl_storage.storage.conversion_state.assets.insert( new_asset, ( - addr.clone(), + (addr.clone(), *denom), wl_storage.storage.block.epoch, MaspAmount::zero().into(), wl_storage.storage.conversion_state.tree.size(), @@ -195,8 +209,12 @@ where } /// Construct MASP asset type with given epoch for given token -pub fn encode_asset_type(addr: Address, epoch: Epoch) -> AssetType { - let new_asset_bytes = (addr, epoch.0) +pub fn encode_asset_type( + addr: Address, + denom: MaspDenom, + epoch: Epoch, +) -> AssetType { + let new_asset_bytes = (addr, denom, epoch.0) .try_to_vec() .expect("unable to serialize address and epoch"); AssetType::new(new_asset_bytes.as_ref()) diff --git a/core/src/ledger/testnet_pow.rs b/core/src/ledger/testnet_pow.rs index bed2273a70..88b3aac392 100644 --- a/core/src/ledger/testnet_pow.rs +++ b/core/src/ledger/testnet_pow.rs @@ -511,7 +511,7 @@ mod test_with_tx_and_vp_env { fn test_challenge_and_solution() -> storage_api::Result<()> { let faucet_address = address::testing::established_address_1(); let difficulty = Difficulty::try_new(1).unwrap(); - let withdrawal_limit = token::Amount::whole(1_000); + let withdrawal_limit = token::Amount::native_whole(1_000); let mut tx_env = TestTxEnv::default(); diff --git a/core/src/types/address.rs b/core/src/types/address.rs index ae96842f8c..9c3985ff81 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -14,6 +14,7 @@ use thiserror::Error; use crate::types::key; use crate::types::key::PublicKeyHash; +use crate::types::token::MaspDenom; /// The length of an established [`Address`] encoded with Borsh. pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 45; @@ -575,15 +576,15 @@ pub fn tokens() -> HashMap { /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap { +pub fn masp_rewards() -> HashMap<(Address, MaspDenom), (u64, u64)> { vec![ - (nam(), (0, 100)), - (btc(), (1, 100)), - (eth(), (2, 100)), - (dot(), (3, 100)), - (schnitzel(), (4, 100)), - (apfel(), (5, 100)), - (kartoffel(), (6, 100)), + ((nam(), MaspDenom::Zero), (0, 100)), + ((btc(), MaspDenom::Zero), (1, 100)), + ((eth(), MaspDenom::Zero), (2, 100)), + ((dot(), MaspDenom::Zero), (3, 100)), + ((schnitzel(), MaspDenom::Zero), (4, 100)), + ((apfel(), MaspDenom::Zero), (5, 100)), + ((kartoffel(), MaspDenom::Zero), (6, 100)), ] .into_iter() .collect() diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 818851a726..5a426f3013 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -293,6 +293,41 @@ impl DenominatedAmount { } string } + + /// Find the minimal precision that holds this value losslessly. + /// This equates to stripping trailing zeros after the decimal + /// place. + pub fn canonical(self) -> Self { + let mut value = self.amount.raw; + let ten = Uint::from(10); + let mut denom = self.denom.0; + for _ in 0..self.denom.0 { + let (div, rem) = value.div_mod(ten); + if rem == Uint::zero() { + value = div; + denom -= 1; + } + } + Self { + amount: Amount { raw: value }, + denom: denom.into(), + } + } + + /// Attempt to increase the precision of an amount. Can fail + /// if the resulting amount does not fit into 256 bits. + pub fn increase_precision(self, denom: Denomination) -> Option { + if denom.0 < self.denom.0 { + return None; + } + Uint::from(10) + .checked_pow(Uint::from(denom.0 - self.denom.0)) + .and_then(|scaling| self.amount.raw.checked_mul(scaling)) + .map(|amount| Self { + amount: Amount { raw: amount }, + denom, + }) + } } impl Display for DenominatedAmount { @@ -307,11 +342,41 @@ impl FromStr for DenominatedAmount { type Err = AmountParseError; fn from_str(s: &str) -> Result { - let decimal = - Decimal::from_str(s).map_err(AmountParseError::InvalidDecimal)?; - let denom = Denomination(decimal.scale() as u8); + let precision = s.find('.').map(|pos| s.len() - pos); + let digits = s + .chars() + .filter_map(|c| { + if c.is_numeric() { + c.to_digit(10).map(Uint::from) + } else { + None + } + }) + .rev() + .collect::>(); + if digits.len() != s.len() && precision.is_none() + || digits.len() != s.len() - 1 && precision.is_some() + { + return Err(AmountParseError::NotNumeric); + } + if digits.len() > 77 { + return Err(AmountParseError::ScaleTooLarge( + digits.len() as u32, + 77, + )); + } + let mut value = Uint::default(); + let ten = Uint::from(10); + for (pow, digit) in digits.into_iter().enumerate() { + value = ten + .checked_pow(Uint::from(pow)) + .and_then(|scaling| scaling.checked_mul(digit)) + .and_then(|scaled| value.checked_add(scaled)) + .ok_or(AmountParseError::InvalidRange)?; + } + let denom = Denomination(precision.unwrap_or_default() as u8); Ok(Self { - amount: Amount::from_decimal(decimal, denom)?, + amount: Amount { raw: value }, denom, }) } @@ -399,8 +464,8 @@ impl TryFrom for u128 { fn try_from(value: Amount) -> Result { let Uint(arr) = value.raw; - for i in 2..4 { - if arr[i] != 0 { + for word in arr.iter().skip(2) { + if *word != 0 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "Integer overflow when casting to u128", @@ -529,6 +594,8 @@ pub enum AmountParseError { "Could not convert from string, expected an unsigned 256-bit integer." )] FromString, + #[error("Could not parse string as a correctly formatted number.")] + NotNumeric, } impl From for Change { @@ -543,6 +610,12 @@ impl From for Amount { } } +impl From for Uint { + fn from(amount: Amount) -> Self { + amount.raw + } +} + /// The four possible u64 words in a [`Uint`]. /// Used for converting to MASP amounts. #[derive( @@ -819,27 +892,34 @@ mod tests { /// starting to lose precision. #[test] fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { - let amount = Amount::from(raw_amount); + let amount = Amount::from_uint(raw_amount, NATIVE_MAX_DECIMAL_PLACES).expect("Test failed"); // A round-trip conversion to and from Decimal should be an identity - let decimal = Decimal::from(amount); - let identity = Amount::from(decimal); + let decimal = Decimal::from(raw_amount); + let identity = Amount::from_decimal(decimal, NATIVE_MAX_DECIMAL_PLACES).expect("Test failed"); assert_eq!(amount, identity); } } #[test] fn test_token_display() { - let max = Amount::from(u64::MAX); - assert_eq!("18446744073709.551615", max.to_string()); - - let whole = Amount::from(u64::MAX / NATIVE_SCALE * NATIVE_SCALE); - assert_eq!("18446744073709", whole.to_string()); - - let trailing_zeroes = Amount::from(123000); - assert_eq!("0.123", trailing_zeroes.to_string()); - - let zero = Amount::from(0); - assert_eq!("0", zero.to_string()); + let max = Amount::from_uint(u64::MAX, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"); + assert_eq!("18446744073709.551615", max.to_string_native()); + + let whole = Amount::from_uint( + u64::MAX / NATIVE_SCALE * NATIVE_SCALE, + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Test failed"); + assert_eq!("18446744073709", whole.to_string_native()); + + let trailing_zeroes = + Amount::from_uint(123000, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"); + assert_eq!("0.123", trailing_zeroes.to_string_native()); + + let zero = Amount::default(); + assert_eq!("0", zero.to_string_native()); } #[test] diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index d23d0ca2cb..604dc1dd78 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -20,6 +20,7 @@ pub mod wrapper_tx { use crate::types::transaction::{ hash_tx, EncryptionKey, Hash, TxError, TxType, }; + use crate::types::uint::Uint; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -81,39 +82,46 @@ pub mod wrapper_tx { BorshDeserialize, BorshSchema, )] - #[serde(from = "u64")] - #[serde(into = "u64")] + #[serde(from = "Uint")] + #[serde(into = "Uint")] pub struct GasLimit { - multiplier: u64, + multiplier: Uint, } impl GasLimit { /// We refund unused gas up to GAS_LIMIT_RESOLUTION - pub fn refund_amount(&self, used_gas: u64) -> Amount { - Amount::native_whole(if used_gas < (u64::from(self) - GAS_LIMIT_RESOLUTION) { - // we refund only up to GAS_LIMIT_RESOLUTION - GAS_LIMIT_RESOLUTION - } else if used_gas >= u64::from(self) { - // Gas limit was under estimated, no refund - 0 - } else { - // compute refund - u64::from(self) - used_gas - }) + pub fn refund_amount(&self, used_gas: Uint) -> Amount { + Amount::from_uint( + if used_gas + < (Uint::from(self) - Uint::from(GAS_LIMIT_RESOLUTION)) + { + // we refund only up to GAS_LIMIT_RESOLUTION + Uint::from(GAS_LIMIT_RESOLUTION) + } else if used_gas >= Uint::from(self) { + // Gas limit was under estimated, no refund + Uint::from(0) + } else { + // compute refund + Uint::from(self) - used_gas + }, + 0, + ) + .unwrap() } } /// Round the input number up to the next highest multiple /// of GAS_LIMIT_RESOLUTION - impl From for GasLimit { - fn from(amount: u64) -> GasLimit { - if GAS_LIMIT_RESOLUTION * (amount / GAS_LIMIT_RESOLUTION) < amount { + impl From for GasLimit { + fn from(amount: Uint) -> GasLimit { + let gas_limit_resolution = Uint::from(GAS_LIMIT_RESOLUTION); + if gas_limit_resolution * (amount / gas_limit_resolution) < amount { GasLimit { - multiplier: (amount / GAS_LIMIT_RESOLUTION) + 1, + multiplier: (amount / gas_limit_resolution) + 1, } } else { GasLimit { - multiplier: (amount / GAS_LIMIT_RESOLUTION), + multiplier: (amount / gas_limit_resolution), } } } @@ -123,21 +131,20 @@ pub mod wrapper_tx { /// of GAS_LIMIT_RESOLUTION impl From for GasLimit { fn from(amount: Amount) -> GasLimit { - // TODO: this may panic. - GasLimit::from(u128::try_from(amount).unwrap() as u64) + GasLimit::from(Uint::from(amount)) } } /// Get back the gas limit as a raw number - impl From<&GasLimit> for u64 { - fn from(limit: &GasLimit) -> u64 { + impl From<&GasLimit> for Uint { + fn from(limit: &GasLimit) -> Uint { limit.multiplier * GAS_LIMIT_RESOLUTION } } /// Get back the gas limit as a raw number - impl From for u64 { - fn from(limit: GasLimit) -> u64 { + impl From for Uint { + fn from(limit: GasLimit) -> Uint { limit.multiplier * GAS_LIMIT_RESOLUTION } } @@ -145,7 +152,8 @@ pub mod wrapper_tx { /// Get back the gas limit as a raw number, viewed as an Amount impl From for Amount { fn from(limit: GasLimit) -> Amount { - Amount::native_whole(limit.multiplier * GAS_LIMIT_RESOLUTION) + Amount::from_uint(limit.multiplier * GAS_LIMIT_RESOLUTION, 0) + .unwrap() } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 01d315829c..4d0d19484b 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -8,6 +8,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; use serde::{Deserialize, Serialize}; use uint::construct_uint; + use crate::types::token; construct_uint! { @@ -51,7 +52,9 @@ pub const MAX_SIGNED_VALUE: Uint = Uint([u64::MAX, u64::MAX, u64::MAX, 9223372036854775807]); /// A signed 256 big integer. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, +)] pub struct SignedUint(Uint); impl SignedUint { @@ -90,7 +93,8 @@ impl SignedUint { impl From for SignedUint { fn from(val: u64) -> Self { - SignedUint::try_from(Uint::from(val)).expect("A u64 will always fit in this type") + SignedUint::try_from(Uint::from(val)) + .expect("A u64 will always fit in this type") } } @@ -161,4 +165,4 @@ impl Sub for SignedUint { fn sub(self, rhs: Self) -> Self::Output { self + (-rhs) } -} \ No newline at end of file +} diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c65a539e72..e3fdcde9f2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -20,7 +20,7 @@ pub mod types; // pub mod validation; //#[cfg(test)] -//mod tests; +// mod tests; use core::fmt::Debug; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -42,6 +42,7 @@ use namada_core::types::key::{ }; pub use namada_core::types::storage::Epoch; use namada_core::types::token; +use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use once_cell::unsync::Lazy; use parameters::PosParams; use rust_decimal::Decimal; @@ -56,7 +57,6 @@ use storage::{ ReverseOrdTokenAmount, UnbondDetails, WeightedValidator, }; use thiserror::Error; -use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use types::{ BelowCapacityValidatorSet, BelowCapacityValidatorSets, Bonds, CommissionRates, ConsensusValidator, ConsensusValidatorSet, @@ -729,7 +729,10 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Bonding token amount {} at epoch {current_epoch}", amount.to_string_native()); + tracing::debug!( + "Bonding token amount {} at epoch {current_epoch}", + amount.to_string_native() + ); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; if let Some(source) = source { @@ -1329,7 +1332,10 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Unbonding token amount {} at epoch {current_epoch}", amount.to_string_native()); + tracing::debug!( + "Unbonding token amount {} at epoch {current_epoch}", + amount.to_string_native() + ); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; tracing::debug!( @@ -1375,7 +1381,8 @@ where if amount > remaining_at_pipeline { return Err(UnbondError::UnbondAmountGreaterThanBond( token::Amount::from_change(amount).to_string_native(), - token::Amount::from_change(remaining_at_pipeline).to_string_native(), + token::Amount::from_change(remaining_at_pipeline) + .to_string_native(), ) .into()); } @@ -1591,7 +1598,8 @@ where ) = unbond?; tracing::debug!( - "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", amount.to_string_native() + "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", + amount.to_string_native() ); // TODO: worry about updating this later after PR 740 perhaps @@ -1615,10 +1623,14 @@ where .unwrap_or_default() { let slash_rate = slash_type.get_slash_rate(¶ms); - let to_slash = token::Amount::from_uint(decimal_mult_u128( - slash_rate, - u128::from(amount), - ), NATIVE_MAX_DECIMAL_PLACES).expect("Amount out of bounds"); + let to_slash = token::Amount::from_uint( + decimal_mult_u128( + slash_rate, + u128::try_from(amount).expect("Amount out of bounds"), + ), + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Amount out of bounds"); slashed += to_slash; } } @@ -1626,7 +1638,10 @@ where unbonds_to_remove.push((withdraw_epoch, start_epoch)); } withdrawable_amount -= slashed; - tracing::debug!("Withdrawing total {}", withdrawable_amount.to_string_native()); + tracing::debug!( + "Withdrawing total {}", + withdrawable_amount.to_string_native() + ); // Remove the unbond data from storage for (withdraw_epoch, start_epoch) in unbonds_to_remove { @@ -1726,9 +1741,13 @@ where read_validator_stake(storage, params, validator, current_epoch)? .unwrap_or_default(); let slashed_amount = Amount::from_uint( - decimal_mult_u128(rate, u128::from(current_stake)), - NATIVE_MAX_DECIMAL_PLACES - ).expect("Amount out of bounds"); + decimal_mult_u128( + rate, + u128::try_from(current_stake).expect("Amount out of bounds"), + ), + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Amount out of bounds"); let token_change = -token::Change::from(slashed_amount); // Update validator sets and deltas at the pipeline length @@ -1869,7 +1888,8 @@ where continue; } let current_slashed = - mult_change_to_amount(slash_type.get_slash_rate(params), delta).change(); + mult_change_to_amount(slash_type.get_slash_rate(params), delta) + .change(); let delta = token::Amount::from_change(delta - current_slashed); total += delta; if bond_epoch <= epoch { @@ -1917,7 +1937,8 @@ where ) = validator.unwrap(); tracing::debug!( - "Consensus validator address {address}, stake {}", cur_stake.to_string_native() + "Consensus validator address {address}, stake {}", + cur_stake.to_string_native() ); // Check if the validator was consensus in the previous epoch with @@ -1937,13 +1958,17 @@ where .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, - u128::from(prev_validator_stake) as u64, + u128::try_from(prev_validator_stake) + .expect("Amount out of bounds") + as u64, ) }); let cur_tm_voting_power = Lazy::new(|| { into_tm_voting_power( params.tm_votes_per_token, - u128::from(cur_stake) as u64, + u128::try_from(cur_stake) + .expect("Amount out of bounds") + as u64, ) }); @@ -1980,7 +2005,9 @@ where ); Some(ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key, - bonded_stake: u128::from(cur_stake) as u64, + bonded_stake: u128::try_from(cur_stake) + .expect("Amount out of bounds") + as u64, })) }); let cur_below_capacity_validators = @@ -1999,7 +2026,8 @@ where let cur_stake = token::Amount::from(cur_stake); tracing::debug!( - "Below-capacity validator address {address}, stake {}", cur_stake.to_string_native() + "Below-capacity validator address {address}, stake {}", + cur_stake.to_string_native() ); let prev_tm_voting_power = previous_epoch @@ -2012,7 +2040,9 @@ where .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, - u128::from(prev_validator_stake) as u64, + u128::try_from(prev_validator_stake) + .expect("Amount out of bounds") + as u64, ) }) .unwrap_or_default(); diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 7a8af3eebb..70759250ed 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -19,10 +19,10 @@ use namada_core::types::address::Address; use namada_core::types::key::common; use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; +use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; pub use rev_order::ReverseOrdTokenAmount; use rust_decimal::prelude::{Decimal, ToPrimitive}; use rust_decimal::RoundingStrategy; -use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use crate::parameters::PosParams; @@ -242,7 +242,8 @@ impl Display for WeightedValidator { write!( f, "{} with bonded stake {}", - self.address, self.bonded_stake.to_string_native() + self.address, + self.bonded_stake.to_string_native() ) } } @@ -469,21 +470,17 @@ pub fn mult_change_to_amount( ) -> token::Amount { // this function is used for slashing calculations. We want to err // on the side of slashing more, not less. - let dec = dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); - Amount::from_decimal( - dec, - NATIVE_MAX_DECIMAL_PLACES - ).unwrap() * change.abs() + let dec = + dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); + Amount::from_decimal(dec, NATIVE_MAX_DECIMAL_PLACES).unwrap() * change.abs() } /// Multiply a value of type Decimal with one of type Amount and then return the /// truncated Amount pub fn mult_amount(dec: Decimal, amount: token::Amount) -> token::Amount { - let dec = dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); - Amount::from_decimal( - dec, - NATIVE_MAX_DECIMAL_PLACES - ).unwrap() * amount + let dec = + dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); + Amount::from_decimal(dec, NATIVE_MAX_DECIMAL_PLACES).unwrap() * amount } /// Calculate voting power in the tendermint context (which is stored as i64) diff --git a/proof_of_stake/src/types/rev_order.rs b/proof_of_stake/src/types/rev_order.rs index 19749fabd4..57619941d4 100644 --- a/proof_of_stake/src/types/rev_order.rs +++ b/proof_of_stake/src/types/rev_order.rs @@ -54,7 +54,8 @@ impl std::str::FromStr for ReverseOrdTokenAmount { type Err = token::AmountParseError; fn from_str(s: &str) -> Result { - let amount = token::Amount::from_str(s, token::NATIVE_MAX_DECIMAL_PLACES)?; + let amount = + token::Amount::from_str(s, token::NATIVE_MAX_DECIMAL_PLACES)?; Ok(Self(amount)) } } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index e460301330..0f34024da4 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -122,7 +122,7 @@ where changes.insert(sub_prefix, change + this_change); } } - if changes.iter().all(|(_, c)| c.is_zero()) { + if changes.iter().all(|(_, c)| c.is_zero()) { return Ok(true); } else { return Err(Error::TokenTransfer( @@ -191,7 +191,8 @@ where } let token = ibc_storage::token(&data.denom) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; + let amount = + Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; let denom = if let Some(denom) = data .denom @@ -276,7 +277,8 @@ where .map_err(Error::DecodingPacketData)?; let token = ibc_storage::token(&data.denom) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; + let amount = + Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; let prefix = format!( "{}/{}/", @@ -334,7 +336,8 @@ where .map_err(Error::DecodingPacketData)?; let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; - let amount = Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; + let amount = + Amount::from_string_precise(&data.amount).map_err(Error::Amount)?; // check the denom field let prefix = format!( diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 30e6a48fb9..f97e438763 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -15,7 +15,6 @@ use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::Epoch; -use crate::types::token; /// Proposal structure holding votes information necessary to compute the /// outcome @@ -86,7 +85,8 @@ where { let params = read_pos_params(storage)?; let total_stake = read_total_stake(storage, ¶ms, epoch)?; - let total_stake = VotePower::from(total_stake); + let total_stake = + VotePower::try_from(total_stake).expect("Amount out of bounds"); let Votes { yay_validators, @@ -156,7 +156,8 @@ where epoch, )? .unwrap_or_default() - .into(); + .try_into() + .expect("Amount out of bounds"); yay_validators.insert(voter_address.clone(), amount); } else if !validators.contains(voter_address) { @@ -179,7 +180,8 @@ where .or_default(); entry.insert( validator.to_owned(), - VotePower::from(amount), + VotePower::try_from(amount) + .expect("Amount out of bounds"), ); } else { let entry = nay_delegators @@ -187,7 +189,8 @@ where .or_default(); entry.insert( validator.to_owned(), - VotePower::from(amount), + VotePower::try_from(amount) + .expect("Amount out of bounds"), ); } } diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index db095a2c00..f959a7a54c 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -828,265 +828,263 @@ macro_rules! router { ); } -/* -/// You can expand the `handlers!` macro invocation with e.g.: -/// ```shell -/// cargo expand ledger::queries::router::test_rpc_handlers --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib -/// ``` -#[cfg(test)] -mod test_rpc_handlers { - use borsh::BorshSerialize; - - use crate::ledger::queries::{ - EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, - }; - use crate::ledger::storage::{DBIter, StorageHasher, DB}; - use crate::ledger::storage_api::{self, ResultExt}; - use crate::types::storage::Epoch; - use crate::types::token; - - /// A little macro to generate boilerplate for RPC handler functions. - /// These are implemented to return their name as a String, joined by - /// slashes with their argument values turned `to_string()`, if any. - macro_rules! handlers { - ( - // name and params, if any - $( $name:ident $( ( $( $param:ident: $param_ty:ty ),* ) )? ),* - // optional trailing comma - $(,)? ) => { - $( - pub fn $name( - _ctx: RequestCtx<'_, D, H>, - $( $( $param: $param_ty ),* )? - ) -> storage_api::Result - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - { - let data = stringify!($name).to_owned(); - $( $( - let data = format!("{data}/{}", $param); - )* )? - Ok(data) - } - )* - }; - } - - // Generate handler functions for the router below - handlers!( - a, - b0i, - b0ii, - b1, - b2i(balance: token::Amount), - b3(a1: token::Amount, a2: token::Amount, a3: token::Amount), - b3i(a1: token::Amount, a2: token::Amount, a3: token::Amount), - b3ii(a1: token::Amount, a2: token::Amount, a3: token::Amount), - x, - y(untyped_arg: &str), - z(untyped_arg: &str), - ); - - /// This handler is hand-written, because the test helper macro doesn't - /// support optional args. - pub fn b3iii( - _ctx: RequestCtx<'_, D, H>, - a1: token::Amount, - a2: token::Amount, - a3: Option, - ) -> storage_api::Result - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - { - let data = "b3iii".to_owned(); - let data = format!("{data}/{}", a1); - let data = format!("{data}/{}", a2); - let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); - Ok(data) - } - - /// This handler is hand-written, because the test helper macro doesn't - /// support optional args. - pub fn b3iiii( - _ctx: RequestCtx<'_, D, H>, - a1: token::Amount, - a2: token::Amount, - a3: Option, - a4: Option, - ) -> storage_api::Result - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - { - let data = "b3iiii".to_owned(); - let data = format!("{data}/{}", a1); - let data = format!("{data}/{}", a2); - let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); - let data = a4.map(|a4| format!("{data}/{}", a4)).unwrap_or(data); - Ok(data) - } - - /// This handler is hand-written, because the test helper macro doesn't - /// support handlers with `with_options`. - pub fn c( - _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, - ) -> storage_api::Result - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - { - let data = "c".to_owned().try_to_vec().into_storage_result()?; - Ok(ResponseQuery { - data, - ..ResponseQuery::default() - }) - } -} - -/// You can expand the `router!` macro invocation with e.g.: -/// ```shell -/// cargo expand ledger::queries::router::test_rpc --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib -/// ``` -#[cfg(test)] -mod test_rpc { - use super::test_rpc_handlers::*; - use crate::types::storage::Epoch; - use crate::types::token; - - // Setup an RPC router for testing - router! {TEST_RPC, - ( "sub" ) = (sub TEST_SUB_RPC), - ( "a" ) -> String = a, - ( "b" ) = { - ( "0" ) = { - ( "i" ) -> String = b0i, - ( "ii" ) -> String = b0ii, - }, - ( "1" ) -> String = b1, - ( "2" ) = { - ( "i" / [balance: token::Amount] ) -> String = b2i, - }, - ( "3" / [a1: token::Amount] / [a2: token::Amount] ) = { - ( "i" / [a3: token:: Amount] ) -> String = b3i, - ( [a3: token:: Amount] ) -> String = b3, - ( [a3: token:: Amount] / "ii" ) -> String = b3ii, - ( [a3: opt token::Amount] / "iii" ) -> String = b3iii, - ( "iiii" / [a3: opt token::Amount] / "xyz" / [a4: opt Epoch] ) -> String = b3iiii, - }, - }, - ( "c" ) -> String = (with_options c), - } - - router! {TEST_SUB_RPC, - ( "x" ) -> String = x, - ( "y" / [untyped_arg] ) -> String = y, - ( "z" / [untyped_arg] ) -> String = z, - } -} - -#[cfg(test)] -mod test { - use super::test_rpc::TEST_RPC; - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; - use crate::ledger::storage_api; - use crate::types::storage::Epoch; - use crate::types::token; - - /// Test all the possible paths in `TEST_RPC` router. - #[tokio::test] - async fn test_router_macro() -> storage_api::Result<()> { - let client = TestClient::new(TEST_RPC); - - // Test request with an invalid path - let request = RequestQuery { - path: "/invalid".to_owned(), - ..RequestQuery::default() - }; - let ctx = RequestCtx { - event_log: &client.event_log, - wl_storage: &client.wl_storage, - vp_wasm_cache: client.vp_wasm_cache.clone(), - tx_wasm_cache: client.tx_wasm_cache.clone(), - storage_read_past_height_limit: None, - }; - let result = TEST_RPC.handle(ctx, &request); - assert!(result.is_err()); - - // Test requests to valid paths using the router's methods - - let result = TEST_RPC.a(&client).await.unwrap(); - assert_eq!(result, "a"); - - let result = TEST_RPC.b0i(&client).await.unwrap(); - assert_eq!(result, "b0i"); - - let result = TEST_RPC.b0ii(&client).await.unwrap(); - assert_eq!(result, "b0ii"); - - let result = TEST_RPC.b1(&client).await.unwrap(); - assert_eq!(result, "b1"); - - let balance = token::Amount::native_whole(123_000_000); - let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); - assert_eq!(result, format!("b2i/{balance}")); - - let a1 = token::Amount::native_whole(345); - let a2 = token::Amount::native_whole(123_000); - let a3 = token::Amount::native_whole(1_000_999); - let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); - assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); - - let result = TEST_RPC.b3i(&client, &a1, &a2, &a3).await.unwrap(); - assert_eq!(result, format!("b3i/{a1}/{a2}/{a3}")); - - let result = TEST_RPC.b3ii(&client, &a1, &a2, &a3).await.unwrap(); - assert_eq!(result, format!("b3ii/{a1}/{a2}/{a3}")); - - let result = - TEST_RPC.b3iii(&client, &a1, &a2, &Some(a3)).await.unwrap(); - assert_eq!(result, format!("b3iii/{a1}/{a2}/{a3}")); - - let result = TEST_RPC.b3iii(&client, &a1, &a2, &None).await.unwrap(); - assert_eq!(result, format!("b3iii/{a1}/{a2}")); - - let result = TEST_RPC - .b3iiii(&client, &a1, &a2, &Some(a3), &None) - .await - .unwrap(); - assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}")); - - let a4 = Epoch::from(10); - let result = TEST_RPC - .b3iiii(&client, &a1, &a2, &Some(a3), &Some(a4)) - .await - .unwrap(); - assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}/{a4}")); - - let result = TEST_RPC - .b3iiii(&client, &a1, &a2, &None, &None) - .await - .unwrap(); - assert_eq!(result, format!("b3iiii/{a1}/{a2}")); - - let result = TEST_RPC.c(&client, None, None, false).await.unwrap(); - assert_eq!(result.data, format!("c")); - - let result = TEST_RPC.test_sub_rpc().x(&client).await.unwrap(); - assert_eq!(result, format!("x")); - - let arg = "test123"; - let result = TEST_RPC.test_sub_rpc().y(&client, arg).await.unwrap(); - assert_eq!(result, format!("y/{arg}")); - - let arg = "test321"; - let result = TEST_RPC.test_sub_rpc().z(&client, arg).await.unwrap(); - assert_eq!(result, format!("z/{arg}")); - - Ok(()) - } -} -*/ \ No newline at end of file +// You can expand the `handlers!` macro invocation with e.g.: +// ```shell +// cargo expand ledger::queries::router::test_rpc_handlers --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib +// ``` +// #[cfg(test)] +// mod test_rpc_handlers { +// use borsh::BorshSerialize; +// +// use crate::ledger::queries::{ +// EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, +// }; +// use crate::ledger::storage::{DBIter, StorageHasher, DB}; +// use crate::ledger::storage_api::{self, ResultExt}; +// use crate::types::storage::Epoch; +// use crate::types::token; +// +// A little macro to generate boilerplate for RPC handler functions. +// These are implemented to return their name as a String, joined by +// slashes with their argument values turned `to_string()`, if any. +// macro_rules! handlers { +// ( +// name and params, if any +// $( $name:ident $( ( $( $param:ident: $param_ty:ty ),* ) )? ),* +// optional trailing comma +// $(,)? ) => { +// $( +// pub fn $name( +// _ctx: RequestCtx<'_, D, H>, +// $( $( $param: $param_ty ),* )? +// ) -> storage_api::Result +// where +// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +// H: 'static + StorageHasher + Sync, +// { +// let data = stringify!($name).to_owned(); +// $( $( +// let data = format!("{data}/{}", $param); +// )* )? +// Ok(data) +// } +// )* +// }; +// } +// +// Generate handler functions for the router below +// handlers!( +// a, +// b0i, +// b0ii, +// b1, +// b2i(balance: token::Amount), +// b3(a1: token::Amount, a2: token::Amount, a3: token::Amount), +// b3i(a1: token::Amount, a2: token::Amount, a3: token::Amount), +// b3ii(a1: token::Amount, a2: token::Amount, a3: token::Amount), +// x, +// y(untyped_arg: &str), +// z(untyped_arg: &str), +// ); +// +// This handler is hand-written, because the test helper macro doesn't +// support optional args. +// pub fn b3iii( +// _ctx: RequestCtx<'_, D, H>, +// a1: token::Amount, +// a2: token::Amount, +// a3: Option, +// ) -> storage_api::Result +// where +// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +// H: 'static + StorageHasher + Sync, +// { +// let data = "b3iii".to_owned(); +// let data = format!("{data}/{}", a1); +// let data = format!("{data}/{}", a2); +// let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); +// Ok(data) +// } +// +// This handler is hand-written, because the test helper macro doesn't +// support optional args. +// pub fn b3iiii( +// _ctx: RequestCtx<'_, D, H>, +// a1: token::Amount, +// a2: token::Amount, +// a3: Option, +// a4: Option, +// ) -> storage_api::Result +// where +// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +// H: 'static + StorageHasher + Sync, +// { +// let data = "b3iiii".to_owned(); +// let data = format!("{data}/{}", a1); +// let data = format!("{data}/{}", a2); +// let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); +// let data = a4.map(|a4| format!("{data}/{}", a4)).unwrap_or(data); +// Ok(data) +// } +// +// This handler is hand-written, because the test helper macro doesn't +// support handlers with `with_options`. +// pub fn c( +// _ctx: RequestCtx<'_, D, H>, +// _request: &RequestQuery, +// ) -> storage_api::Result +// where +// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +// H: 'static + StorageHasher + Sync, +// { +// let data = "c".to_owned().try_to_vec().into_storage_result()?; +// Ok(ResponseQuery { +// data, +// ..ResponseQuery::default() +// }) +// } +// } +// +// You can expand the `router!` macro invocation with e.g.: +// ```shell +// cargo expand ledger::queries::router::test_rpc --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib +// ``` +// #[cfg(test)] +// mod test_rpc { +// use super::test_rpc_handlers::*; +// use crate::types::storage::Epoch; +// use crate::types::token; +// +// Setup an RPC router for testing +// router! {TEST_RPC, +// ( "sub" ) = (sub TEST_SUB_RPC), +// ( "a" ) -> String = a, +// ( "b" ) = { +// ( "0" ) = { +// ( "i" ) -> String = b0i, +// ( "ii" ) -> String = b0ii, +// }, +// ( "1" ) -> String = b1, +// ( "2" ) = { +// ( "i" / [balance: token::Amount] ) -> String = b2i, +// }, +// ( "3" / [a1: token::Amount] / [a2: token::Amount] ) = { +// ( "i" / [a3: token:: Amount] ) -> String = b3i, +// ( [a3: token:: Amount] ) -> String = b3, +// ( [a3: token:: Amount] / "ii" ) -> String = b3ii, +// ( [a3: opt token::Amount] / "iii" ) -> String = b3iii, +// ( "iiii" / [a3: opt token::Amount] / "xyz" / [a4: opt Epoch] ) -> String = +// b3iiii, }, +// }, +// ( "c" ) -> String = (with_options c), +// } +// +// router! {TEST_SUB_RPC, +// ( "x" ) -> String = x, +// ( "y" / [untyped_arg] ) -> String = y, +// ( "z" / [untyped_arg] ) -> String = z, +// } +// } +// +// #[cfg(test)] +// mod test { +// use super::test_rpc::TEST_RPC; +// use crate::ledger::queries::testing::TestClient; +// use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; +// use crate::ledger::storage_api; +// use crate::types::storage::Epoch; +// use crate::types::token; +// +// Test all the possible paths in `TEST_RPC` router. +// #[tokio::test] +// async fn test_router_macro() -> storage_api::Result<()> { +// let client = TestClient::new(TEST_RPC); +// +// Test request with an invalid path +// let request = RequestQuery { +// path: "/invalid".to_owned(), +// ..RequestQuery::default() +// }; +// let ctx = RequestCtx { +// event_log: &client.event_log, +// wl_storage: &client.wl_storage, +// vp_wasm_cache: client.vp_wasm_cache.clone(), +// tx_wasm_cache: client.tx_wasm_cache.clone(), +// storage_read_past_height_limit: None, +// }; +// let result = TEST_RPC.handle(ctx, &request); +// assert!(result.is_err()); +// +// Test requests to valid paths using the router's methods +// +// let result = TEST_RPC.a(&client).await.unwrap(); +// assert_eq!(result, "a"); +// +// let result = TEST_RPC.b0i(&client).await.unwrap(); +// assert_eq!(result, "b0i"); +// +// let result = TEST_RPC.b0ii(&client).await.unwrap(); +// assert_eq!(result, "b0ii"); +// +// let result = TEST_RPC.b1(&client).await.unwrap(); +// assert_eq!(result, "b1"); +// +// let balance = token::Amount::native_whole(123_000_000); +// let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); +// assert_eq!(result, format!("b2i/{balance}")); +// +// let a1 = token::Amount::native_whole(345); +// let a2 = token::Amount::native_whole(123_000); +// let a3 = token::Amount::native_whole(1_000_999); +// let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); +// assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); +// +// let result = TEST_RPC.b3i(&client, &a1, &a2, &a3).await.unwrap(); +// assert_eq!(result, format!("b3i/{a1}/{a2}/{a3}")); +// +// let result = TEST_RPC.b3ii(&client, &a1, &a2, &a3).await.unwrap(); +// assert_eq!(result, format!("b3ii/{a1}/{a2}/{a3}")); +// +// let result = +// TEST_RPC.b3iii(&client, &a1, &a2, &Some(a3)).await.unwrap(); +// assert_eq!(result, format!("b3iii/{a1}/{a2}/{a3}")); +// +// let result = TEST_RPC.b3iii(&client, &a1, &a2, &None).await.unwrap(); +// assert_eq!(result, format!("b3iii/{a1}/{a2}")); +// +// let result = TEST_RPC +// .b3iiii(&client, &a1, &a2, &Some(a3), &None) +// .await +// .unwrap(); +// assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}")); +// +// let a4 = Epoch::from(10); +// let result = TEST_RPC +// .b3iiii(&client, &a1, &a2, &Some(a3), &Some(a4)) +// .await +// .unwrap(); +// assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}/{a4}")); +// +// let result = TEST_RPC +// .b3iiii(&client, &a1, &a2, &None, &None) +// .await +// .unwrap(); +// assert_eq!(result, format!("b3iiii/{a1}/{a2}")); +// +// let result = TEST_RPC.c(&client, None, None, false).await.unwrap(); +// assert_eq!(result.data, format!("c")); +// +// let result = TEST_RPC.test_sub_rpc().x(&client).await.unwrap(); +// assert_eq!(result, format!("x")); +// +// let arg = "test123"; +// let result = TEST_RPC.test_sub_rpc().y(&client, arg).await.unwrap(); +// assert_eq!(result, format!("y/{arg}")); +// +// let arg = "test321"; +// let result = TEST_RPC.test_sub_rpc().z(&client, arg).await.unwrap(); +// assert_eq!(result, format!("z/{arg}")); +// +// Ok(()) +// } +// } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 65ed48c3d1..f81f8433b0 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -4,7 +4,8 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::BlockResults; +use namada_core::types::storage::{BlockHeight, BlockResults}; +use namada_core::types::token::MaspDenom; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; @@ -20,6 +21,7 @@ use crate::types::transaction::TxResult; type Conversion = ( Address, + MaspDenom, Epoch, masp_primitives::transaction::components::Amount, MerklePath, @@ -29,6 +31,9 @@ router! {SHELL, // Epoch of the last committed block ( "epoch" ) -> Epoch = epoch, + // Epoch of the input block height + ( "epoch_at_height" / [height: BlockHeight]) -> Option = epoch_at_height, + // Raw storage access - read value ( "value" / [storage_key: storage::Key] ) -> Vec = (with_options storage_value), @@ -137,7 +142,7 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some((addr, epoch, conv, pos)) = ctx + if let Some(((addr, denom), epoch, conv, pos)) = ctx .wl_storage .storage .conversion_state @@ -146,6 +151,7 @@ where { Ok(( addr.clone(), + *denom, *epoch, Into::::into( conv.clone(), @@ -181,6 +187,17 @@ where Ok(data) } +fn epoch_at_height( + ctx: RequestCtx<'_, D, H>, + height: BlockHeight, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + Ok(ctx.wl_storage.storage.block.pred_epochs.get_epoch(height)) +} + /// Returns data with `vec![]` when the storage key is not found. For all /// borsh-encoded types, it is safe to check `data.is_empty()` to see if the /// value was found, except for unit - see `fn query_storage_value` in diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index ed83a492ec..74deb0838b 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -1,8 +1,7 @@ // Re-export to show in rustdoc! pub use pos::Pos; -pub use token::Token; - use pos::POS; +pub use token::Token; use token::TOKEN; mod pos; diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index 7f5e0a0b84..930a9412c5 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -1,9 +1,10 @@ -use namada_core::ledger::storage::{DB, DBIter, StorageHasher}; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; use namada_core::types::token; use namada_core::types::token::Denomination; + use crate::ledger::queries::RequestCtx; router! {TOKEN, @@ -16,9 +17,9 @@ fn denomination( ctx: RequestCtx<'_, D, H>, addr: Address, ) -> storage_api::Result> - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, { read_denom(ctx.wl_storage, &addr) -} \ No newline at end of file +} diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 753f666a30..44d775843f 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -36,7 +36,9 @@ pub fn transfer( None => token::balance_key(token, dest), }; let src_bal: Option = match src { - Address::Internal(InternalAddress::IbcMint) => Some(Amount::max_signed()), + Address::Internal(InternalAddress::IbcMint) => { + Some(Amount::max_signed()) + } Address::Internal(InternalAddress::IbcBurn) => { log_string("invalid transfer from the burn address"); unreachable!() diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs index dd54ab864f..1e302204a7 100644 --- a/vp_prelude/src/token.rs +++ b/vp_prelude/src/token.rs @@ -56,7 +56,9 @@ pub fn vp( let this_change = post.change() - pre.change(); change += this_change; // make sure that the spender approved the transaction - if !(this_change.non_negative() || verifiers.contains(owner) || *owner == address::masp()) + if !(this_change.non_negative() + || verifiers.contains(owner) + || *owner == address::masp()) { return reject(); } From 92976fd8b081713185f8c327df0c37b96679b3f3 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Apr 2023 17:30:25 +0200 Subject: [PATCH 12/69] [feat]: First compiling refactor. Now for testing --- apps/src/lib/cli.rs | 130 ++------ apps/src/lib/client/rpc.rs | 49 ++- apps/src/lib/client/tx.rs | 70 +++-- apps/src/lib/client/types.rs | 7 +- apps/src/lib/config/genesis.rs | 4 +- .../lib/node/ledger/shell/finalize_block.rs | 47 ++- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 6 +- .../lib/node/ledger/shell/prepare_proposal.rs | 8 +- .../lib/node/ledger/shell/process_proposal.rs | 40 +-- core/src/types/token.rs | 12 +- core/src/types/transaction/mod.rs | 11 +- core/src/types/transaction/wrapper.rs | 59 ++-- core/src/types/uint.rs | 23 ++ tests/src/e2e/helpers.rs | 14 +- tests/src/e2e/ibc_tests.rs | 12 +- tests/src/e2e/ledger_tests.rs | 81 +++-- tests/src/e2e/multitoken_tests.rs | 23 +- tests/src/e2e/multitoken_tests/helpers.rs | 6 +- tests/src/native_vp/pos.rs | 62 ++-- tests/src/vm_host_env/mod.rs | 15 +- wasm/Cargo.lock | 283 +++++++++++++++++- wasm/wasm_source/src/tx_bond.rs | 6 +- .../src/tx_change_validator_commission.rs | 6 +- wasm/wasm_source/src/tx_unbond.rs | 7 +- wasm/wasm_source/src/tx_withdraw.rs | 4 +- wasm/wasm_source/src/vp_implicit.rs | 102 ++++++- wasm/wasm_source/src/vp_masp.rs | 37 ++- wasm/wasm_source/src/vp_testnet_faucet.rs | 32 +- wasm/wasm_source/src/vp_user.rs | 98 +++++- wasm/wasm_source/src/vp_validator.rs | 96 +++++- wasm_for_tests/wasm_source/Cargo.lock | 283 +++++++++++++++++- wasm_for_tests/wasm_source/src/lib.rs | 2 +- 33 files changed, 1243 insertions(+), 394 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index fa99319ed1..c2c89d9285 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1567,7 +1567,6 @@ pub mod args { use std::str::FromStr; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; - use namada::ledger::queries::RPC; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::governance::ProposalVote; @@ -1579,7 +1578,6 @@ pub mod args { use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::GasLimit; use rust_decimal::Decimal; - use tendermint_rpc::HttpClient; use super::context::*; use super::utils::*; @@ -1886,64 +1884,32 @@ pub mod args { /// Transferred token address pub sub_prefix: Option, /// Transferred token amount - pub amount: token::DenominatedAmount, + pub amount: InputAmount, + } + + /// An amount read in by the cli + #[derive(Copy, Clone, Debug)] + pub enum InputAmount { + /// An amount whose representation has been validated + /// against the allowed representation in storage + Validated(token::DenominatedAmount), + /// The parsed amount read in from the cli. It has + /// not yet been validated against the allowed + /// representation in storage. + Unvalidated(token::DenominatedAmount), } impl TxTransfer { - pub async fn parse_from_context( - &mut self, + pub fn parse_from_context( + &self, ctx: &mut Context, ) -> ParsedTxTransferArgs { - let token = ctx.get(&self.token); ParsedTxTransferArgs { - tx: self.tx.parse_from_context(ctx).await, + tx: self.tx.parse_from_context(ctx), source: ctx.get_cached(&self.source), target: ctx.get(&self.target), - amount: self.validate_amount(&token).await, - token, - } - } - - /// Get the correct representation of the amount given the token type. - async fn validate_amount(&mut self, token: &Address) -> token::Amount { - let client = - HttpClient::new(self.tx.ledger_address.clone()).unwrap(); - let denom = RPC - .vp() - .token() - .denomination(&client, token) - .await - .unwrap_or_else(|err| { - eprintln!("Error in the query {}", err); - safe_exit(1) - }) - .unwrap_or_else(|| { - println!( - "No denomination found for token: {token}, the input \ - arguments could - not be parsed." - ); - safe_exit(1); - }); - let input_amount = self.amount.canonical(); - if denom < input_amount.denom { - println!( - "The input amount contained a higher precision than \ - allowed by {token}." - ); - safe_exit(1); - } else { - let validated = input_amount - .increase_precision(denom) - .unwrap_or_else(|| { - println!( - "The amount provided requires more the 256 bits \ - to represent." - ); - safe_exit(1); - }); - self.amount = validated; - self.amount.amount + amount: self.amount, + token: ctx.get(&self.token), } } } @@ -1955,7 +1921,7 @@ pub mod args { let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); let sub_prefix = SUB_PREFIX.parse(matches); - let amount = AMOUNT.parse(matches); + let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); Self { tx, source, @@ -2931,7 +2897,7 @@ pub mod args { /// save it in the wallet. pub initialized_account_alias: Option, /// The amount being payed to include the transaction - pub fee_amount: token::DenominatedAmount, + pub fee_amount: InputAmount, /// The token in which the fee is being paid pub fee_token: WalletAddress, /// The max amount of gas used to process tx @@ -2943,11 +2909,7 @@ pub mod args { } impl Tx { - pub async fn parse_from_context( - &mut self, - ctx: &mut Context, - ) -> ParsedTxArgs { - let fee_token = ctx.get(&self.fee_token); + pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { ParsedTxArgs { dry_run: self.dry_run, dump_tx: self.dump_tx, @@ -2957,8 +2919,8 @@ pub mod args { initialized_account_alias: self .initialized_account_alias .clone(), - fee_amount: self.validate_amount(&fee_token).await, - fee_token, + fee_amount: self.fee_amount, + fee_token: ctx.get(&self.fee_token), gas_limit: self.gas_limit.clone(), signing_key: self .signing_key @@ -2967,49 +2929,6 @@ pub mod args { signer: self.signer.as_ref().map(|signer| ctx.get(signer)), } } - - /// Get the correct representation of the fee amount given the token - /// type. - async fn validate_amount(&mut self, token: &Address) -> token::Amount { - let client = HttpClient::new(self.ledger_address.clone()).unwrap(); - let denom = RPC - .vp() - .token() - .denomination(&client, token) - .await - .unwrap_or_else(|err| { - eprintln!("Error in the query {}", err); - safe_exit(1) - }) - .unwrap_or_else(|| { - println!( - "No denomination found for token: {token}, the input \ - arguments could - not be parsed." - ); - safe_exit(1); - }); - let input_amount = self.fee_amount.canonical(); - if denom < input_amount.denom { - println!( - "The input amount contained a higher precision than \ - allowed by {token}." - ); - safe_exit(1); - } else { - let validated = input_amount - .increase_precision(denom) - .unwrap_or_else(|| { - println!( - "The amount provided requires more the 256 bits \ - to represent." - ); - safe_exit(1); - }); - self.fee_amount = validated; - self.fee_amount.amount - } - } } impl Args for Tx { @@ -3071,7 +2990,8 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = GAS_AMOUNT.parse(matches); + let fee_amount = + InputAmount::Unvalidated(GAS_AMOUNT.parse(matches)); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).amount.into(); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index c156e4ed8b..0e6d7d9ce9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -55,6 +55,7 @@ use namada::types::transaction::{ use namada::types::{address, storage, token}; use tokio::time::{Duration, Instant}; +use crate::cli::args::InputAmount; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; use crate::client::tx::{ @@ -224,11 +225,6 @@ pub async fn query_tx_deltas( .txs; for response_tx in txs { let height = BlockHeight(response_tx.height.value()); - let epoch = - query_epoch_at_height(&client, height).await.expect( - "This block height should not exceed that latest \ - committed block height", - ); let idx = TxIndex(response_tx.index); // Only process yet unprocessed transactions which have been // accepted by node VPs @@ -262,7 +258,7 @@ pub async fn query_tx_deltas( denom.denominate(&transfer.amount); if denominated != 0 { let tfer_delta = Amount::from_nonnegative( - (transfer.token.clone(), denom.into()), + (transfer.token.clone(), denom), denominated, ) .expect("invalid value for amount"); @@ -778,7 +774,7 @@ async fn print_balances( format!( "with {}: {}, owned by {}", sub_prefix, - format_denominated_amount(&client, &token, balance).await, + format_denominated_amount(client, token, balance).await, lookup_alias(ctx, owner) ), ), @@ -788,7 +784,7 @@ async fn print_balances( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount(&client, &token, balance) + format_denominated_amount(client, token, balance) .await, lookup_alias(ctx, owner) ), @@ -2813,3 +2809,40 @@ pub fn make_asset_type( // Generate the unique asset identifier from the unique token address AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") } + +/// Get the correct representation of the amount given the token type. +pub async fn validate_amount( + client: &HttpClient, + amount: InputAmount, + token: &Address, +) -> token::DenominatedAmount { + let input_amount = match amount { + InputAmount::Unvalidated(amt) => amt.canonical(), + InputAmount::Validated(amt) => return amt, + }; + let denom = unwrap_client_response( + RPC.vp().token().denomination(client, token).await, + ) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, the input arguments \ + could + not be parsed." + ); + cli::safe_exit(1); + }); + if denom < input_amount.denom { + println!( + "The input amount contained a higher precision than allowed by \ + {token}." + ); + cli::safe_exit(1); + } else { + input_amount.increase_precision(denom).unwrap_or_else(|| { + println!( + "The amount provided requires more the 256 bits to represent." + ); + cli::safe_exit(1); + }) + } +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6f2575bb0d..6e64df6114 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -40,7 +40,6 @@ use namada::ibc_proto::cosmos::base::v1beta1::Coin; use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp; use namada::ledger::pos::{CommissionPair, PosParams}; -use namada::ledger::queries::RPC; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ @@ -69,11 +68,12 @@ use tokio::time::{Duration, Instant}; use super::rpc; use super::types::ShieldedTransferContext; +use crate::cli::args::InputAmount; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{ format_denominated_amount, make_asset_type, query_conversion, - query_storage_value, unwrap_client_response, + query_storage_value, validate_amount, }; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; @@ -848,9 +848,11 @@ impl ShieldedContext { // Record the changes to the transparent accounts let mut transfer_delta = TransferDelta::new(); for denom in MaspDenom::iter() { - let transparent_delta = - Amount::from_nonnegative((tx.token.clone(), denom), denom.denominate(&tx.amount.amount)) - .expect("invalid value for amount"); + let transparent_delta = Amount::from_nonnegative( + (tx.token.clone(), denom), + denom.denominate(&tx.amount.amount), + ) + .expect("invalid value for amount"); if transparent_delta == Amount::zero() { continue; } @@ -1367,12 +1369,15 @@ where .expect("unable to load MASP Parameters") }; let fee = if spending_key.is_some() { + let InputAmount::Validated(fee) = args.tx.fee_amount else { + unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") + }; // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used. This amount should be <= `u64::MAX`. let (_, fee) = convert_amount( epoch, &args.tx.fee_token, - MaspDenom::Zero.denominate(&args.tx.fee_amount), + MaspDenom::Zero.denominate(&fee.amount), MaspDenom::Zero, ); builder.set_fee(fee.clone())?; @@ -1386,8 +1391,11 @@ where let mut epoch_transitions = vec![]; // break up a transfer into a number of transfers with suitable // denominations + let InputAmount::Validated(amt) = args.amount else { + unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") + }; for denom in MaspDenom::iter() { - let denom_amount = denom.denominate(&args.amount); + let denom_amount = denom.denominate(&amt.amount); if denom_amount == 0 { continue; } @@ -1562,8 +1570,10 @@ where tx.map(Some) } -pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { - let parsed_args = args.parse_from_context(&mut ctx).await; +pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { + let mut parsed_args: ParsedTxTransferArgs = + args.parse_from_context(&mut ctx); + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let source = parsed_args.source.effective_address(); let target = parsed_args.target.effective_address(); // Check that the source address exists on chain @@ -1597,6 +1607,17 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { safe_exit(1) } } + // validate the amount given + let validated_amount = + validate_amount(&client, parsed_args.amount, &parsed_args.token).await; + let validate_fee = validate_amount( + &client, + parsed_args.tx.fee_amount, + &parsed_args.tx.fee_token, + ) + .await; + parsed_args.amount = InputAmount::Validated(validated_amount); + parsed_args.tx.fee_amount = InputAmount::Validated(validate_fee); // Check source balance let (sub_prefix, balance_key) = match args.sub_prefix { Some(sub_prefix) => { @@ -1612,11 +1633,10 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { } None => (None, token::balance_key(&parsed_args.token, &source)), }; - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { - if balance < parsed_args.amount { + if balance < validated_amount.amount { let balance_amount = format_denominated_amount( &client, &parsed_args.token, @@ -1627,7 +1647,7 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, parsed_args.token, args.amount, balance_amount + source, parsed_args.token, validated_amount, balance_amount ); if !args.tx.force { safe_exit(1) @@ -1661,13 +1681,13 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { } else if source == masp_addr { ( TxSigningKey::SecretKey(masp_tx_key()), - parsed_args.amount, + validated_amount.amount, parsed_args.token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.to_address()), - parsed_args.amount, + validated_amount.amount, parsed_args.token.clone(), ) }; @@ -1686,25 +1706,15 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { #[cfg(not(feature = "mainnet"))] let is_source_faucet = rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; - let denom = unwrap_client_response( - RPC.vp() - .token() - .denomination(&client, &parsed_args.token) - .await, - ) - .unwrap_or_else(|| { - println!( - "No denomination found for token: {token}, defaulting to zero \ - decimal places" - ); - 0.into() - }); let transfer = token::Transfer { source, target, token, sub_prefix, - amount: DenominatedAmount { amount, denom }, + amount: DenominatedAmount { + amount, + denom: validated_amount.denom, + }, key, shielded: { let spending_key = parsed_args.source.spending_key(); @@ -1738,9 +1748,9 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { amount to be transferred and fees. Amount to \ transfer is {} {} and fees are {} {}.", parsed_args.source, - args.amount, + validated_amount, parsed_args.token, - args.tx.fee_amount, + validate_fee, parsed_args.tx.fee_token, ); safe_exit(1) diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs index d75d5a596c..c5acbc2ba5 100644 --- a/apps/src/lib/client/types.rs +++ b/apps/src/lib/client/types.rs @@ -4,12 +4,13 @@ use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; use masp_primitives::sapling::Node; use masp_primitives::transaction::components::Amount; use namada::types::address::Address; +use namada::types::key; use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::storage::Epoch; use namada::types::transaction::GasLimit; -use namada::types::{key, token}; use super::rpc; +use crate::cli::args::InputAmount; use crate::cli::{args, Context}; use crate::client::tx::Conversions; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -30,7 +31,7 @@ pub struct ParsedTxArgs { /// save it in the wallet. pub initialized_account_alias: Option, /// The amount being payed to include the transaction - pub fee_amount: token::Amount, + pub fee_amount: InputAmount, /// The token in which the fee is being paid pub fee_token: Address, /// The max amount of gas used to process tx @@ -52,7 +53,7 @@ pub struct ParsedTxTransferArgs { /// Transferred token address pub token: Address, /// Transferred token amount - pub amount: token::Amount, + pub amount: InputAmount, } #[async_trait(?Send)] diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index d072395e45..7dde5fbe5b 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -324,7 +324,9 @@ pub mod genesis_config { pos_data: GenesisValidator { address: Address::decode(config.address.as_ref().unwrap()) .unwrap(), - tokens: token::Amount::native_whole(config.tokens.unwrap_or_default()), + tokens: token::Amount::native_whole( + config.tokens.unwrap_or_default(), + ), consensus_key: config .consensus_public_key .as_ref() diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 336c4d9415..ea8fd012c2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -197,7 +197,9 @@ where .storage .write( &balance_key, - Amount::from(0).try_to_vec().unwrap(), + Amount::native_whole(0) + .try_to_vec() + .unwrap(), ) .unwrap(); tx_event["info"] = @@ -468,6 +470,7 @@ mod test_finalize_block { use namada::types::governance::ProposalVote; use namada::types::storage::Epoch; use namada::types::time::DurationSecs; + use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -497,7 +500,10 @@ mod test_finalize_block { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); // create some wrapper txs @@ -508,12 +514,16 @@ mod test_finalize_block { ); let wrapper = WrapperTx::new( Fee { - amount: MIN_FEE.into(), + amount: Amount::from_uint( + MIN_FEE, + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -581,12 +591,12 @@ mod test_finalize_block { ); let wrapper = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -639,12 +649,12 @@ mod test_finalize_block { ); let wrapper = WrapperTx { fee: Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: Default::default(), inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] @@ -699,7 +709,10 @@ mod test_finalize_block { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); // create two decrypted txs @@ -718,12 +731,16 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: MIN_FEE.into(), + amount: Amount::from_uint( + MIN_FEE, + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -755,12 +772,16 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: MIN_FEE.into(), + amount: Amount::from_uint( + MIN_FEE, + NATIVE_MAX_DECIMAL_PLACES, + ) + .expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 1198007bee..0bdaff27e2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -218,7 +218,7 @@ where // withdrawal limit defaults to 1000 NAM when not set let withdrawal_limit = genesis .faucet_withdrawal_limit - .unwrap_or_else(|| token::Amount::whole(1_000)); + .unwrap_or_else(|| token::Amount::native_whole(1_000)); testnet_pow::init_faucet_storage( &mut self.wl_storage, &address, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index c79d65a384..dbaf8610c4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -731,7 +731,7 @@ where &self.wl_storage, ) .expect("Must be able to read wrapper tx fees parameter"); - fees.unwrap_or(token::Amount::native_whole(MIN_FEE)) + fees.unwrap_or_else(|| token::Amount::native_whole(MIN_FEE)) } #[cfg(not(feature = "mainnet"))] @@ -1001,12 +1001,12 @@ mod test_utils { ); let wrapper = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: native_token, }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1783a127fd..933b8c15ba 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -224,12 +224,12 @@ mod test_prepare_proposal { Some( WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -284,12 +284,12 @@ mod test_prepare_proposal { })); let wrapper_tx = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 11deec9e13..0b8e29a151 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -215,7 +215,7 @@ mod test_process_proposal { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; - use namada::types::token::Amount; + use namada::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; @@ -238,12 +238,12 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -287,12 +287,13 @@ mod test_process_proposal { let timestamp = tx.timestamp; let mut wrapper = WrapperTx::new( Fee { - amount: 100.into(), + amount: Amount::from_uint(100, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -318,7 +319,7 @@ mod test_process_proposal { }; // we mount a malleability attack to try and remove the fee - new_wrapper.fee.amount = 0.into(); + new_wrapper.fee.amount = Default::default(); let new_data = TxType::Wrapper(new_wrapper) .try_to_vec() .expect("Test failed"); @@ -371,12 +372,13 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: 1.into(), + amount: Amount::from_uint(1, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -419,7 +421,7 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) + .write(&balance_key, Amount::native_whole(99).try_to_vec().unwrap()) .unwrap(); let tx = Tx::new( @@ -428,12 +430,12 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: Amount::whole(1_000_100), + amount: Amount::native_whole(1_000_100), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -478,12 +480,12 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: i.into(), + amount: Amount::native_whole(i as u64), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -548,12 +550,12 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -609,12 +611,12 @@ mod test_process_proposal { ); let mut wrapper = WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -664,12 +666,12 @@ mod test_process_proposal { let inner_tx = EncryptedTx::encrypt(&tx, pubkey); let wrapper = WrapperTx { fee: Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: Default::default(), inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 5a426f3013..cd49a4c0ef 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -924,9 +924,9 @@ mod tests { #[test] fn test_amount_checked_sub() { - let max = Amount::from(u64::MAX); - let one = Amount::from(1); - let zero = Amount::from(0); + let max = Amount::native_whole(u64::MAX); + let one = Amount::native_whole(1); + let zero = Amount::native_whole(0); assert_eq!(zero.checked_sub(zero), Some(zero)); assert_eq!(zero.checked_sub(one), None); @@ -939,9 +939,9 @@ mod tests { #[test] fn test_amount_checked_add() { - let max = Amount::from(u64::MAX); - let one = Amount::from(1); - let zero = Amount::from(0); + let max = Amount::native_whole(u64::MAX); + let one = Amount::native_whole(1); + let zero = Amount::native_whole(0); assert_eq!(zero.checked_add(zero), Some(zero)); assert_eq!(zero.checked_add(one), Some(one)); diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0e0a5e980e..8d59fd5a9f 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -342,6 +342,7 @@ pub mod tx_types { use super::*; use crate::types::address::nam; use crate::types::storage::Epoch; + use crate::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -423,12 +424,13 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: nam(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -460,12 +462,13 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: nam(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 604dc1dd78..472a05ce86 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -73,6 +73,7 @@ pub mod wrapper_tx { /// This struct only stores the multiple of GAS_LIMIT_RESOLUTION, /// not the raw amount #[derive( + Default, Debug, Clone, PartialEq, @@ -295,7 +296,9 @@ pub mod wrapper_tx { /// Test that serializing converts GasLimit to u64 correctly #[test] fn test_gas_limit_roundtrip() { - let limit = GasLimit { multiplier: 1 }; + let limit = GasLimit { + multiplier: 1.into(), + }; // Test serde roundtrip let js = serde_json::to_string(&limit).expect("Test failed"); assert_eq!(js, format!("{}", GAS_LIMIT_RESOLUTION)); @@ -321,31 +324,49 @@ pub mod wrapper_tx { .expect("Test failed"); let limit: GasLimit = serde_json::from_str(&js).expect("Test failed"); - assert_eq!(limit, GasLimit { multiplier: 2 }); + assert_eq!( + limit, + GasLimit { + multiplier: 2.into() + } + ); } /// Test that refund is calculated correctly #[test] fn test_gas_limit_refund() { - let limit = GasLimit { multiplier: 1 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION - 1); - assert_eq!(refund, Amount::from(1u64)); + let limit = GasLimit { + multiplier: 1.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION - 1)); + assert_eq!(refund, Amount::from_uint(1, 0).expect("Test failed")); } /// Test that we don't refund more than GAS_LIMIT_RESOLUTION #[test] fn test_gas_limit_too_high_no_refund() { - let limit = GasLimit { multiplier: 2 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION - 1); - assert_eq!(refund, Amount::from(GAS_LIMIT_RESOLUTION)); + let limit = GasLimit { + multiplier: 2.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION - 1)); + assert_eq!( + refund, + Amount::from_uint(GAS_LIMIT_RESOLUTION, 0) + .expect("Test failed") + ); } /// Test that if gas usage was underestimated, we issue no refund #[test] fn test_gas_limit_too_low_no_refund() { - let limit = GasLimit { multiplier: 1 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION + 1); - assert_eq!(refund, Amount::from(0u64)); + let limit = GasLimit { + multiplier: 1.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION + 1)); + assert_eq!(refund, Amount::default()); } } @@ -354,6 +375,7 @@ pub mod wrapper_tx { use super::*; use crate::proto::SignedTxData; use crate::types::address::nam; + use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -375,12 +397,13 @@ pub mod wrapper_tx { let wrapper = WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: nam(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -403,12 +426,13 @@ pub mod wrapper_tx { let mut wrapper = WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: nam(), }, &gen_keypair(), Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -437,12 +461,13 @@ pub mod wrapper_tx { // the signed tx let mut tx = WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + .expect("Test failed"), token: nam(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 4d0d19484b..443a69caea 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -166,3 +166,26 @@ impl Sub for SignedUint { self + (-rhs) } } + +impl From for SignedUint { + fn from(val: i128) -> Self { + if val < 0 { + let abs = Self((-val).into()); + -abs + } else { + Self(val.into()) + } + } +} + +impl From for SignedUint { + fn from(val: i64) -> Self { + Self::from(val as i128) + } +} + +impl From for SignedUint { + fn from(val: i32) -> Self { + Self::from(val as i128) + } +} diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 943962f620..a7b2714548 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -16,6 +16,7 @@ use namada::types::storage::Epoch; use namada::types::token; use namada_apps::config::genesis::genesis_config; use namada_apps::config::{Config, TendermintMode}; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::setup::{ self, sleep, NamadaBgCmd, Test, ENV_VAR_DEBUG, @@ -184,12 +185,13 @@ pub fn find_bonded_stake( .rsplit_once(' ') .unwrap() .1; - token::Amount::from_str(bonded_stake_str).map_err(|e| { - eyre!(format!( - "Bonded stake: {} parsed from {}, Error: {}\n\nOutput: {}", - bonded_stake_str, matched, e, unread - )) - }) + token::Amount::from_str(bonded_stake_str, NATIVE_MAX_DECIMAL_PLACES) + .map_err(|e| { + eyre!(format!( + "Bonded stake: {} parsed from {}, Error: {}\n\nOutput: {}", + bonded_stake_str, matched, e, unread + )) + }) } /// Get the last committed epoch. diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 6c2c35d89a..9f6196b97d 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -701,7 +701,7 @@ fn transfer_token( ALBERT, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_a, None, None, @@ -820,7 +820,7 @@ fn transfer_back( BERTHA, &receiver, NAM, - &Amount::whole(50000), + &Amount::native_whole(50000), port_channel_id_b, Some(sub_prefix), None, @@ -879,7 +879,7 @@ fn transfer_timeout( ALBERT, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_a, None, Some(Duration::new(5, 0)), @@ -924,7 +924,7 @@ fn transfer_timeout_on_close( BERTHA, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_b, None, None, @@ -973,7 +973,7 @@ fn try_transfer_on_close( ALBERT, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_a, None, None, @@ -1095,7 +1095,7 @@ fn transfer( let rpc = get_actor_rpc(test, &Who::Validator(0)); let receiver = receiver.to_string(); - let amount = amount.to_string(); + let amount = amount.to_string_native(); let port_id = port_channel_id.port_id.to_string(); let channel_id = port_channel_id.channel_id.to_string(); let mut tx_args = vec![ diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7649b379f2..b532cc7102 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -26,6 +26,7 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; +use namada_core::types::token::{MaspDenom, NATIVE_MAX_DECIMAL_PLACES}; use serde_json::json; use setup::constants::*; @@ -459,7 +460,7 @@ fn ledger_txs_and_queries() -> Result<()> { } let christel = find_address(&test, CHRISTEL)?; // as setup in `genesis/e2e-tests-single-node.toml` - let christel_balance = token::Amount::whole(1000000); + let christel_balance = token::Amount::native_whole(1000000); let nam = find_address(&test, NAM)?; let storage_key = token::balance_key(&nam, &christel).to_string(); let query_args_and_expected_response = vec![ @@ -1051,8 +1052,10 @@ fn masp_incentives() -> Result<()> { client.exp_string("BTC: 20")?; client.assert_success(); - let amt20 = token::Amount::from_str("20").unwrap(); - let amt30 = token::Amount::from_str("30").unwrap(); + let amt20 = + token::Amount::from_uint(20, NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let amt30 = + token::Amount::from_uint(30, NATIVE_MAX_DECIMAL_PLACES).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1071,7 +1074,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep1.0 - ep0.0)) + .to_string_native() ))?; client.assert_success(); @@ -1092,7 +1096,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep1.0 - ep0.0)) + .to_string_native() ))?; client.assert_success(); @@ -1134,7 +1139,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep2.0 - ep0.0)) + .to_string_native() ))?; client.assert_success(); @@ -1155,7 +1161,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep2.0 - ep0.0)) + .to_string_native(), ))?; client.assert_success(); @@ -1258,7 +1265,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0) + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep4.0 - ep3.0)) + .to_string_native(), ))?; client.assert_success(); @@ -1280,8 +1288,11 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)) + (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + * (ep4.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + * (ep4.0 - ep3.0))) + .to_string_native() ))?; client.assert_success(); @@ -1349,7 +1360,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0) + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep.0 - ep3.0)) + .to_string_native() ))?; client.assert_success(); @@ -1372,8 +1384,11 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0)) + (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + * (ep.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + * (ep.0 - ep3.0))) + .to_string_native() ))?; client.assert_success(); @@ -1439,7 +1454,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) + .to_string_native() ))?; client.assert_success(); @@ -1461,8 +1477,11 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) + (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + * (ep5.0 - ep3.0))) + .to_string_native() ))?; client.assert_success(); @@ -1486,7 +1505,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) + ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) + .to_string_native() ))?; client.assert_success(); @@ -1507,7 +1527,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep5.0 - ep3.0)) + .to_string_native() ))?; client.assert_success(); @@ -1529,8 +1550,11 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "NAM: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) + (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + * (ep5.0 - ep3.0))) + .to_string_native() ))?; client.assert_success(); @@ -1551,7 +1575,9 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), + &((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + * (ep5.0 - ep3.0)) + .to_string_native(), "--signer", BERTHA, "--ledger-address", @@ -1578,7 +1604,9 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), + &((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + * (ep6.0 - ep0.0)) + .to_string_native(), "--signer", ALBERT, "--ledger-address", @@ -1673,7 +1701,10 @@ fn invalid_transactions() -> Result<()> { target: find_address(&test, ALBERT)?, token: find_address(&test, NAM)?, sub_prefix: None, - amount: token::Amount::whole(1), + amount: token::DenominatedAmount { + amount: token::Amount::native_whole(1), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }, key: None, shielded: None, }; @@ -2167,7 +2198,9 @@ fn pos_init_validator() -> Result<()> { // 7. Check the new validator's bonded stake let bonded_stake = find_bonded_stake(&test, new_validator, &validator_one_rpc)?; - assert_eq!(bonded_stake, token::Amount::from_str("11_000.5").unwrap()); + let amount = + token::Amount::from_str("11_000.5", NATIVE_MAX_DECIMAL_PLACES).unwrap(); + assert_eq!(bonded_stake, amount); Ok(()) } diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs index 460395cc59..0f2b15d877 100644 --- a/tests/src/e2e/multitoken_tests.rs +++ b/tests/src/e2e/multitoken_tests.rs @@ -26,7 +26,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { println!("Fake multitoken VP established at {}", multitoken_vp_addr); let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::from(100_000_000); + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); helpers::mint_red_tokens( &test, &rpc_addr, @@ -35,7 +35,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { &albert_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // make a transfer from Albert to Bertha, signed by Christel - this should // be rejected @@ -70,7 +70,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { ALBERT, BERTHA, ALBERT, - &token::Amount::from(10_000_000), + &token::Amount::native_whole(10_000_000), )?; authorized_transfer.exp_string("Transaction applied with result")?; authorized_transfer.exp_string("Transaction is valid")?; @@ -110,7 +110,8 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { established_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -122,7 +123,7 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { &established_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer to Albert from the established account let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( &test, @@ -197,7 +198,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { )?; let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::from(100_000_000); + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); helpers::mint_red_tokens( &test, &rpc_addr, @@ -206,7 +207,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { &albert_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer from Albert to the established account let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( @@ -280,7 +281,8 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { established_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -302,7 +304,8 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { receiver_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -314,7 +317,7 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { &established_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index fb1138ca82..73e230862b 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -1,11 +1,11 @@ //! Helpers for use in multitoken tests. use std::path::PathBuf; -use std::str::FromStr; use borsh::BorshSerialize; use color_eyre::eyre::Result; use eyre::Context; use namada_core::types::address::Address; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_core::types::{storage, token}; use namada_test_utils::tx_data::TxWriteData; use namada_tx_prelude::storage::KeySeg; @@ -138,7 +138,7 @@ pub fn attempt_red_tokens_transfer( signer: &str, amount: &token::Amount, ) -> Result { - let amount = amount.to_string(); + let amount = amount.to_string_native(); let transfer_args = vec![ "transfer", "--token", @@ -184,6 +184,6 @@ pub fn fetch_red_token_balance( println!("Got balance for {}: {}", owner_alias, matched); let decimal = decimal_regex.find(&matched).unwrap().as_str(); client_balance.assert_success(); - token::Amount::from_str(decimal) + token::Amount::from_str(decimal, NATIVE_MAX_DECIMAL_PLACES) .wrap_err(format!("Failed to parse {}", matched)) } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index d3e3773f47..b2e6166703 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -517,7 +517,7 @@ mod tests { validator: &Address, amount: token::Amount, ) -> bool { - let raw_amount: u64 = amount.into(); + let raw_amount: u128 = amount.try_into().unwrap(); let mut total_bonds: u64 = 0; for action in self.all_valid_actions().into_iter() { match action { @@ -528,8 +528,8 @@ mod tests { } => { if owner == &bond_owner && validator == &bond_validator { - let raw_amount: u64 = amount.into(); - total_bonds += raw_amount; + let raw_amount: u128 = amount.try_into().unwrap(); + total_bonds += raw_amount as u64; } } ValidPosAction::Unbond { @@ -539,15 +539,15 @@ mod tests { } => { if owner == &bond_owner && validator == &bond_validator { - let raw_amount: u64 = amount.into(); - total_bonds -= raw_amount; + let raw_amount: u128 = amount.try_into().unwrap(); + total_bonds -= raw_amount as u64; } } _ => {} } } - total_bonds >= raw_amount + total_bonds as u128 >= raw_amount } /// Find if the given owner and validator has unbonds that are ready to @@ -591,6 +591,9 @@ pub mod testing { use namada::types::key::RefTo; use namada::types::storage::Epoch; use namada::types::{address, key, token}; + use namada_core::types::token::{ + Amount, Change, NATIVE_MAX_DECIMAL_PLACES, + }; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; use rust_decimal::Decimal; @@ -655,14 +658,14 @@ pub mod testing { Bond { owner: Address, validator: Address, - delta: i128, + delta: Change, offset: DynEpochOffset, }, /// Add tokens unbonded from a bond at unbonding offset Unbond { owner: Address, validator: Address, - delta: i128, + delta: Change, }, /// Withdraw tokens from an unbond at the current epoch WithdrawUnbond { @@ -670,12 +673,12 @@ pub mod testing { validator: Address, }, TotalDeltas { - delta: i128, + delta: Change, offset: Either, }, ValidatorSet { validator: Address, - token_delta: i128, + token_delta: Change, offset: DynEpochOffset, }, ValidatorConsensusKey { @@ -685,7 +688,7 @@ pub mod testing { }, ValidatorDeltas { validator: Address, - delta: i128, + delta: Change, offset: DynEpochOffset, }, ValidatorState { @@ -693,7 +696,7 @@ pub mod testing { state: ValidatorState, }, StakingTokenPosBalance { - delta: i128, + delta: Change, }, ValidatorAddressRawHash { address: Address, @@ -764,7 +767,11 @@ pub mod testing { arb_validator, ) .prop_map(|(amount, owner, validator)| ValidPosAction::Bond { - amount: amount.into(), + amount: Amount::from_uint( + amount, + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(), owner, validator, }); @@ -794,12 +801,19 @@ pub mod testing { // them let arb_unbond = arb_current_bond.prop_flat_map( |(bond_id, current_bond_amount)| { - let current_bond_amount: u64 = - current_bond_amount.into(); + let current_bond_amount = + >::try_into( + current_bond_amount, + ) + .unwrap() as u64; // Unbond an arbitrary amount up to what's available (0..current_bond_amount).prop_map(move |amount| { ValidPosAction::Unbond { - amount: amount.into(), + amount: Amount::from_uint( + amount, + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(), owner: bond_id.source.clone(), validator: bond_id.validator.clone(), } @@ -898,7 +912,7 @@ pub mod testing { }, PosStorageChange::ValidatorSet { validator: address.clone(), - token_delta: 0, + token_delta: 0.into(), offset, }, PosStorageChange::ValidatorConsensusKey { @@ -911,7 +925,7 @@ pub mod testing { }, PosStorageChange::ValidatorDeltas { validator: address.clone(), - delta: 0, + delta: 0.into(), offset, }, PosStorageChange::ValidatorCommissionRate { @@ -1317,13 +1331,11 @@ pub mod testing { ); let mut balance: token::Amount = tx::ctx().read(&balance_key).unwrap().unwrap_or_default(); - if delta < 0 { - let to_spend: u64 = (-delta).try_into().unwrap(); - let to_spend: token::Amount = to_spend.into(); + if !delta.non_negative() { + let to_spend = token::Amount::from_change(delta); balance.spend(&to_spend); } else { - let to_recv: u64 = delta.try_into().unwrap(); - let to_recv: token::Amount = to_recv.into(); + let to_recv = token::Amount::from_change(delta); balance.receive(&to_recv); } tx::ctx().write(&balance_key, balance).unwrap(); @@ -1378,7 +1390,7 @@ pub mod testing { pub fn apply_validator_set_change( _validator: Address, - _token_delta: i128, + _token_delta: Change, _offset: DynEpochOffset, _current_epoch: Epoch, _params: &PosParams, @@ -1557,7 +1569,7 @@ pub mod testing { PosStorageChange::Bond { owner, validator, - delta, + delta: delta.into(), offset, }, ] diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 04a545e8b1..8754928b3e 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -35,6 +35,7 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; + use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -1312,7 +1313,9 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); let key = token::multitoken_balance_key(&key_prefix, &sender); - let init_bal = Amount::from(1_000_000_000u64); + let init_bal = + Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) + .unwrap(); writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { @@ -1373,7 +1376,9 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); let key = token::multitoken_balance_key(&key_prefix, &receiver); - let init_bal = Amount::from(1_000_000_000u64); + let init_bal = + Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) + .unwrap(); writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { @@ -1452,7 +1457,11 @@ mod tests { &key_prefix, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let val = Amount::from(1_000_000_000u64).try_to_vec().unwrap(); + let val = + Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) + .unwrap() + .try_to_vec() + .unwrap(); tx_host_env::with(|env| { env.wl_storage .storage diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f0ec20a6a7..30dd14eb1a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -287,7 +287,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -374,10 +374,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -502,7 +514,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -533,6 +545,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -1334,6 +1352,50 @@ dependencies = [ "version_check", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -1378,11 +1440,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1434,6 +1508,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -2012,7 +2092,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2032,6 +2112,55 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2085,6 +2214,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2115,7 +2253,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -2285,7 +2423,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2511,10 +2649,12 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "ibc", "ibc-proto", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1", @@ -2537,6 +2677,7 @@ dependencies = [ "thiserror", "tonic-build", "tracing", + "uint", "zeroize", ] @@ -2791,7 +2932,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -2818,6 +2959,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.45.0" @@ -3011,6 +3178,19 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3020,6 +3200,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3209,6 +3399,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3495,6 +3691,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3529,6 +3735,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3893,6 +4105,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -4447,6 +4669,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.6.2" @@ -4638,9 +4877,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy 0.2.2", @@ -5312,6 +5551,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5321,6 +5569,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5360,7 +5617,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index b949731a70..b4cfd51842 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -186,7 +186,7 @@ mod tests { // Check that the validator set and deltas are unchanged before pipeline // length and that they are updated between the pipeline and // unbonding lengths - if bond.amount == token::Amount::from(0) { + if bond.amount.is_zero() { // None of the optional storage fields should have been updated assert_eq!(epoched_validator_set_pre, epoched_validator_set_post); assert_eq!( @@ -217,7 +217,7 @@ mod tests { ..=pos_params.unbonding_len as usize { let expected_stake = - i128::from(initial_stake) + i128::from(bond.amount); + initial_stake.change() + bond.amount.change(); assert_eq!( epoched_validator_stake_post[epoch], token::Amount::from_change(expected_stake), @@ -341,7 +341,7 @@ mod tests { // Generate initial stake (initial_stake in token::testing::arb_amount_ceiled((i64::MAX/8) as u64)) // Use the initial stake to limit the bond amount - (bond in arb_bond(((i64::MAX/8) as u64) - u64::from(initial_stake)), + (bond in arb_bond(((i64::MAX/8) as u64) - u128::try_from(initial_stake).unwrap() as u64), // Use the generated initial stake too initial_stake in Just(initial_stake), ) -> (token::Amount, transaction::pos::Bond) { diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 3b77c9197c..5e68993097 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -68,7 +68,11 @@ mod tests { let consensus_key = key::testing::keypair_1().ref_to(); let genesis_validators = [GenesisValidator { address: commission_change.validator.clone(), - tokens: token::Amount::from(1_000_000), + tokens: token::Amount::from_uint( + 1_000_000, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(), consensus_key, commission_rate: initial_rate, max_commission_rate_change: max_change, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 42fa0666bc..33d8653a12 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -202,7 +202,7 @@ mod tests { let expected_amount_before_pipeline = if is_delegation { // When this is a delegation, there will be no bond until pipeline - 0.into() + token::Amount::default() } else { // Before pipeline offset, there can only be self-bond initial_stake @@ -276,7 +276,7 @@ mod tests { { let epoch = pos_params.unbonding_len + 1; let expected_stake = - i128::from(initial_stake) - i128::from(unbond.amount); + initial_stake.change() - unbond.amount.change(); assert_eq!( read_validator_stake( ctx(), @@ -405,7 +405,8 @@ mod tests { token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( |initial_stake| { // Use the initial stake to limit the bond amount - let unbond = arb_unbond(u64::from(initial_stake)); + let unbond = + arb_unbond(u128::try_from(initial_stake).unwrap() as u64); // Use the generated initial stake too too (Just(initial_stake), unbond) }, diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 80c0f00265..a16dc81572 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -14,7 +14,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let slashed = ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator)?; if slashed != token::Amount::default() { - debug_log!("New withdrawal slashed for {}", slashed); + debug_log!("New withdrawal slashed for {}", slashed.to_string_native()); } Ok(()) } @@ -216,7 +216,7 @@ mod tests { // stake let unbonded_amount = token::testing::arb_amount_non_zero_ceiled( - initial_stake.into(), + u128::try_from(initial_stake).unwrap() as u64, ); // Use the generated initial stake too too (Just(initial_stake), unbonded_amount) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 31a920a540..97b50c2511 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -123,12 +123,17 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig; + let valid = change.non_negative() || *valid_sig; + let sign = if change.non_negative() { "" } else { "-" }; + let denom_amount = token::Amount::from_change(change) + .denominated(owner, &ctx.pre()) + .unwrap(); debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {}{}, valid_sig: {}, valid \ modification: {}", key, - change, + sign, + denom_amount, *valid_sig, valid ); @@ -338,8 +343,11 @@ mod tests { let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -347,6 +355,10 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -382,7 +394,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -405,9 +421,21 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -446,7 +474,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -469,9 +501,21 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -519,7 +563,11 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -528,6 +576,11 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -567,7 +620,11 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -578,6 +635,10 @@ mod tests { tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -621,7 +682,11 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -630,6 +695,11 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx::ctx().insert_verifier(address).unwrap(); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index a9b7f53230..9dd284e3af 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -12,16 +12,17 @@ fn convert_amount( epoch: Epoch, token: &Address, val: token::Amount, + denom: token::MaspDenom, ) -> (AssetType, Amount) { // Timestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) + let token_bytes = (token, denom, epoch.0) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address let asset_type = AssetType::new(token_bytes.as_ref()) .expect("unable to create asset type"); // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + let amount = Amount::from_nonnegative(asset_type, denom.denominate(&val)) .expect("invalid value or asset type for amount"); (asset_type, amount) } @@ -59,14 +60,17 @@ fn validate_tx( // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected - let (_transp_asset, transp_amt) = convert_amount( - ctx.get_block_epoch().unwrap(), - &transfer.token, - transfer.amount, - ); + for denom in token::MaspDenom::iter() { + let (_transp_asset, transp_amt) = convert_amount( + ctx.get_block_epoch().unwrap(), + &transfer.token, + transfer.amount.into(), + denom, + ); - // Non-masp sources add to transparent tx pool - transparent_tx_pool += transp_amt; + // Non-masp sources add to transparent tx pool + transparent_tx_pool += transp_amt; + } } // Handle unshielding/transparent output @@ -74,13 +78,16 @@ fn validate_tx( // Timestamp is derived to allow unshields for older tokens let atype = shielded_tx.value_balance.components().next().unwrap().0; + for denom in token::MaspDenom::iter() { + let transp_amt = Amount::from_nonnegative( + *atype, + denom.denominate(&transfer.amount), + ) + .expect("invalid value or asset type for amount"); - let transp_amt = - Amount::from_nonnegative(*atype, u64::from(transfer.amount)) - .expect("invalid value or asset type for amount"); - - // Non-masp destinations subtract from transparent tx pool - transparent_tx_pool -= transp_amt; + // Non-masp destinations subtract from transparent tx pool + transparent_tx_pool -= transp_amt; + } } match transparent_tx_pool.partial_cmp(&Amount::zero()) { diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 1b8802df6e..ab94e88c9f 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -57,7 +57,7 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); - if change < 0 { + if !change.non_negative() { // Allow to withdraw without a sig if there's a valid PoW if ctx.has_valid_pow() { let max_free_debit = @@ -152,7 +152,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -161,6 +165,11 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -290,12 +299,12 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(amount); + let amount = token::Amount::from_uint(amount, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -304,6 +313,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.commit_genesis(); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into() + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -330,13 +343,13 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); let token = address::nam(); - let amount = token::Amount::from(amount); + let amount = token::Amount::from_uint(amount, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -358,6 +371,11 @@ mod tests { sig, }; + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Don't call `Solution::invalidate_if_valid` - this is done by the @@ -392,7 +410,7 @@ mod tests { // Init the VP let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let keypair = key::testing::keypair_1(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index b8cbc20982..0e9c0556d9 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -98,12 +98,16 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || addr == masp() || *valid_sig; + let valid = + change.non_negative() || addr == masp() || *valid_sig; + let denom_amount = token::Amount::from(change) + .denominated(owner, &ctx.pre()) + .unwrap(); debug_log!( "token key: {}, change: {}, valid_sig: {}, valid \ modification: {}", key, - change, + denom_amount, *valid_sig, valid ); @@ -234,7 +238,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -243,6 +251,10 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -280,7 +292,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -289,6 +305,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -328,7 +348,11 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -339,6 +363,11 @@ mod tests { tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -377,7 +406,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -400,9 +433,21 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -441,7 +486,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -464,9 +513,21 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -513,7 +574,11 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -522,6 +587,11 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx::ctx().insert_verifier(address).unwrap(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index c9e4700d8e..f616e9dd31 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -98,12 +98,15 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig; + let valid = change.non_negative() || *valid_sig; + let amount = token::Amount::from(change) + .denominated(owner, &ctx.pre()) + .unwrap(); debug_log!( "token key: {}, change: {}, valid_sig: {}, valid \ modification: {}", key, - change, + amount, *valid_sig, valid ); @@ -243,7 +246,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -251,7 +258,10 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); - + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -289,7 +299,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -297,6 +311,10 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -337,7 +355,11 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -347,6 +369,10 @@ mod tests { tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -386,7 +412,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = Decimal::new(5, 2); let max_commission_rate_change = Decimal::new(1, 2); @@ -409,9 +439,21 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -456,7 +498,11 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = Decimal::new(5, 2); let max_commission_rate_change = Decimal::new(1, 2); @@ -479,9 +525,21 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let bond_amount = token::Amount::from_uint( + 5_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + let unbond_amount = token::Amount::from_uint( + 3_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -534,7 +592,11 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint( + 10_098_123, + token::NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -542,6 +604,10 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fd5ad1519e..afb2c6f3e5 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -287,7 +287,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -374,10 +374,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -502,7 +514,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -533,6 +545,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -1334,6 +1352,50 @@ dependencies = [ "version_check", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -1378,11 +1440,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1434,6 +1508,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -2012,7 +2092,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2032,6 +2112,55 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2085,6 +2214,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2115,7 +2253,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -2285,7 +2423,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2511,10 +2649,12 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "ibc", "ibc-proto", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1", @@ -2537,6 +2677,7 @@ dependencies = [ "thiserror", "tonic-build", "tracing", + "uint", "zeroize", ] @@ -2784,7 +2925,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -2811,6 +2952,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.45.0" @@ -3004,6 +3171,19 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3013,6 +3193,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3202,6 +3392,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3488,6 +3684,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3522,6 +3728,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3886,6 +4098,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -4440,6 +4662,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.6.2" @@ -4620,9 +4859,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy 0.2.2", @@ -5283,6 +5522,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5292,6 +5540,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5331,7 +5588,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 7f3584ad44..425252eb83 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -152,7 +152,7 @@ pub mod main { let target_key = token::balance_key(&token, &target); let mut target_bal: token::Amount = ctx.read(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount); + target_bal.receive(&amount.amount); ctx.write(&target_key, target_bal)?; Ok(()) } From b6517878a8e9d10a35e16652c35a0229b5b6b995 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Apr 2023 12:16:41 +0200 Subject: [PATCH 13/69] [feat]: Fixed unit tests --- core/src/types/token.rs | 67 +++++++++++++++++++++++---- core/src/types/transaction/wrapper.rs | 45 ++++++++++++++---- core/src/types/uint.rs | 8 ++-- shared/src/ledger/ibc/vp/token.rs | 2 +- tests/Cargo.toml | 1 + 5 files changed, 100 insertions(+), 23 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index cd49a4c0ef..7d1ba8a043 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -101,10 +101,23 @@ impl Amount { } /// Checked addition. Returns `None` on overflow or if - /// the amount exceed [`uint::MAX_SIGNED_VALUE`] + /// the amount exceed [`uint::MAX_VALUE`] pub fn checked_add(&self, amount: Amount) -> Option { self.raw.checked_add(amount.raw).and_then(|result| { - if result < uint::MAX_SIGNED_VALUE { + if result <= uint::MAX_VALUE { + Some(Self { raw: result }) + } else { + None + } + }) + } + + /// Checked addition. Returns `None` on overflow or if + /// the amount exceed [`uint::MAX_SIGNED_VALUE`] + pub fn checked_signed_add(&self, amount: Amount) -> Option { + self.raw.checked_add(amount.raw).and_then(|result| { + // TODO: Should this be `MAX_SIGNED_VALUE` or `MAX_VALUE`? + if result <= uint::MAX_SIGNED_VALUE { Some(Self { raw: result }) } else { None @@ -334,7 +347,11 @@ impl Display for DenominatedAmount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = self.to_string_precise(); let string = string.trim_end_matches(&['0', '.']); - f.write_str(string) + if string.is_empty() { + f.write_str("0") + } else { + f.write_str(string) + } } } @@ -902,24 +919,45 @@ mod tests { #[test] fn test_token_display() { - let max = Amount::from_uint(u64::MAX, NATIVE_MAX_DECIMAL_PLACES) + let max = Amount::from_uint(u64::MAX, 0) .expect("Test failed"); assert_eq!("18446744073709.551615", max.to_string_native()); + let max = DenominatedAmount { + amount: max, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + assert_eq!("18446744073709.551615", max.to_string()); let whole = Amount::from_uint( u64::MAX / NATIVE_SCALE * NATIVE_SCALE, - NATIVE_MAX_DECIMAL_PLACES, + 0, ) .expect("Test failed"); - assert_eq!("18446744073709", whole.to_string_native()); + assert_eq!("18446744073709.000000", whole.to_string_native()); + let whole = DenominatedAmount { + amount: whole, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + assert_eq!("18446744073709", whole.to_string()); let trailing_zeroes = - Amount::from_uint(123000, NATIVE_MAX_DECIMAL_PLACES) + Amount::from_uint(123000, 0) .expect("Test failed"); - assert_eq!("0.123", trailing_zeroes.to_string_native()); + assert_eq!("0.123000", trailing_zeroes.to_string_native()); + let trailing_zeroes = DenominatedAmount { + amount: trailing_zeroes, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + assert_eq!("0.123", trailing_zeroes.to_string()); + let zero = Amount::default(); - assert_eq!("0", zero.to_string_native()); + assert_eq!("0.000000", zero.to_string_native()); + let zero = DenominatedAmount { + amount: zero, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + assert_eq!("0", zero.to_string()); } #[test] @@ -939,18 +977,27 @@ mod tests { #[test] fn test_amount_checked_add() { - let max = Amount::native_whole(u64::MAX); + let max = Amount::max(); + let max_signed = Amount::max_signed(); let one = Amount::native_whole(1); let zero = Amount::native_whole(0); assert_eq!(zero.checked_add(zero), Some(zero)); + assert_eq!(zero.checked_signed_add(zero), Some(zero)); assert_eq!(zero.checked_add(one), Some(one)); assert_eq!(zero.checked_add(max - one), Some(max - one)); + assert_eq!(zero.checked_signed_add(max_signed - one), Some(max_signed - one)); assert_eq!(zero.checked_add(max), Some(max)); + assert_eq!(zero.checked_signed_add(max_signed), Some(max_signed)); assert_eq!(max.checked_add(zero), Some(max)); + assert_eq!(max.checked_signed_add(zero), None); assert_eq!(max.checked_add(one), None); assert_eq!(max.checked_add(max), None); + + assert_eq!(max_signed.checked_add(zero), Some(max_signed)); + assert_eq!(max_signed.checked_add(one), Some(max_signed + one)); + assert_eq!(max_signed.checked_signed_add(max_signed), None); } } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 472a05ce86..83f6e77935 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -4,11 +4,13 @@ #[cfg(feature = "ferveo-tpke")] pub mod wrapper_tx { use std::convert::TryFrom; + use std::fmt::Formatter; pub use ark_bls12_381::Bls12_381 as EllipticCurve; pub use ark_ec::{AffineCurve, PairingEngine}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; - use serde::{Deserialize, Serialize}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde::de::Error; use thiserror::Error; use crate::proto::Tx; @@ -77,18 +79,46 @@ pub mod wrapper_tx { Debug, Clone, PartialEq, - Serialize, - Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, )] - #[serde(from = "Uint")] - #[serde(into = "Uint")] pub struct GasLimit { multiplier: Uint, } + impl Serialize for GasLimit { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + let limit = Uint::from(self).to_string(); + Serialize::serialize(&limit, serializer) + } + } + + impl<'de> Deserialize<'de> for GasLimit { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct GasLimitVisitor; + + impl<'a> serde::de::Visitor<'a> for GasLimitVisitor { + type Value = GasLimit; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("A string representing 256-bit unsigned integer") + } + + fn visit_str(self, v: &str) -> Result + where E: Error + { + let uint = Uint::from_dec_str(v) + .map_err(|e| E::custom(e.to_string()))?; + Ok(GasLimit::from(uint)) + } + } + deserializer.deserialize_any(GasLimitVisitor) + } + } + impl GasLimit { /// We refund unused gas up to GAS_LIMIT_RESOLUTION pub fn refund_amount(&self, used_gas: Uint) -> Amount { @@ -301,7 +331,7 @@ pub mod wrapper_tx { }; // Test serde roundtrip let js = serde_json::to_string(&limit).expect("Test failed"); - assert_eq!(js, format!("{}", GAS_LIMIT_RESOLUTION)); + assert_eq!(js, format!(r#""{}""#, GAS_LIMIT_RESOLUTION)); let new_limit: GasLimit = serde_json::from_str(&js).expect("Test failed"); assert_eq!(new_limit, limit); @@ -320,8 +350,7 @@ pub mod wrapper_tx { /// multiple #[test] fn test_deserialize_not_multiple_of_resolution() { - let js = serde_json::to_string(&(GAS_LIMIT_RESOLUTION + 1)) - .expect("Test failed"); + let js = format!(r#""{}""#, &(GAS_LIMIT_RESOLUTION + 1)); let limit: GasLimit = serde_json::from_str(&js).expect("Test failed"); assert_eq!( diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 443a69caea..0f8f0daa53 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -32,7 +32,7 @@ pub const MAX_VALUE: Uint = Uint([u64::MAX; 4]); impl Uint { /// Compute the two's complement of a number. - fn negate(&self) -> Option { + fn negate(&self) -> Self { Self( self.0 .into_iter() @@ -41,7 +41,7 @@ impl Uint { .try_into() .expect("This cannot fail"), ) - .checked_add(Uint::from(1u64)) + .overflowing_add(Uint::from(1u64)).0 } } @@ -69,7 +69,7 @@ impl SignedUint { if self.non_negative() { self.0 } else { - self.0.negate().unwrap() + self.0.negate() } } @@ -116,7 +116,7 @@ impl Neg for SignedUint { type Output = Self; fn neg(self) -> Self::Output { - Self(self.0.negate().expect("This should not fail")) + Self(self.0.negate()) } } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 0f34024da4..b28019849f 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -84,7 +84,7 @@ where SignedTxData::try_from_slice(tx_data).map_err(Error::Decoding)?; let tx_data = &signed.data.ok_or(Error::NoTxData)?; - // Check the non-onwer balance updates + // Check the non-owner balance updates let ibc_keys_changed: HashSet = keys_changed .iter() .filter(|k| { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 813793cc00..602550367d 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,6 +15,7 @@ mainnet = [ abciplus = [ "namada/abciplus", "namada/ibc-mocks", + "namada_apps/abciplus", "namada_vp_prelude/abciplus", "namada_tx_prelude/abciplus", ] From 2356c5de866c7012f8b7a737bb6fb070fddcb8e6 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Apr 2023 12:18:28 +0200 Subject: [PATCH 14/69] [chore]: Formatting --- core/src/types/token.rs | 20 +++++++++----------- core/src/types/transaction/wrapper.rs | 22 ++++++++++++++++------ core/src/types/uint.rs | 3 ++- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 7d1ba8a043..a9541993d1 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -919,8 +919,7 @@ mod tests { #[test] fn test_token_display() { - let max = Amount::from_uint(u64::MAX, 0) - .expect("Test failed"); + let max = Amount::from_uint(u64::MAX, 0).expect("Test failed"); assert_eq!("18446744073709.551615", max.to_string_native()); let max = DenominatedAmount { amount: max, @@ -928,11 +927,9 @@ mod tests { }; assert_eq!("18446744073709.551615", max.to_string()); - let whole = Amount::from_uint( - u64::MAX / NATIVE_SCALE * NATIVE_SCALE, - 0, - ) - .expect("Test failed"); + let whole = + Amount::from_uint(u64::MAX / NATIVE_SCALE * NATIVE_SCALE, 0) + .expect("Test failed"); assert_eq!("18446744073709.000000", whole.to_string_native()); let whole = DenominatedAmount { amount: whole, @@ -941,8 +938,7 @@ mod tests { assert_eq!("18446744073709", whole.to_string()); let trailing_zeroes = - Amount::from_uint(123000, 0) - .expect("Test failed"); + Amount::from_uint(123000, 0).expect("Test failed"); assert_eq!("0.123000", trailing_zeroes.to_string_native()); let trailing_zeroes = DenominatedAmount { amount: trailing_zeroes, @@ -950,7 +946,6 @@ mod tests { }; assert_eq!("0.123", trailing_zeroes.to_string()); - let zero = Amount::default(); assert_eq!("0.000000", zero.to_string_native()); let zero = DenominatedAmount { @@ -986,7 +981,10 @@ mod tests { assert_eq!(zero.checked_signed_add(zero), Some(zero)); assert_eq!(zero.checked_add(one), Some(one)); assert_eq!(zero.checked_add(max - one), Some(max - one)); - assert_eq!(zero.checked_signed_add(max_signed - one), Some(max_signed - one)); + assert_eq!( + zero.checked_signed_add(max_signed - one), + Some(max_signed - one) + ); assert_eq!(zero.checked_add(max), Some(max)); assert_eq!(zero.checked_signed_add(max_signed), Some(max_signed)); diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 83f6e77935..448de45540 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -9,8 +9,8 @@ pub mod wrapper_tx { pub use ark_bls12_381::Bls12_381 as EllipticCurve; pub use ark_ec::{AffineCurve, PairingEngine}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; use crate::proto::Tx; @@ -88,7 +88,10 @@ pub mod wrapper_tx { } impl Serialize for GasLimit { - fn serialize(&self, serializer: S) -> Result where S: Serializer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { let limit = Uint::from(self).to_string(); Serialize::serialize(&limit, serializer) } @@ -96,19 +99,26 @@ pub mod wrapper_tx { impl<'de> Deserialize<'de> for GasLimit { fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> + where + D: Deserializer<'de>, { struct GasLimitVisitor; impl<'a> serde::de::Visitor<'a> for GasLimitVisitor { type Value = GasLimit; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - formatter.write_str("A string representing 256-bit unsigned integer") + fn expecting( + &self, + formatter: &mut Formatter, + ) -> std::fmt::Result { + formatter.write_str( + "A string representing 256-bit unsigned integer", + ) } fn visit_str(self, v: &str) -> Result - where E: Error + where + E: Error, { let uint = Uint::from_dec_str(v) .map_err(|e| E::custom(e.to_string()))?; diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 0f8f0daa53..fde3571c19 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -41,7 +41,8 @@ impl Uint { .try_into() .expect("This cannot fail"), ) - .overflowing_add(Uint::from(1u64)).0 + .overflowing_add(Uint::from(1u64)) + .0 } } From b244e1b1b4149b8402bf285345a0a66f048411a8 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 13 Apr 2023 11:02:24 +0200 Subject: [PATCH 15/69] [chore]: Added test coverage for new token types and fixed the bugs they found --- apps/src/lib/cli.rs | 8 +- apps/src/lib/client/rpc.rs | 2 +- core/src/types/token.rs | 94 ++++++++++++++++++--- core/src/types/uint.rs | 162 +++++++++++++++++++++++++++++++++++-- 4 files changed, 244 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c2c89d9285..c735e70eb5 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2211,8 +2211,8 @@ pub mod args { let amount = amount .canonical() .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) - .unwrap_or_else(|| { - println!("Could not parse bond amount"); + .unwrap_or_else(|e| { + println!("Could not parse bond amount: {:?}", e); safe_exit(1); }) .amount; @@ -2258,8 +2258,8 @@ pub mod args { let amount = amount .canonical() .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) - .unwrap_or_else(|| { - println!("Could not parse bond amount"); + .unwrap_or_else(|e| { + println!("Could not parse bond amount: {:?}", e); safe_exit(1); }) .amount; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0e6d7d9ce9..443885857a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2838,7 +2838,7 @@ pub async fn validate_amount( ); cli::safe_exit(1); } else { - input_amount.increase_precision(denom).unwrap_or_else(|| { + input_amount.increase_precision(denom).unwrap_or_else(|_| { println!( "The amount provided requires more the 256 bits to represent." ); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index a9541993d1..71d8a56b8a 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -116,7 +116,6 @@ impl Amount { /// the amount exceed [`uint::MAX_SIGNED_VALUE`] pub fn checked_signed_add(&self, amount: Amount) -> Option { self.raw.checked_add(amount.raw).and_then(|result| { - // TODO: Should this be `MAX_SIGNED_VALUE` or `MAX_VALUE`? if result <= uint::MAX_SIGNED_VALUE { Some(Self { raw: result }) } else { @@ -163,10 +162,9 @@ impl Amount { string: impl AsRef, denom: impl Into, ) -> Result { - match Decimal::from_str(string.as_ref()) { - Ok(dec) => Ok(Self::from_decimal(dec, denom)?), - Err(err) => Err(AmountParseError::InvalidDecimal(err)), - } + DenominatedAmount::from_str(string.as_ref())? + .increase_precision(denom.into().into()) + .map(Into::into) } /// Attempt to convert a float to an `Amount` with the specified @@ -200,10 +198,9 @@ impl Amount { /// Given a u64 and [`MaspDenom`], construct the corresponding /// amount. pub fn from_masp_denominated(val: u64, denom: MaspDenom) -> Self { - let val = Uint::from(val); - let denom = Uint::from(denom as u64); - let scaling = Uint::from(2).pow(denom); - Self { raw: val * scaling } + let mut raw = [0u64; 4]; + raw[denom as usize] = val; + Self { raw: Uint(raw)} } /// Get a string representation of a native token amount. @@ -329,9 +326,9 @@ impl DenominatedAmount { /// Attempt to increase the precision of an amount. Can fail /// if the resulting amount does not fit into 256 bits. - pub fn increase_precision(self, denom: Denomination) -> Option { + pub fn increase_precision(self, denom: Denomination) -> Result { if denom.0 < self.denom.0 { - return None; + return Err(AmountParseError::PrecisionDecrease); } Uint::from(10) .checked_pow(Uint::from(denom.0 - self.denom.0)) @@ -340,6 +337,7 @@ impl DenominatedAmount { amount: Amount { raw: amount }, denom, }) + .ok_or(AmountParseError::PrecisionOverflow) } } @@ -359,7 +357,7 @@ impl FromStr for DenominatedAmount { type Err = AmountParseError; fn from_str(s: &str) -> Result { - let precision = s.find('.').map(|pos| s.len() - pos); + let precision = s.find('.').map(|pos| s.len() - pos - 1); let digits = s .chars() .filter_map(|c| { @@ -613,6 +611,10 @@ pub enum AmountParseError { FromString, #[error("Could not parse string as a correctly formatted number.")] NotNumeric, + #[error("This amount cannot handle the requested precision in 256 bits.")] + PrecisionOverflow, + #[error("More precision given in the amount than requested.")] + PrecisionDecrease, } impl From for Change { @@ -901,6 +903,7 @@ impl TryFrom for Transfer { #[cfg(test)] mod tests { use proptest::prelude::*; + use rust_decimal_macros::dec; use super::*; @@ -953,6 +956,20 @@ mod tests { denom: NATIVE_MAX_DECIMAL_PLACES.into(), }; assert_eq!("0", zero.to_string()); + + let amount = DenominatedAmount { + amount: Amount::from_uint(1120, 0).expect("Test failed"), + denom: 3u8.into(), + }; + assert_eq!("1.12", amount.to_string()); + assert_eq!("1.120", amount.to_string_precise()); + + let amount = DenominatedAmount { + amount: Amount::from_uint(1120, 0).expect("Test failed"), + denom: 5u8.into(), + }; + assert_eq!("0.0112", amount.to_string()); + assert_eq!("0.01120", amount.to_string_precise()); } #[test] @@ -997,6 +1014,59 @@ mod tests { assert_eq!(max_signed.checked_add(one), Some(max_signed + one)); assert_eq!(max_signed.checked_signed_add(max_signed), None); } + + #[test] + fn test_amount_from_decimal() { + assert!(Amount::from_decimal(dec!(1.12), 1).is_err()); + assert!(Amount::from_decimal(dec!(1.12), 80).is_err()); + let amount = Amount::from_decimal(dec!(1.12), 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); + + } + + #[test] + fn test_amount_from_string() { + assert!(Amount::from_str("1.12", 1).is_err()); + assert!(Amount::from_str("0.0", 0).is_err()); + assert!(Amount::from_str("1.12", 80).is_err()); + assert!(Amount::from_str("1.12.1", 3).is_err()); + assert!(Amount::from_str("1.1a", 3).is_err()); + assert_eq!(Amount::zero(), Amount::from_str("0.0", 1).expect("Test failed")); + assert_eq!(Amount::zero(), Amount::from_str(".0", 1).expect("Test failed")); + + let amount = Amount::from_str("1.12", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); + let amount = Amount::from_str(".34", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + let amount = Amount::from_str("0.34", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + let amount = Amount::from_str("34", 1).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + } + + #[test] + fn test_from_masp_denominated() { + let uint = Uint([15u64, 16, 17, 18]); + let original = Amount::from_uint(uint, 0).expect("Test failed"); + for denom in MaspDenom::iter() { + let word = denom.denominate(&original); + assert_eq!(word, denom as u64 + 15u64); + let amount = Amount::from_masp_denominated(word, denom); + let raw = Uint::from(amount).0; + let mut expected = [0u64; 4]; + expected[denom as usize] = word; + assert_eq!(raw, expected); + } + } + + #[test] + fn test_key_seg() { + let original = Amount::from_uint(1234560000, 0).expect("Test failed"); + let key = original.raw(); + let amount = Amount::parse(key).expect("Test failed"); + assert_eq!(amount, original); + } + } /// Helpers for testing with addresses. diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index fde3571c19..16c4045fd2 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -43,6 +43,17 @@ impl Uint { ) .overflowing_add(Uint::from(1u64)) .0 + .canonical() + } + + /// There are two valid representations of zero: plus and + /// minus. We only allow the positive representation. + fn canonical(self) -> Self { + if self == MINUS_ZERO { + Self::zero() + } else { + self + } } } @@ -52,6 +63,8 @@ impl Uint { pub const MAX_SIGNED_VALUE: Uint = Uint([u64::MAX, u64::MAX, u64::MAX, 9223372036854775807]); +const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); + /// A signed 256 big integer. #[derive( Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, @@ -82,7 +95,7 @@ impl SignedUint { /// Get a string representation of `self` as a /// native token amount. pub fn to_string_native(&self) -> String { - let mut sign = if self.non_negative() { + let mut sign = if !self.non_negative() { String::from("-") } else { String::new() @@ -90,6 +103,34 @@ impl SignedUint { sign.push_str(&token::Amount::from(*self).to_string_native()); sign } + + /// Adds two [`SignedUint`]'s if the absolute value does + /// not exceed [`MAX_SIGNED_AMOUNT`], else returns `None`. + pub fn checked_add(&self, other: &Self) -> Option { + if self.non_negative() == other.non_negative() { + self.abs().checked_add(other.abs()) + .and_then(|val| Self::try_from(val) + .ok() + .map(|val| if !self.non_negative() { + -val + } else { + val + })) + } else { + Some(*self + *other) + } + } + + /// Subtracts two [`SignedUint`]'s if the absolute value does + /// not exceed [`MAX_SIGNED_AMOUNT`], else returns `None`. + pub fn checked_sub(&self, other: &Self) -> Option { + self.checked_add(&other.neg()) + } + + /// Changed the inner Uint into a canonical representation. + fn canonical(self) -> Self { + Self(self.0.canonical()) + } } impl From for SignedUint { @@ -103,7 +144,7 @@ impl TryFrom for SignedUint { type Error = Box; fn try_from(value: Uint) -> Result { - if value.0 <= MAX_SIGNED_VALUE.0 { + if value <= MAX_SIGNED_VALUE { Ok(Self(value)) } else { Err("The given integer is too large to be represented asa \ @@ -148,9 +189,17 @@ impl Add for SignedUint { match (self.non_negative(), rhs.non_negative()) { (true, true) => Self(self.0 + rhs.0), (false, false) => -Self(self.abs() + rhs.abs()), - (true, false) => Self(self.0 - rhs.abs()), - (false, true) => Self(rhs.0 - self.abs()), - } + (true, false) => if self.0 >= rhs.abs() { + Self(self.0 - rhs.abs()) + } else { + -Self(rhs.abs() - self.0) + } + (false, true) => if rhs.0 >= self.abs() { + Self(rhs.0 - self.abs()) + } else { + -Self(self.abs() - rhs.0) + }, + }.canonical() } } @@ -190,3 +239,106 @@ impl From for SignedUint { Self::from(val as i128) } } + + +#[cfg(test)] +mod test_uint { + use super::*; + + /// Test that adding one to the max signed + /// value gives zero. + #[test] + fn test_max_signed_value() { + let signed = SignedUint::try_from(MAX_SIGNED_VALUE).expect("Test failed"); + let one = SignedUint::try_from(Uint::from(1u64)).expect("Test failed"); + let overflow = signed + one; + assert_eq!(overflow, SignedUint::try_from(Uint::zero()).expect("Test failed")); + assert!(signed.checked_add(&one).is_none()); + assert!((-signed).checked_sub(&one).is_none()); + } + + /// Sanity on our constants and that the minus zero representation + /// is not allowed. + #[test] + fn test_minus_zero_not_allowed() { + let larger = Uint([0, 0, 0, 2u64.pow(63)]); + let smaller = Uint([u64::MAX, u64::MAX, u64::MAX, 2u64.pow(63) -1]); + assert!(larger > smaller); + assert_eq!(smaller, MAX_SIGNED_VALUE); + assert_eq!(larger, MINUS_ZERO); + assert!(SignedUint::try_from(MINUS_ZERO).is_err()); + let zero = Uint::zero(); + assert_eq!(zero, zero.negate()); + } + + /// Test that we correctly reserve the right bit for indicating the + /// sign. + #[test] + fn test_non_negative() { + let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); + assert!(zero.non_negative()); + assert!((-zero).non_negative()); + let negative = SignedUint(Uint([1u64, 0, 0, 2u64.pow(63)])); + assert!(!negative.non_negative()); + assert!((-negative).non_negative()); + let positive = SignedUint(MAX_SIGNED_VALUE); + assert!(positive.non_negative()); + assert!(!(-positive).non_negative()); + } + + /// Test that the absolute vale is computed correctly + #[test] + fn test_abs() { + let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); + let neg_one = SignedUint(Uint::max_value()); + let neg_eight = SignedUint(Uint::max_value() - Uint::from(7)); + let two = SignedUint(Uint::from(2)); + let ten = SignedUint(Uint::from(10)); + + assert_eq!(zero.abs(), Uint::zero()); + assert_eq!(neg_one.abs(), Uint::from(1)); + assert_eq!(neg_eight.abs(), Uint::from(8)); + assert_eq!(two.abs(), Uint::from(2)); + assert_eq!(ten.abs(), Uint::from(10)); + } + + /// Test that the absolute vale is computed correctly + #[test] + fn test_to_string_native() { + let native_scaling = Uint::exp10(6); + let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); + let neg_one = -SignedUint(native_scaling); + let neg_eight = -SignedUint(Uint::from(8) * native_scaling); + let two = SignedUint(Uint::from(2) * native_scaling); + let ten = SignedUint(Uint::from(10) * native_scaling); + + assert_eq!(zero.to_string_native(), "0.000000"); + assert_eq!(neg_one.to_string_native(), "-1.000000"); + assert_eq!(neg_eight.to_string_native(), "-8.000000"); + assert_eq!(two.to_string_native(), "2.000000"); + assert_eq!(ten.to_string_native(), "10.000000"); + } + + /// Test that we correctly handle arithmetic with two's complement + #[test] + fn test_arithmetic() { + let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); + let neg_one = SignedUint(Uint::max_value()); + let neg_eight = SignedUint(Uint::max_value() - Uint::from(7)); + let two = SignedUint(Uint::from(2)); + let ten = SignedUint(Uint::from(10)); + + assert_eq!(zero + neg_one, neg_one); + assert_eq!(neg_one - zero, neg_one); + assert_eq!(zero - neg_one, SignedUint(Uint::one())); + assert_eq!(two - neg_eight, ten); + assert_eq!(two + ten, SignedUint(Uint::from(12))); + assert_eq!(ten - two, -neg_eight); + assert_eq!(two - ten, neg_eight); + assert_eq!(neg_eight + neg_one, -SignedUint(Uint::from(9))); + assert_eq!(neg_one - neg_eight, SignedUint(Uint::from(7))); + assert_eq!(neg_eight - neg_one, -SignedUint(Uint::from(7))); + assert_eq!(neg_eight - two, -ten); + assert!((two - two).is_zero()); + } +} \ No newline at end of file From b58d49bb039cd10573954e57f1452a19956ba337 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 13 Apr 2023 13:25:09 +0200 Subject: [PATCH 16/69] [fix]: Fixed some conversions to amounts in tests. Fixed PoS state machine tests --- .../lib/node/ledger/shell/finalize_block.rs | 19 +--- .../lib/node/ledger/shell/process_proposal.rs | 8 +- core/src/types/token.rs | 19 ++-- core/src/types/transaction/mod.rs | 6 +- core/src/types/transaction/wrapper.rs | 7 +- core/src/types/uint.rs | 60 ++++++++----- proof_of_stake/src/lib.rs | 10 +-- proof_of_stake/src/tests.rs | 86 +++++++++---------- proof_of_stake/src/tests/state_machine.rs | 17 ++-- rust-toolchain.toml | 2 +- 10 files changed, 123 insertions(+), 111 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index ea8fd012c2..054405b067 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -470,7 +470,6 @@ mod test_finalize_block { use namada::types::governance::ProposalVote; use namada::types::storage::Epoch; use namada::types::time::DurationSecs; - use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -514,11 +513,7 @@ mod test_finalize_block { ); let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint( - MIN_FEE, - NATIVE_MAX_DECIMAL_PLACES, - ) - .expect("Test failed"), + amount: Amount::from_uint(MIN_FEE, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, @@ -731,11 +726,7 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: Amount::from_uint( - MIN_FEE, - NATIVE_MAX_DECIMAL_PLACES, - ) - .expect("Test failed"), + amount: Amount::from_uint(MIN_FEE, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, @@ -772,11 +763,7 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: Amount::from_uint( - MIN_FEE, - NATIVE_MAX_DECIMAL_PLACES, - ) - .expect("Test failed"), + amount: Amount::from_uint(MIN_FEE, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 0b8e29a151..98ab3654f5 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -215,7 +215,7 @@ mod test_process_proposal { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; - use namada::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; + use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; @@ -287,8 +287,7 @@ mod test_process_proposal { let timestamp = tx.timestamp; let mut wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(100, NATIVE_MAX_DECIMAL_PLACES) - .expect("Test failed"), + amount: Amount::from_uint(100, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, @@ -372,8 +371,7 @@ mod test_process_proposal { ); let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(1, NATIVE_MAX_DECIMAL_PLACES) - .expect("Test failed"), + amount: Amount::from_uint(1, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 71d8a56b8a..0851d8463f 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -200,7 +200,7 @@ impl Amount { pub fn from_masp_denominated(val: u64, denom: MaspDenom) -> Self { let mut raw = [0u64; 4]; raw[denom as usize] = val; - Self { raw: Uint(raw)} + Self { raw: Uint(raw) } } /// Get a string representation of a native token amount. @@ -326,7 +326,10 @@ impl DenominatedAmount { /// Attempt to increase the precision of an amount. Can fail /// if the resulting amount does not fit into 256 bits. - pub fn increase_precision(self, denom: Denomination) -> Result { + pub fn increase_precision( + self, + denom: Denomination, + ) -> Result { if denom.0 < self.denom.0 { return Err(AmountParseError::PrecisionDecrease); } @@ -1021,7 +1024,6 @@ mod tests { assert!(Amount::from_decimal(dec!(1.12), 80).is_err()); let amount = Amount::from_decimal(dec!(1.12), 3).expect("Test failed"); assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); - } #[test] @@ -1031,8 +1033,14 @@ mod tests { assert!(Amount::from_str("1.12", 80).is_err()); assert!(Amount::from_str("1.12.1", 3).is_err()); assert!(Amount::from_str("1.1a", 3).is_err()); - assert_eq!(Amount::zero(), Amount::from_str("0.0", 1).expect("Test failed")); - assert_eq!(Amount::zero(), Amount::from_str(".0", 1).expect("Test failed")); + assert_eq!( + Amount::zero(), + Amount::from_str("0.0", 1).expect("Test failed") + ); + assert_eq!( + Amount::zero(), + Amount::from_str(".0", 1).expect("Test failed") + ); let amount = Amount::from_str("1.12", 3).expect("Test failed"); assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); @@ -1066,7 +1074,6 @@ mod tests { let amount = Amount::parse(key).expect("Test failed"); assert_eq!(amount, original); } - } /// Helpers for testing with addresses. diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 8d59fd5a9f..5683c5a2fe 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -342,7 +342,7 @@ pub mod tx_types { use super::*; use crate::types::address::nam; use crate::types::storage::Epoch; - use crate::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; + use crate::types::token::Amount; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -424,7 +424,7 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + amount: Amount::from_uint(10, 0) .expect("Test failed"), token: nam(), }, @@ -462,7 +462,7 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + amount: Amount::from_uint(10, 0) .expect("Test failed"), token: nam(), }, diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 448de45540..4dac6e7ce2 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -414,7 +414,6 @@ pub mod wrapper_tx { use super::*; use crate::proto::SignedTxData; use crate::types::address::nam; - use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -436,7 +435,7 @@ pub mod wrapper_tx { let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + amount: Amount::from_uint(10, 0) .expect("Test failed"), token: nam(), }, @@ -465,7 +464,7 @@ pub mod wrapper_tx { let mut wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + amount: Amount::from_uint(10, 0) .expect("Test failed"), token: nam(), }, @@ -500,7 +499,7 @@ pub mod wrapper_tx { // the signed tx let mut tx = WrapperTx::new( Fee { - amount: Amount::from_uint(10, NATIVE_MAX_DECIMAL_PLACES) + amount: Amount::from_uint(10, 0) .expect("Test failed"), token: nam(), }, diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 16c4045fd2..223044387a 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use uint::construct_uint; use crate::types::token; +use crate::types::token::Amount; construct_uint! { /// Namada native type to replace for unsigned 256 bit @@ -108,14 +109,11 @@ impl SignedUint { /// not exceed [`MAX_SIGNED_AMOUNT`], else returns `None`. pub fn checked_add(&self, other: &Self) -> Option { if self.non_negative() == other.non_negative() { - self.abs().checked_add(other.abs()) - .and_then(|val| Self::try_from(val) + self.abs().checked_add(other.abs()).and_then(|val| { + Self::try_from(val) .ok() - .map(|val| if !self.non_negative() { - -val - } else { - val - })) + .map(|val| if !self.non_negative() { -val } else { val }) + }) } else { Some(*self + *other) } @@ -189,17 +187,22 @@ impl Add for SignedUint { match (self.non_negative(), rhs.non_negative()) { (true, true) => Self(self.0 + rhs.0), (false, false) => -Self(self.abs() + rhs.abs()), - (true, false) => if self.0 >= rhs.abs() { - Self(self.0 - rhs.abs()) - } else { - -Self(rhs.abs() - self.0) + (true, false) => { + if self.0 >= rhs.abs() { + Self(self.0 - rhs.abs()) + } else { + -Self(rhs.abs() - self.0) + } } - (false, true) => if rhs.0 >= self.abs() { - Self(rhs.0 - self.abs()) - } else { - -Self(self.abs() - rhs.0) - }, - }.canonical() + (false, true) => { + if rhs.0 >= self.abs() { + Self(rhs.0 - self.abs()) + } else { + -Self(self.abs() - rhs.0) + } + } + } + .canonical() } } @@ -240,6 +243,17 @@ impl From for SignedUint { } } +impl TryFrom for i128 { + type Error = std::io::Error; + + fn try_from(value: SignedUint) -> Result { + if !value.non_negative() { + Ok(-(u128::try_from(Amount::from_change(value))? as i128)) + } else { + Ok(u128::try_from(Amount::from_change(value))? as i128) + } + } +} #[cfg(test)] mod test_uint { @@ -249,10 +263,14 @@ mod test_uint { /// value gives zero. #[test] fn test_max_signed_value() { - let signed = SignedUint::try_from(MAX_SIGNED_VALUE).expect("Test failed"); + let signed = + SignedUint::try_from(MAX_SIGNED_VALUE).expect("Test failed"); let one = SignedUint::try_from(Uint::from(1u64)).expect("Test failed"); let overflow = signed + one; - assert_eq!(overflow, SignedUint::try_from(Uint::zero()).expect("Test failed")); + assert_eq!( + overflow, + SignedUint::try_from(Uint::zero()).expect("Test failed") + ); assert!(signed.checked_add(&one).is_none()); assert!((-signed).checked_sub(&one).is_none()); } @@ -262,7 +280,7 @@ mod test_uint { #[test] fn test_minus_zero_not_allowed() { let larger = Uint([0, 0, 0, 2u64.pow(63)]); - let smaller = Uint([u64::MAX, u64::MAX, u64::MAX, 2u64.pow(63) -1]); + let smaller = Uint([u64::MAX, u64::MAX, u64::MAX, 2u64.pow(63) - 1]); assert!(larger > smaller); assert_eq!(smaller, MAX_SIGNED_VALUE); assert_eq!(larger, MINUS_ZERO); @@ -341,4 +359,4 @@ mod test_uint { assert_eq!(neg_eight - two, -ten); assert!((two - two).is_zero()); } -} \ No newline at end of file +} diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e3fdcde9f2..c9c468f79b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -19,8 +19,8 @@ pub mod storage; pub mod types; // pub mod validation; -//#[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; use core::fmt::Debug; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -42,7 +42,7 @@ use namada_core::types::key::{ }; pub use namada_core::types::storage::Epoch; use namada_core::types::token; -use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_core::types::token::Amount; use once_cell::unsync::Lazy; use parameters::PosParams; use rust_decimal::Decimal; @@ -1628,7 +1628,7 @@ where slash_rate, u128::try_from(amount).expect("Amount out of bounds"), ), - NATIVE_MAX_DECIMAL_PLACES, + 0, ) .expect("Amount out of bounds"); slashed += to_slash; @@ -1745,7 +1745,7 @@ where rate, u128::try_from(current_stake).expect("Amount out of bounds"), ), - NATIVE_MAX_DECIMAL_PLACES, + 0, ) .expect("Amount out of bounds"); let token_change = -token::Change::from(slashed_amount); diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 22149a191e..9b7de24f7c 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -215,7 +215,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); // Self-bond - let amount_self_bond = token::Amount::from(100_500_000); + let amount_self_bond = token::Amount::from_uint(100_500_000, 0).unwrap(); credit_tokens( &mut s, &staking_token_address(), @@ -325,7 +325,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Get a non-validating account with tokens let delegator = address::testing::gen_implicit_address(); - let amount_del = token::Amount::from(201_000_000); + let amount_del = token::Amount::from_uint(201_000_000, 0).unwrap(); credit_tokens(&mut s, &staking_token_address(), &delegator, amount_del) .unwrap(); let balance_key = token::balance_key(&staking_token_address(), &delegator); @@ -461,7 +461,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pipeline_epoch = current_epoch + params.pipeline_len; // Unbond the self-bond - let amount_self_unbond = token::Amount::from(50_000); + let amount_self_unbond = token::Amount::from_uint(50_000, 0).unwrap(); unbond_tokens( &mut s, None, @@ -511,7 +511,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { ); // Unbond delegation - let amount_undel = token::Amount::from(1_000_000); + let amount_undel = token::Amount::from_uint(1_000_000, 0).unwrap(); unbond_tokens( &mut s, Some(&delegator), @@ -694,7 +694,7 @@ fn test_become_validator_aux( current_epoch = advance_epoch(&mut s, ¶ms); // Self-bond to the new validator - let amount = token::Amount::from(100_500_000); + let amount = token::Amount::from_uint(100_500_000, 0).unwrap(); credit_tokens(&mut s, &staking_token_address(), &new_validator, amount) .unwrap(); bond_tokens(&mut s, None, &new_validator, amount, current_epoch).unwrap(); @@ -831,20 +831,20 @@ fn test_validator_sets() { // Start with two genesis validators with 1 NAM stake let epoch = Epoch::default(); - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::whole(1)); - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::whole(1)); - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::whole(10)); - let ((val4, pk4), stake4) = (gen_validator(), token::Amount::whole(1)); - let ((val5, pk5), stake5) = (gen_validator(), token::Amount::whole(100)); - let ((val6, pk6), stake6) = (gen_validator(), token::Amount::whole(1)); - let ((val7, pk7), stake7) = (gen_validator(), token::Amount::whole(1)); - println!("val1: {val1}, {pk1}, {stake1}"); - println!("val2: {val2}, {pk2}, {stake2}"); - println!("val3: {val3}, {pk3}, {stake3}"); - println!("val4: {val4}, {pk4}, {stake4}"); - println!("val5: {val5}, {pk5}, {stake5}"); - println!("val6: {val6}, {pk6}, {stake6}"); - println!("val7: {val7}, {pk7}, {stake7}"); + let ((val1, pk1), stake1) = (gen_validator(), token::Amount::native_whole(1)); + let ((val2, pk2), stake2) = (gen_validator(), token::Amount::native_whole(1)); + let ((val3, pk3), stake3) = (gen_validator(), token::Amount::native_whole(10)); + let ((val4, pk4), stake4) = (gen_validator(), token::Amount::native_whole(1)); + let ((val5, pk5), stake5) = (gen_validator(), token::Amount::native_whole(100)); + let ((val6, pk6), stake6) = (gen_validator(), token::Amount::native_whole(1)); + let ((val7, pk7), stake7) = (gen_validator(), token::Amount::native_whole(1)); + println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); + println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); + println!("val4: {val4}, {pk4}, {}", stake4.to_string_native()); + println!("val5: {val5}, {pk5}, {}", stake5.to_string_native()); + println!("val6: {val6}, {pk6}, {}", stake6.to_string_native()); + println!("val7: {val7}, {pk7}, {}", stake7.to_string_native()); init_genesis( &mut s, @@ -877,14 +877,14 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk1.clone(), - bonded_stake: stake1.into(), + bonded_stake: u128::try_from(stake1).unwrap() as u64, }) ); assert_eq!( tm_updates[1], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk2.clone(), - bonded_stake: stake2.into(), + bonded_stake: u128::try_from(stake2).unwrap() as u64, }) ); @@ -1023,7 +1023,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: stake3.into(), + bonded_stake: u128::try_from(stake3).unwrap() as u64, }) ); @@ -1078,16 +1078,16 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk5, - bonded_stake: stake5.into(), + bonded_stake: u128::try_from(stake5).unwrap() as u64, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk2)); // Unbond some stake from val1, it should be be swapped with the greatest // below-capacity validator val2 into the below-capacity set - let unbond = token::Amount::from(500_000); + let unbond = token::Amount::from_uint(500_000, 0).unwrap(); let stake1 = stake1 - unbond; - println!("val1 {val1} new stake {stake1}"); + println!("val1 {val1} new stake {}", stake1.to_string_native()); // Because `update_validator_set` and `update_validator_deltas` are // effective from pipeline offset, we use pipeline epoch for the rest of the // checks @@ -1274,16 +1274,16 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk4.clone(), - bonded_stake: stake4.into(), + bonded_stake: u128::try_from(stake4).unwrap() as u64, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk1)); // Bond some stake to val6, it should be be swapped with the lowest // consensus validator val2 into the consensus set - let bond = token::Amount::from(500_000); + let bond = token::Amount::from_uint(500_000, 0).unwrap(); let stake6 = stake6 + bond; - println!("val6 {val6} new stake {stake6}"); + println!("val6 {val6} new stake {}", stake6.to_string_native()); update_validator_set(&mut s, ¶ms, &val6, bond.change(), epoch).unwrap(); update_validator_deltas(&mut s, ¶ms, &val6, bond.change(), epoch) .unwrap(); @@ -1390,7 +1390,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk6, - bonded_stake: stake6.into(), + bonded_stake: u128::try_from(stake6).unwrap() as u64, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); @@ -1454,14 +1454,14 @@ fn test_validator_sets_swap() { // Start with two genesis validators, one with 1 voting power and other 0 let epoch = Epoch::default(); // 1M voting power - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::whole(10)); + let ((val1, pk1), stake1) = (gen_validator(), token::Amount::native_whole(10)); // 0 voting power - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::from(5)); + let ((val2, pk2), stake2) = (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); // 0 voting power - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::from(5)); - println!("val1: {val1}, {pk1}, {stake1}"); - println!("val2: {val2}, {pk2}, {stake2}"); - println!("val3: {val3}, {pk3}, {stake3}"); + let ((val3, pk3), stake3) = (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); + println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); init_genesis( &mut s, @@ -1498,14 +1498,14 @@ fn test_validator_sets_swap() { // Add 2 bonds, one for val2 and greater one for val3 let bonds_epoch_1 = pipeline_epoch; - let bond2 = token::Amount::from(1); + let bond2 = token::Amount::from_uint(1, 0).unwrap(); let stake2 = stake2 + bond2; - let bond3 = token::Amount::from(4); + let bond3 = token::Amount::from_uint(4, 0).unwrap(); let stake3 = stake3 + bond3; assert!(stake2 < stake3); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake2), 0); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake3), 0); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake2).unwrap() as u64), 0); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake3).unwrap() as u64), 0); update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch) .unwrap(); @@ -1523,13 +1523,13 @@ fn test_validator_sets_swap() { // Add 2 more bonds, same amount for `val2` and val3` let bonds_epoch_2 = pipeline_epoch; - let bonds = token::Amount::whole(1); + let bonds = token::Amount::native_whole(1); let stake2 = stake2 + bonds; let stake3 = stake3 + bonds; assert!(stake2 < stake3); assert_eq!( - into_tm_voting_power(params.tm_votes_per_token, stake2), - into_tm_voting_power(params.tm_votes_per_token, stake3) + into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake2).unwrap() as u64), + into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake3).unwrap() as u64) ); update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch) @@ -1567,7 +1567,7 @@ fn test_validator_sets_swap() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: stake3.into(), + bonded_stake: u128::try_from(stake3).unwrap() as u64, }) ); } diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 487e52e511..8443842165 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -17,6 +17,7 @@ use rust_decimal::Decimal; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; +use namada_core::types::token::Change; use super::arb_genesis_validators; use crate::parameters::testing::{arb_pos_params, arb_rate}; @@ -584,8 +585,9 @@ impl ConcretePosState { assert!( consensus_stake >= below_cap_stake, "Consensus validator {consensus_addr} with stake \ - {consensus_stake} and below-capacity {below_cap_addr} \ - with stake {below_cap_stake} should be swapped." + {} and below-capacity {below_cap_addr} \ + with stake {} should be swapped.", + consensus_stake.to_string_native(), below_cap_stake.to_string_native() ); } } @@ -759,11 +761,12 @@ impl AbstractStateMachine for AbstractPosState { let arb_unbondable = prop::sample::select(unbondable); let arb_unbond = arb_unbondable.prop_flat_map(|(id, deltas_sum)| { + let deltas_sum = i128::try_from(deltas_sum).unwrap(); // Generate an amount to unbond, up to the sum assert!(deltas_sum > 0); (0..deltas_sum).prop_map(move |to_unbond| { let id = id.clone(); - let amount = token::Amount::from_change(to_unbond); + let amount = token::Amount::from_change(Change::from(to_unbond)); Transition::Unbond { id, amount } }) }); @@ -952,7 +955,7 @@ impl AbstractPosState { let bond = bonds.entry(id.clone()).or_default(); *bond += change; // Remove fully unbonded entries - if *bond == 0 { + if bond.is_zero() { bonds.remove(id); } } @@ -1120,9 +1123,9 @@ impl AbstractPosState { |mut acc, (_epoch, bonds)| { for (id, delta) in bonds { let entry = acc.entry(id.clone()).or_default(); - *entry += delta; + *entry += *delta; // Remove entries that are fully unbonded - if *entry == 0 { + if entry.is_zero() { acc.remove(id); } } @@ -1198,5 +1201,5 @@ fn arb_delegation( // Bond up to 10 tokens (10M micro units) to avoid overflows pub fn arb_bond_amount() -> impl Strategy { - (1_u64..10).prop_map(token::Amount::from) + (1_u64..10).prop_map(|val| token::Amount::from_uint(val, 0).unwrap()) } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index bb2965ded6..173d7f6b62 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.65.0" +channel = "1.68.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] targets = ['wasm32-unknown-unknown'] \ No newline at end of file From 8efcc323b74c772110a4df031f241d729d642c72 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 13 Apr 2023 13:46:46 +0200 Subject: [PATCH 17/69] [fix]: Fixed the router tests --- core/src/types/transaction/mod.rs | 6 +- core/src/types/transaction/wrapper.rs | 9 +- proof_of_stake/src/tests.rs | 56 ++- proof_of_stake/src/tests/state_machine.rs | 14 +- shared/src/ledger/queries/router.rs | 546 +++++++++++----------- 5 files changed, 341 insertions(+), 290 deletions(-) diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 5683c5a2fe..b1148b5eeb 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -424,8 +424,7 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, 0) - .expect("Test failed"), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &keypair, @@ -462,8 +461,7 @@ pub mod tx_types { // the signed tx let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, 0) - .expect("Test failed"), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &keypair, diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 4dac6e7ce2..1bfd345400 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -435,8 +435,7 @@ pub mod wrapper_tx { let wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, 0) - .expect("Test failed"), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &keypair, @@ -464,8 +463,7 @@ pub mod wrapper_tx { let mut wrapper = WrapperTx::new( Fee { - amount: Amount::from_uint(10, 0) - .expect("Test failed"), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &gen_keypair(), @@ -499,8 +497,7 @@ pub mod wrapper_tx { // the signed tx let mut tx = WrapperTx::new( Fee { - amount: Amount::from_uint(10, 0) - .expect("Test failed"), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &keypair, diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 9b7de24f7c..921dce9df9 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -831,13 +831,20 @@ fn test_validator_sets() { // Start with two genesis validators with 1 NAM stake let epoch = Epoch::default(); - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::native_whole(1)); - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::native_whole(1)); - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::native_whole(10)); - let ((val4, pk4), stake4) = (gen_validator(), token::Amount::native_whole(1)); - let ((val5, pk5), stake5) = (gen_validator(), token::Amount::native_whole(100)); - let ((val6, pk6), stake6) = (gen_validator(), token::Amount::native_whole(1)); - let ((val7, pk7), stake7) = (gen_validator(), token::Amount::native_whole(1)); + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::native_whole(10)); + let ((val4, pk4), stake4) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val5, pk5), stake5) = + (gen_validator(), token::Amount::native_whole(100)); + let ((val6, pk6), stake6) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val7, pk7), stake7) = + (gen_validator(), token::Amount::native_whole(1)); println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); @@ -1454,11 +1461,14 @@ fn test_validator_sets_swap() { // Start with two genesis validators, one with 1 voting power and other 0 let epoch = Epoch::default(); // 1M voting power - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::native_whole(10)); + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(10)); // 0 voting power - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); // 0 voting power - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); @@ -1504,8 +1514,20 @@ fn test_validator_sets_swap() { let stake3 = stake3 + bond3; assert!(stake2 < stake3); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake2).unwrap() as u64), 0); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake3).unwrap() as u64), 0); + assert_eq!( + into_tm_voting_power( + params.tm_votes_per_token, + u128::try_from(stake2).unwrap() as u64 + ), + 0 + ); + assert_eq!( + into_tm_voting_power( + params.tm_votes_per_token, + u128::try_from(stake3).unwrap() as u64 + ), + 0 + ); update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch) .unwrap(); @@ -1528,8 +1550,14 @@ fn test_validator_sets_swap() { let stake3 = stake3 + bonds; assert!(stake2 < stake3); assert_eq!( - into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake2).unwrap() as u64), - into_tm_voting_power(params.tm_votes_per_token, u128::try_from(stake3).unwrap() as u64) + into_tm_voting_power( + params.tm_votes_per_token, + u128::try_from(stake2).unwrap() as u64 + ), + into_tm_voting_power( + params.tm_votes_per_token, + u128::try_from(stake3).unwrap() as u64 + ) ); update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 8443842165..3662f4e382 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -9,6 +9,7 @@ use namada_core::types::address::{self, Address}; use namada_core::types::key; use namada_core::types::key::common::PublicKey; use namada_core::types::storage::Epoch; +use namada_core::types::token::Change; use proptest::prelude::*; use proptest::prop_state_machine; use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; @@ -17,7 +18,6 @@ use rust_decimal::Decimal; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; -use namada_core::types::token::Change; use super::arb_genesis_validators; use crate::parameters::testing::{arb_pos_params, arb_rate}; @@ -584,10 +584,11 @@ impl ConcretePosState { { assert!( consensus_stake >= below_cap_stake, - "Consensus validator {consensus_addr} with stake \ - {} and below-capacity {below_cap_addr} \ - with stake {} should be swapped.", - consensus_stake.to_string_native(), below_cap_stake.to_string_native() + "Consensus validator {consensus_addr} with stake {} and \ + below-capacity {below_cap_addr} with stake {} should be \ + swapped.", + consensus_stake.to_string_native(), + below_cap_stake.to_string_native() ); } } @@ -766,7 +767,8 @@ impl AbstractStateMachine for AbstractPosState { assert!(deltas_sum > 0); (0..deltas_sum).prop_map(move |to_unbond| { let id = id.clone(); - let amount = token::Amount::from_change(Change::from(to_unbond)); + let amount = + token::Amount::from_change(Change::from(to_unbond)); Transition::Unbond { id, amount } }) }); diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index f959a7a54c..799a34e5bd 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -828,263 +828,289 @@ macro_rules! router { ); } -// You can expand the `handlers!` macro invocation with e.g.: -// ```shell -// cargo expand ledger::queries::router::test_rpc_handlers --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib -// ``` -// #[cfg(test)] -// mod test_rpc_handlers { -// use borsh::BorshSerialize; -// -// use crate::ledger::queries::{ -// EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, -// }; -// use crate::ledger::storage::{DBIter, StorageHasher, DB}; -// use crate::ledger::storage_api::{self, ResultExt}; -// use crate::types::storage::Epoch; -// use crate::types::token; -// -// A little macro to generate boilerplate for RPC handler functions. -// These are implemented to return their name as a String, joined by -// slashes with their argument values turned `to_string()`, if any. -// macro_rules! handlers { -// ( -// name and params, if any -// $( $name:ident $( ( $( $param:ident: $param_ty:ty ),* ) )? ),* -// optional trailing comma -// $(,)? ) => { -// $( -// pub fn $name( -// _ctx: RequestCtx<'_, D, H>, -// $( $( $param: $param_ty ),* )? -// ) -> storage_api::Result -// where -// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, -// H: 'static + StorageHasher + Sync, -// { -// let data = stringify!($name).to_owned(); -// $( $( -// let data = format!("{data}/{}", $param); -// )* )? -// Ok(data) -// } -// )* -// }; -// } -// -// Generate handler functions for the router below -// handlers!( -// a, -// b0i, -// b0ii, -// b1, -// b2i(balance: token::Amount), -// b3(a1: token::Amount, a2: token::Amount, a3: token::Amount), -// b3i(a1: token::Amount, a2: token::Amount, a3: token::Amount), -// b3ii(a1: token::Amount, a2: token::Amount, a3: token::Amount), -// x, -// y(untyped_arg: &str), -// z(untyped_arg: &str), -// ); -// -// This handler is hand-written, because the test helper macro doesn't -// support optional args. -// pub fn b3iii( -// _ctx: RequestCtx<'_, D, H>, -// a1: token::Amount, -// a2: token::Amount, -// a3: Option, -// ) -> storage_api::Result -// where -// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, -// H: 'static + StorageHasher + Sync, -// { -// let data = "b3iii".to_owned(); -// let data = format!("{data}/{}", a1); -// let data = format!("{data}/{}", a2); -// let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); -// Ok(data) -// } -// -// This handler is hand-written, because the test helper macro doesn't -// support optional args. -// pub fn b3iiii( -// _ctx: RequestCtx<'_, D, H>, -// a1: token::Amount, -// a2: token::Amount, -// a3: Option, -// a4: Option, -// ) -> storage_api::Result -// where -// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, -// H: 'static + StorageHasher + Sync, -// { -// let data = "b3iiii".to_owned(); -// let data = format!("{data}/{}", a1); -// let data = format!("{data}/{}", a2); -// let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); -// let data = a4.map(|a4| format!("{data}/{}", a4)).unwrap_or(data); -// Ok(data) -// } -// -// This handler is hand-written, because the test helper macro doesn't -// support handlers with `with_options`. -// pub fn c( -// _ctx: RequestCtx<'_, D, H>, -// _request: &RequestQuery, -// ) -> storage_api::Result -// where -// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, -// H: 'static + StorageHasher + Sync, -// { -// let data = "c".to_owned().try_to_vec().into_storage_result()?; -// Ok(ResponseQuery { -// data, -// ..ResponseQuery::default() -// }) -// } -// } -// -// You can expand the `router!` macro invocation with e.g.: -// ```shell -// cargo expand ledger::queries::router::test_rpc --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib -// ``` -// #[cfg(test)] -// mod test_rpc { -// use super::test_rpc_handlers::*; -// use crate::types::storage::Epoch; -// use crate::types::token; -// -// Setup an RPC router for testing -// router! {TEST_RPC, -// ( "sub" ) = (sub TEST_SUB_RPC), -// ( "a" ) -> String = a, -// ( "b" ) = { -// ( "0" ) = { -// ( "i" ) -> String = b0i, -// ( "ii" ) -> String = b0ii, -// }, -// ( "1" ) -> String = b1, -// ( "2" ) = { -// ( "i" / [balance: token::Amount] ) -> String = b2i, -// }, -// ( "3" / [a1: token::Amount] / [a2: token::Amount] ) = { -// ( "i" / [a3: token:: Amount] ) -> String = b3i, -// ( [a3: token:: Amount] ) -> String = b3, -// ( [a3: token:: Amount] / "ii" ) -> String = b3ii, -// ( [a3: opt token::Amount] / "iii" ) -> String = b3iii, -// ( "iiii" / [a3: opt token::Amount] / "xyz" / [a4: opt Epoch] ) -> String = -// b3iiii, }, -// }, -// ( "c" ) -> String = (with_options c), -// } -// -// router! {TEST_SUB_RPC, -// ( "x" ) -> String = x, -// ( "y" / [untyped_arg] ) -> String = y, -// ( "z" / [untyped_arg] ) -> String = z, -// } -// } -// -// #[cfg(test)] -// mod test { -// use super::test_rpc::TEST_RPC; -// use crate::ledger::queries::testing::TestClient; -// use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; -// use crate::ledger::storage_api; -// use crate::types::storage::Epoch; -// use crate::types::token; -// -// Test all the possible paths in `TEST_RPC` router. -// #[tokio::test] -// async fn test_router_macro() -> storage_api::Result<()> { -// let client = TestClient::new(TEST_RPC); -// -// Test request with an invalid path -// let request = RequestQuery { -// path: "/invalid".to_owned(), -// ..RequestQuery::default() -// }; -// let ctx = RequestCtx { -// event_log: &client.event_log, -// wl_storage: &client.wl_storage, -// vp_wasm_cache: client.vp_wasm_cache.clone(), -// tx_wasm_cache: client.tx_wasm_cache.clone(), -// storage_read_past_height_limit: None, -// }; -// let result = TEST_RPC.handle(ctx, &request); -// assert!(result.is_err()); -// -// Test requests to valid paths using the router's methods -// -// let result = TEST_RPC.a(&client).await.unwrap(); -// assert_eq!(result, "a"); -// -// let result = TEST_RPC.b0i(&client).await.unwrap(); -// assert_eq!(result, "b0i"); -// -// let result = TEST_RPC.b0ii(&client).await.unwrap(); -// assert_eq!(result, "b0ii"); -// -// let result = TEST_RPC.b1(&client).await.unwrap(); -// assert_eq!(result, "b1"); -// -// let balance = token::Amount::native_whole(123_000_000); -// let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); -// assert_eq!(result, format!("b2i/{balance}")); -// -// let a1 = token::Amount::native_whole(345); -// let a2 = token::Amount::native_whole(123_000); -// let a3 = token::Amount::native_whole(1_000_999); -// let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); -// assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); -// -// let result = TEST_RPC.b3i(&client, &a1, &a2, &a3).await.unwrap(); -// assert_eq!(result, format!("b3i/{a1}/{a2}/{a3}")); -// -// let result = TEST_RPC.b3ii(&client, &a1, &a2, &a3).await.unwrap(); -// assert_eq!(result, format!("b3ii/{a1}/{a2}/{a3}")); -// -// let result = -// TEST_RPC.b3iii(&client, &a1, &a2, &Some(a3)).await.unwrap(); -// assert_eq!(result, format!("b3iii/{a1}/{a2}/{a3}")); -// -// let result = TEST_RPC.b3iii(&client, &a1, &a2, &None).await.unwrap(); -// assert_eq!(result, format!("b3iii/{a1}/{a2}")); -// -// let result = TEST_RPC -// .b3iiii(&client, &a1, &a2, &Some(a3), &None) -// .await -// .unwrap(); -// assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}")); -// -// let a4 = Epoch::from(10); -// let result = TEST_RPC -// .b3iiii(&client, &a1, &a2, &Some(a3), &Some(a4)) -// .await -// .unwrap(); -// assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}/{a4}")); -// -// let result = TEST_RPC -// .b3iiii(&client, &a1, &a2, &None, &None) -// .await -// .unwrap(); -// assert_eq!(result, format!("b3iiii/{a1}/{a2}")); -// -// let result = TEST_RPC.c(&client, None, None, false).await.unwrap(); -// assert_eq!(result.data, format!("c")); -// -// let result = TEST_RPC.test_sub_rpc().x(&client).await.unwrap(); -// assert_eq!(result, format!("x")); -// -// let arg = "test123"; -// let result = TEST_RPC.test_sub_rpc().y(&client, arg).await.unwrap(); -// assert_eq!(result, format!("y/{arg}")); -// -// let arg = "test321"; -// let result = TEST_RPC.test_sub_rpc().z(&client, arg).await.unwrap(); -// assert_eq!(result, format!("z/{arg}")); -// -// Ok(()) -// } -// } +/// You can expand the `handlers!` macro invocation with e.g.: +/// ```shell +/// cargo expand ledger::queries::router::test_rpc_handlers --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib +/// ``` +#[cfg(test)] +mod test_rpc_handlers { + use borsh::BorshSerialize; + + use crate::ledger::queries::{ + EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, + }; + use crate::ledger::storage::{DBIter, StorageHasher, DB}; + use crate::ledger::storage_api::{self, ResultExt}; + use crate::types::storage::Epoch; + use crate::types::token; + + /// A little macro to generate boilerplate for RPC handler functions. + /// These are implemented to return their name as a String, joined by + /// slashes with their argument values turned `to_string()`, if any. + macro_rules! handlers { + ( + // name and params, if any + $( $name:ident $( ( $( $param:ident: $param_ty:ty ),* ) )? ),* + // optional trailing comma + $(,)? ) => { + $( + pub fn $name( + _ctx: RequestCtx<'_, D, H>, + $( $( $param: $param_ty ),* )? + ) -> storage_api::Result + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + { + let data = stringify!($name).to_owned(); + $( $( + let data = format!("{data}/{}", $param); + )* )? + Ok(data) + } + )* + }; + } + + // Generate handler functions for the router below + handlers!( + a, + b0i, + b0ii, + b1, + b2i(balance: token::DenominatedAmount), + b3( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), + b3i( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), + b3ii( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), + x, + y(untyped_arg: &str), + z(untyped_arg: &str), + ); + + /// This handler is hand-written, because the test helper macro doesn't + /// support optional args. + pub fn b3iii( + _ctx: RequestCtx<'_, D, H>, + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: Option, + ) -> storage_api::Result + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + { + let data = "b3iii".to_owned(); + let data = format!("{data}/{}", a1); + let data = format!("{data}/{}", a2); + let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); + Ok(data) + } + + /// This handler is hand-written, because the test helper macro doesn't + /// support optional args. + pub fn b3iiii( + _ctx: RequestCtx<'_, D, H>, + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: Option, + a4: Option, + ) -> storage_api::Result + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + { + let data = "b3iiii".to_owned(); + let data = format!("{data}/{}", a1); + let data = format!("{data}/{}", a2); + let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); + let data = a4.map(|a4| format!("{data}/{}", a4)).unwrap_or(data); + Ok(data) + } + + /// This handler is hand-written, because the test helper macro doesn't + /// support handlers with `with_options`. + pub fn c( + _ctx: RequestCtx<'_, D, H>, + _request: &RequestQuery, + ) -> storage_api::Result + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + { + let data = "c".to_owned().try_to_vec().into_storage_result()?; + Ok(ResponseQuery { + data, + ..ResponseQuery::default() + }) + } +} + +/// You can expand the `router!` macro invocation with e.g.: +/// ```shell +/// cargo expand ledger::queries::router::test_rpc --features "ferveo-tpke, ibc-mocks, testing, wasm-runtime, tendermint-rpc" --tests --lib +/// ``` +#[cfg(test)] +mod test_rpc { + use super::test_rpc_handlers::*; + use crate::types::storage::Epoch; + use crate::types::token; + + // Setup an RPC router for testing + router! {TEST_RPC, + ( "sub" ) = (sub TEST_SUB_RPC), + ( "a" ) -> String = a, + ( "b" ) = { + ( "0" ) = { + ( "i" ) -> String = b0i, + ( "ii" ) -> String = b0ii, + }, + ( "1" ) -> String = b1, + ( "2" ) = { + ( "i" / [balance: token::DenominatedAmount] ) -> String = b2i, + }, + ( "3" / [a1: token::DenominatedAmount] / [a2: token::DenominatedAmount] ) = { + ( "i" / [a3: token::DenominatedAmount] ) -> String = b3i, + ( [a3: token::DenominatedAmount] ) -> String = b3, + ( [a3: token::DenominatedAmount] / "ii" ) -> String = b3ii, + ( [a3: opt token::DenominatedAmount] / "iii" ) -> String = b3iii, + ( "iiii" / [a3: opt token::DenominatedAmount] / "xyz" / [a4: opt Epoch] ) -> String = b3iiii, + }, + }, + ( "c" ) -> String = (with_options c), + } + + router! {TEST_SUB_RPC, + ( "x" ) -> String = x, + ( "y" / [untyped_arg] ) -> String = y, + ( "z" / [untyped_arg] ) -> String = z, + } +} + +#[cfg(test)] +mod test { + use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; + + use super::test_rpc::TEST_RPC; + use crate::ledger::queries::testing::TestClient; + use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; + use crate::ledger::storage_api; + use crate::types::storage::Epoch; + use crate::types::token; + + /// Test all the possible paths in `TEST_RPC` router. + #[tokio::test] + async fn test_router_macro() -> storage_api::Result<()> { + let client = TestClient::new(TEST_RPC); + + // Test request with an invalid path + let request = RequestQuery { + path: "/invalid".to_owned(), + ..RequestQuery::default() + }; + let ctx = RequestCtx { + event_log: &client.event_log, + wl_storage: &client.wl_storage, + vp_wasm_cache: client.vp_wasm_cache.clone(), + tx_wasm_cache: client.tx_wasm_cache.clone(), + storage_read_past_height_limit: None, + }; + let result = TEST_RPC.handle(ctx, &request); + assert!(result.is_err()); + + // Test requests to valid paths using the router's methods + + let result = TEST_RPC.a(&client).await.unwrap(); + assert_eq!(result, "a"); + + let result = TEST_RPC.b0i(&client).await.unwrap(); + assert_eq!(result, "b0i"); + + let result = TEST_RPC.b0ii(&client).await.unwrap(); + assert_eq!(result, "b0ii"); + + let result = TEST_RPC.b1(&client).await.unwrap(); + assert_eq!(result, "b1"); + + let balance = token::DenominatedAmount { + amount: token::Amount::native_whole(123_000_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); + assert_eq!(result, format!("b2i/{balance}")); + + let a1 = token::DenominatedAmount { + amount: token::Amount::native_whole(345), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let a2 = token::DenominatedAmount { + amount: token::Amount::native_whole(123_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let a3 = token::DenominatedAmount { + amount: token::Amount::native_whole(1_000_999), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); + assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); + + let result = TEST_RPC.b3i(&client, &a1, &a2, &a3).await.unwrap(); + assert_eq!(result, format!("b3i/{a1}/{a2}/{a3}")); + + let result = TEST_RPC.b3ii(&client, &a1, &a2, &a3).await.unwrap(); + assert_eq!(result, format!("b3ii/{a1}/{a2}/{a3}")); + + let result = + TEST_RPC.b3iii(&client, &a1, &a2, &Some(a3)).await.unwrap(); + assert_eq!(result, format!("b3iii/{a1}/{a2}/{a3}")); + + let result = TEST_RPC.b3iii(&client, &a1, &a2, &None).await.unwrap(); + assert_eq!(result, format!("b3iii/{a1}/{a2}")); + + let result = TEST_RPC + .b3iiii(&client, &a1, &a2, &Some(a3), &None) + .await + .unwrap(); + assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}")); + + let a4 = Epoch::from(10); + let result = TEST_RPC + .b3iiii(&client, &a1, &a2, &Some(a3), &Some(a4)) + .await + .unwrap(); + assert_eq!(result, format!("b3iiii/{a1}/{a2}/{a3}/{a4}")); + + let result = TEST_RPC + .b3iiii(&client, &a1, &a2, &None, &None) + .await + .unwrap(); + assert_eq!(result, format!("b3iiii/{a1}/{a2}")); + + let result = TEST_RPC.c(&client, None, None, false).await.unwrap(); + assert_eq!(result.data, format!("c")); + + let result = TEST_RPC.test_sub_rpc().x(&client).await.unwrap(); + assert_eq!(result, format!("x")); + + let arg = "test123"; + let result = TEST_RPC.test_sub_rpc().y(&client, arg).await.unwrap(); + assert_eq!(result, format!("y/{arg}")); + + let arg = "test321"; + let result = TEST_RPC.test_sub_rpc().z(&client, arg).await.unwrap(); + assert_eq!(result, format!("z/{arg}")); + + Ok(()) + } +} From 70702a108fb758f70150382a628bad892185d954 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 13 Apr 2023 14:08:53 +0200 Subject: [PATCH 18/69] [fix]: Cleanup up the wasms --- tests/src/native_vp/pos.rs | 16 +---- tests/src/vm_host_env/ibc.rs | 2 +- tests/src/vm_host_env/mod.rs | 18 ++--- wasm/checksums.json | 36 +++++----- .../src/tx_change_validator_commission.rs | 6 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 16 ++--- wasm/wasm_source/src/vp_user.rs | 72 ++++--------------- wasm/wasm_source/src/vp_validator.rs | 72 ++++--------------- 8 files changed, 59 insertions(+), 179 deletions(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index b2e6166703..6b606f33ca 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -591,9 +591,7 @@ pub mod testing { use namada::types::key::RefTo; use namada::types::storage::Epoch; use namada::types::{address, key, token}; - use namada_core::types::token::{ - Amount, Change, NATIVE_MAX_DECIMAL_PLACES, - }; + use namada_core::types::token::{Amount, Change}; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; use rust_decimal::Decimal; @@ -767,11 +765,7 @@ pub mod testing { arb_validator, ) .prop_map(|(amount, owner, validator)| ValidPosAction::Bond { - amount: Amount::from_uint( - amount, - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(), + amount: Amount::from_uint(amount, 0).unwrap(), owner, validator, }); @@ -809,11 +803,7 @@ pub mod testing { // Unbond an arbitrary amount up to what's available (0..current_bond_amount).prop_map(move |amount| { ValidPosAction::Unbond { - amount: Amount::from_uint( - amount, - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(), + amount: Amount::from_uint(amount, 0).unwrap(), owner: bond_id.source.clone(), validator: bond_id.validator.clone(), } diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 23d59dfb01..45b4e364c9 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -202,7 +202,7 @@ pub fn init_storage() -> (Address, Address) { // initialize an account let account = tx::ctx().init_account(code).unwrap(); let key = token::balance_key(&token, &account); - let init_bal = Amount::native_whole(1_000_000_000u64); + let init_bal = Amount::from_uint(1_000_000_000u64, 0).unwrap(); tx::ctx().write(&key, init_bal).unwrap(); (token, account) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 8754928b3e..e345d00956 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -35,7 +35,6 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -1313,9 +1312,7 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); let key = token::multitoken_balance_key(&key_prefix, &sender); - let init_bal = - Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) - .unwrap(); + let init_bal = Amount::from_uint(1_000_000_000u64, 0).unwrap(); writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { @@ -1376,9 +1373,7 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); let key = token::multitoken_balance_key(&key_prefix, &receiver); - let init_bal = - Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) - .unwrap(); + let init_bal = Amount::from_uint(1_000_000_000u64, 0).unwrap(); writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { @@ -1457,11 +1452,10 @@ mod tests { &key_prefix, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let val = - Amount::from_uint(1_000_000_000u64, NATIVE_MAX_DECIMAL_PLACES) - .unwrap() - .try_to_vec() - .unwrap(); + let val = Amount::from_uint(1_000_000_000u64, 0) + .unwrap() + .try_to_vec() + .unwrap(); tx_host_env::with(|env| { env.wl_storage .storage diff --git a/wasm/checksums.json b/wasm/checksums.json index 9e821b3575..82e5672487 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", - "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", - "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", - "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", - "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", - "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", - "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", - "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", - "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", - "vp_token.wasm": "vp_token.ac90ced308b618c6d991939a60735a1679e773bb5e76dd03a4dc4c9d56180ddd.wasm", - "vp_user.wasm": "vp_user.cf363aaf2fd13faa79501d8c8c251bafe22992b9989035b2c2edaf3edbe629fe.wasm", - "vp_validator.wasm": "vp_validator.4f7b0efb2f742b4b605738fe48ede23f57623ff083f23dc18c5bf8c5e26294cd.wasm" + "tx_bond.wasm": "tx_bond.4353af6713777b2738d2c8b5050e32687a63423ea4827c3dce31895f04cf17a8.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.3012d6e8322ed0caf8f818f6f47ac70d40d7bf5366ab49b6594cbb160efd31a6.wasm", + "tx_ibc.wasm": "tx_ibc.91f160e0a518eab3e2b6adb54a19b7d50b30b6936c43de3e3cdd83c2b931cd06.wasm", + "tx_init_account.wasm": "tx_init_account.eee57525a9759281a0ed1579ff13440d09ccb6187ff8a7c9cc7d7027a1611f97.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6b7dc8ddffad3ad01d666ef738e3839a54af7c73b23f24a9c1e6d27b68449dec.wasm", + "tx_init_validator.wasm": "tx_init_validator.f4c7d983bd6076dfe7ca9d6ad497eb352c047543ee01d577e6f56c3b9309e56e.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.5a86cc0bd44d9755a4ce326ed1e178614311704468380ba7b297ed0f62018778.wasm", + "tx_transfer.wasm": "tx_transfer.06235371b028d3a4fafb3e35fd7b7b111763ed630647a39cd5e0655b91b37995.wasm", + "tx_unbond.wasm": "tx_unbond.f1f32820e024be0053abbaf0f5c30622cb0cc2470360b3e0894285fcf982e413.wasm", + "tx_update_vp.wasm": "tx_update_vp.2e319438adbc5256a29f1e8817c1cba69ffd5a8d3a51887f50383a471231d388.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4cc5a9dde9dd1fef3f0dbc6cc2defb500c194c3934cd055d6e46f9b82c528368.wasm", + "tx_withdraw.wasm": "tx_withdraw.368f8bc90a2381169008590ba14a6cf6dce7cfd3f86a76e591eb4c5ed804d5a6.wasm", + "vp_implicit.wasm": "vp_implicit.64e6048d45d832360d59ac3437bebf947313d93ea75dd7538cf55f29b3e29cd6.wasm", + "vp_masp.wasm": "vp_masp.31688087bba04ca8b4c57a17ce7532ad2eb58f19221a94d3a7b4748cae79f420.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.21f0f5e0ed2d604fa291512fb6937aacef77c8e6303da306577a07a6db168564.wasm", + "vp_token.wasm": "vp_token.2ca9b0e8791315a8d29579011d194270bd007fe49e72874f3bbdcef58622361e.wasm", + "vp_user.wasm": "vp_user.225064a9c6b4f1f06d8843daa5fa8795f010d72320dee076b15a7c7e17b1bb3f.wasm", + "vp_validator.wasm": "vp_validator.0202d4cd90930910ffb0a9f12ada0952b4e095968e05ef9d636916115b4d5648.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 5e68993097..b8f2a856cc 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -68,11 +68,7 @@ mod tests { let consensus_key = key::testing::keypair_1().ref_to(); let genesis_validators = [GenesisValidator { address: commission_change.validator.clone(), - tokens: token::Amount::from_uint( - 1_000_000, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(), + tokens: token::Amount::from_uint(1_000_000, 0).unwrap(), consensus_key, commission_rate: initial_rate, max_commission_rate_change: max_change, diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index ab94e88c9f..61a80d681e 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -152,11 +152,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -299,12 +295,12 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint(amount, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let amount = token::Amount::from_uint(amount, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -343,13 +339,13 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); let token = address::nam(); - let amount = token::Amount::from_uint(amount, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let amount = token::Amount::from_uint(amount, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -410,7 +406,7 @@ mod tests { // Init the VP let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, token::NATIVE_MAX_DECIMAL_PLACES).unwrap(); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let keypair = key::testing::keypair_1(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 0e9c0556d9..a44d99ddaa 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -238,11 +238,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -292,11 +288,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -348,11 +340,7 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -406,11 +394,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -433,21 +417,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -486,11 +458,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -513,21 +481,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -574,11 +530,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index f616e9dd31..18aa679fb3 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -246,11 +246,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -299,11 +295,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -355,11 +347,7 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -412,11 +400,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = Decimal::new(5, 2); let max_commission_rate_change = Decimal::new(1, 2); @@ -439,21 +423,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -498,11 +470,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = Decimal::new(5, 2); let max_commission_rate_change = Decimal::new(1, 2); @@ -525,21 +493,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -592,11 +548,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); From 86698a98c242461864a572aa2e0b398658234912 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Apr 2023 11:59:54 +0200 Subject: [PATCH 19/69] [feat]: Added sub-prefixs to denom storage read/writes --- Makefile | 19 +- apps/src/lib/client/rpc.rs | 171 +++++++++++++----- apps/src/lib/client/tx.rs | 50 +++-- apps/src/lib/config/genesis.rs | 12 +- apps/src/lib/node/ledger/shell/init_chain.rs | 11 ++ apps/src/lib/wallet/defaults.rs | 2 +- core/src/ledger/storage_api/token.rs | 7 +- core/src/types/address.rs | 20 +- core/src/types/token.rs | 46 +++-- core/src/types/uint.rs | 4 +- rust-toolchain.toml | 2 +- shared/src/ledger/ibc/vp/token.rs | 13 +- shared/src/ledger/queries/vp/token.rs | 9 +- tx_prelude/src/token.rs | 10 +- wasm/checksums.json | 22 +-- .../vp_testnet_faucet.txt | 7 + wasm/wasm_source/src/vp_implicit.rs | 157 +++++++++------- wasm/wasm_source/src/vp_testnet_faucet.rs | 5 +- wasm/wasm_source/src/vp_user.rs | 32 +++- wasm/wasm_source/src/vp_validator.rs | 32 +++- 20 files changed, 424 insertions(+), 207 deletions(-) create mode 100644 wasm/wasm_source/proptest-regressions/vp_testnet_faucet.txt diff --git a/Makefile b/Makefile index d42b3fe3ec..0778e9e8a0 100644 --- a/Makefile +++ b/Makefile @@ -13,17 +13,23 @@ wasms_for_tests := wasm_for_tests/wasm_source # Paths for all the wasm templates wasm_templates := wasm/tx_template wasm/vp_template +ifdef JOBS +jobs := -j $(JOBS) +else +jobs := +endif + # TODO upgrade libp2p audit-ignores += RUSTSEC-2021-0076 build: - $(cargo) build + $(cargo) $(jobs) build build-test: - $(cargo) +$(nightly) build --tests -Z unstable-options + $(cargo) +$(nightly) build --tests $(jobs) -Z unstable-options build-release: - NAMADA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml + NAMADA_DEV=false $(cargo) build $(jobs) --release --package namada_apps --manifest-path Cargo.toml install-release: NAMADA_DEV=false $(cargo) install --path ./apps --locked @@ -128,6 +134,7 @@ test-e2e: test-unit-abcipp: $(cargo) test \ --manifest-path ./apps/Cargo.toml \ + $(jobs) \ --no-default-features \ --features "testing std abcipp" \ -Z unstable-options \ @@ -136,12 +143,14 @@ test-unit-abcipp: $(cargo) test \ --manifest-path \ ./proof_of_stake/Cargo.toml \ + $(jobs) \ --features "testing" \ -Z unstable-options \ $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./shared/Cargo.toml \ + $(jobs) \ --no-default-features \ --features "testing wasm-runtime abcipp ibc-mocks-abcipp" \ -Z unstable-options \ @@ -149,6 +158,7 @@ test-unit-abcipp: -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./vm_env/Cargo.toml \ + $(jobs) \ --no-default-features \ --features "abcipp" \ -Z unstable-options \ @@ -158,6 +168,7 @@ test-unit-abcipp: test-unit: $(cargo) +$(nightly) test \ $(TEST_FILTER) \ + $(jobs) \ -Z unstable-options \ -- --skip e2e \ -Z unstable-options --report-time @@ -166,12 +177,14 @@ test-unit-mainnet: $(cargo) +$(nightly) test \ --features "mainnet" \ $(TEST_FILTER) \ + $(jobs) -Z unstable-options \ -- --skip e2e \ -Z unstable-options --report-time test-unit-debug: $(debug-cargo) +$(nightly) test \ + $(jobs) $(TEST_FILTER) -- \ -Z unstable-options \ -- --skip e2e \ diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 443885857a..4cd02f9fdd 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -367,8 +367,11 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!(" {}:", account); for ((addr, denom), val) in amt.components() { let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let readable = tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -380,6 +383,9 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { format_denominated_amount( &client, addr, + // TODO: apparently MASP doesn't support + // multi-tokens? + &None, token::Amount::from_masp_denominated( val.unsigned_abs(), *denom @@ -399,8 +405,11 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!(" {}:", fvk_map[&account]); for ((addr, denom), val) in amt.components() { let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let readable = tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -412,6 +421,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { format_denominated_amount( &client, addr, + &None, token::Amount::from_masp_denominated( val.unsigned_abs(), *denom @@ -517,27 +527,39 @@ pub async fn query_transparent_balance( (Some(token), Some(owner)) => { let token = ctx.get(&token); let owner = ctx.get_cached(&owner); - let key = match &args.sub_prefix { + let (balance_key, sub_prefix) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = Key::parse(sub_prefix).unwrap(); let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - token::multitoken_balance_key( - &prefix, - &owner.address().unwrap(), + ( + token::multitoken_balance_key( + &prefix, + &owner.address().unwrap(), + ), + Some(sub_prefix), ) } - None => token::balance_key(&token, &owner.address().unwrap()), + None => ( + token::balance_key(&token, &owner.address().unwrap()), + None, + ), }; let currency_code = tokens .get(&token) - .map(|c| Cow::Borrowed(*c)) + .map(|(c, _)| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); - match query_storage_value::(&client, &key).await { + match query_storage_value::(&client, &balance_key) + .await + { Some(balance) => { - let balance = - format_denominated_amount(&client, &token, balance) - .await; + let balance = format_denominated_amount( + &client, + &token, + &sub_prefix, + balance, + ) + .await; match &args.sub_prefix { Some(sub_prefix) => { println!( @@ -676,7 +698,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { let token = ctx.get(token); let currency_code = tokens .get(&token) - .map(|c| Cow::Borrowed(*c)) + .map(|(c, _)| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); let mut total_balance = token::Amount::default(); for denom in MaspDenom::iter() { @@ -698,6 +720,8 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { let formatted = format_denominated_amount( &client, &token, + // TODO: Is this correct? + &None, total_balance, ) .await; @@ -729,12 +753,21 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { found_any = true; } let addr_enc = addr.encode(); - let formatted = - format_denominated_amount(&client, addr, asset_value) - .await; + let formatted = format_denominated_amount( + &client, + addr, + // TODO: Is this correct? + &None, + asset_value, + ) + .await; println!( " {}: {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()), formatted, ); } @@ -763,29 +796,39 @@ async fn print_balances( let tokens = address::tokens(); let currency_code = tokens .get(token) - .map(|c| Cow::Borrowed(*c)) + .map(|(c, _)| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); writeln!(w, "Token {}", currency_code).unwrap(); let mut print_num = 0; for (key, balance) in balances { let (o, s) = match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => ( + Some((sub_prefix, [tok, owner])) => ( owner.clone(), format!( "with {}: {}, owned by {}", - sub_prefix, - format_denominated_amount(client, token, balance).await, + sub_prefix.clone(), + format_denominated_amount( + client, + tok, + &Some(sub_prefix), + balance + ) + .await, lookup_alias(ctx, owner) ), ), None => { - if let Some(owner) = token::is_any_token_balance_key(&key) { + if let Some([tok, owner]) = + token::is_any_token_balance_key(&key) + { ( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount(client, token, balance) - .await, + format_denominated_amount( + client, tok, &None, balance + ) + .await, lookup_alias(ctx, owner) ), ) @@ -1018,7 +1061,7 @@ pub async fn query_shielded_balance( let currency_code = tokens .get(&token) - .map(|c| Cow::Borrowed(*c)) + .map(|(c, _)| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); if total_balance.is_zero() { println!( @@ -1029,8 +1072,14 @@ pub async fn query_shielded_balance( println!( "{}: {}", currency_code, - format_denominated_amount(&client, &token, total_balance) - .await + format_denominated_amount( + &client, + &token, + // TODO: Is this correct? + &None, + total_balance + ) + .await ); } } @@ -1083,6 +1132,7 @@ pub async fn query_shielded_balance( tokens .get(&addr) .cloned() + .map(|a| a.0) .unwrap_or(addr_enc.as_str()) ); read_tokens.insert(addr.clone()); @@ -1093,7 +1143,9 @@ pub async fn query_shielded_balance( denom, ); let formatted = format_denominated_amount( - &client, &addr, value, + &client, &addr, + // TODO: Is this correct? + &None, value, ) .await; println!(" {}, owned by {}", formatted, fvk); @@ -1111,7 +1163,7 @@ pub async fn query_shielded_balance( } } // Print zero balances for remaining assets - for (token, currency_code) in tokens { + for (token, (currency_code, _)) in tokens { if !read_tokens.contains(&token) { println!("Shielded Token {}:", currency_code); println!( @@ -1129,7 +1181,7 @@ pub async fn query_shielded_balance( let mut found_any = false; let currency_code = tokens .get(&token) - .map(|c| Cow::Borrowed(*c)) + .map(|(c, _)| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); println!("Shielded Token {}:", currency_code); for fvk in viewing_keys { @@ -1166,8 +1218,11 @@ pub async fn query_shielded_balance( found_any = true; } } - let formatted = - format_denominated_amount(&client, &token, balance).await; + let formatted = format_denominated_amount( + &client, &token, // TODO: Is this correct? + &None, balance, + ) + .await; println!(" {}, owned by {}", formatted, fvk); } if !found_any { @@ -1237,8 +1292,16 @@ pub async fn print_decoded_balance( let addr_enc = addr.encode(); println!( "{} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - format_denominated_amount(client, addr, amount).await, + tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()), + format_denominated_amount( + client, addr, // TODO: Is this correct? + &None, amount, + ) + .await, ); } } @@ -1265,9 +1328,17 @@ pub async fn print_decoded_balance_with_epoch( let addr_enc = addr.encode(); println!( "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()), epoch, - format_denominated_amount(client, addr, amount).await, + format_denominated_amount( + client, addr, // TODO: Is this correct? + &None, amount, + ) + .await, ); } } @@ -2003,7 +2074,11 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { let addr_enc = addr.encode(); print!( "{}[{}]: ", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()), epoch, ); // Now print out the components of the allowed conversion @@ -2018,7 +2093,11 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { "{}{} {}[{}]", prefix, val, - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .map(|a| a.0) + .unwrap_or(addr_enc.as_str()), epoch ); // Future iterations need to be prefixed with + @@ -2781,10 +2860,14 @@ pub(super) fn unwrap_client_response( pub(super) async fn format_denominated_amount( client: &HttpClient, token: &Address, + sub_prefix: &Option, amount: token::Amount, ) -> String { let denom = unwrap_client_response( - RPC.vp().token().denomination(client, token).await, + RPC.vp() + .token() + .denomination(client, token, sub_prefix) + .await, ) .unwrap_or_else(|| { println!( @@ -2815,13 +2898,17 @@ pub async fn validate_amount( client: &HttpClient, amount: InputAmount, token: &Address, + sub_prefix: &Option, ) -> token::DenominatedAmount { let input_amount = match amount { InputAmount::Unvalidated(amt) => amt.canonical(), InputAmount::Validated(amt) => return amt, }; let denom = unwrap_client_response( - RPC.vp().token().denomination(client, token).await, + RPC.vp() + .token() + .denomination(client, token, sub_prefix) + .await, ) .unwrap_or_else(|| { println!( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6e64df6114..433ee3423d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1607,17 +1607,6 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { safe_exit(1) } } - // validate the amount given - let validated_amount = - validate_amount(&client, parsed_args.amount, &parsed_args.token).await; - let validate_fee = validate_amount( - &client, - parsed_args.tx.fee_amount, - &parsed_args.tx.fee_token, - ) - .await; - parsed_args.amount = InputAmount::Validated(validated_amount); - parsed_args.tx.fee_amount = InputAmount::Validated(validate_fee); // Check source balance let (sub_prefix, balance_key) = match args.sub_prefix { Some(sub_prefix) => { @@ -1633,6 +1622,25 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { } None => (None, token::balance_key(&parsed_args.token, &source)), }; + // validate the amount given + let validated_amount = validate_amount( + &client, + parsed_args.amount, + &parsed_args.token, + &sub_prefix, + ) + .await; + let validate_fee = validate_amount( + &client, + parsed_args.tx.fee_amount, + &parsed_args.tx.fee_token, + // TODO: Currently multi-tokens cannot be used to pay fees + &None, + ) + .await; + parsed_args.amount = InputAmount::Validated(validated_amount); + parsed_args.tx.fee_amount = InputAmount::Validated(validate_fee); + match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { @@ -1640,6 +1648,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { let balance_amount = format_denominated_amount( &client, &parsed_args.token, + &sub_prefix, balance, ) .await; @@ -1820,11 +1829,20 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { { Some(balance) => { if balance < args.amount { - let formatted_amount = - format_denominated_amount(&client, &token, args.amount) - .await; - let formatted_balance = - format_denominated_amount(&client, &token, balance).await; + let formatted_amount = format_denominated_amount( + &client, + &token, + &sub_prefix, + args.amount, + ) + .await; + let formatted_balance = format_denominated_amount( + &client, + &token, + &sub_prefix, + balance, + ) + .await; eprintln!( "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 7dde5fbe5b..52abc2f977 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -18,6 +18,7 @@ use namada::types::chain::ProposalBytes; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::{DateTimeUtc, DurationSecs}; +use namada::types::token::Denomination; use namada::types::{storage, token}; use rust_decimal::Decimal; @@ -41,6 +42,7 @@ pub mod genesis_config { use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::Rfc3339String; + use namada::types::token::Denomination; use namada::types::{storage, token}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -207,6 +209,8 @@ pub mod genesis_config { pub struct TokenAccountConfig { // Address of token account (default: generate). pub address: Option, + // The number of decimal places amounts of this token has + pub denom: Denomination, // Filename of token account VP. (default: token VP) pub vp: Option, // Initial balances held by accounts defined elsewhere. @@ -400,6 +404,7 @@ pub mod genesis_config { TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), + denom: config.denom, vp_code_path: token_vp_config.filename.to_owned(), vp_sha256: token_vp_config .sha256 @@ -799,6 +804,8 @@ pub struct EstablishedAccount { pub struct TokenAccount { /// Address pub address: Address, + /// The number of decimal places amounts of this token has + pub denom: Denomination, /// Validity predicate code WASM pub vp_code_path: String, /// Expected SHA-256 hash of the validity predicate wasm @@ -985,9 +992,10 @@ pub fn genesis() -> Genesis { ((&validator.account_key).into(), default_key_tokens), ]); let token_accounts = address::tokens() - .into_keys() - .map(|address| TokenAccount { + .into_iter() + .map(|(address, (_, denom))| TokenAccount { address, + denom, vp_code_path: vp_token_path.into(), vp_sha256: Default::default(), balances: balances.clone(), diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 0bdaff27e2..3ac250536a 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -6,6 +6,7 @@ use std::hash::Hash; use namada::core::ledger::testnet_pow; use namada::ledger::parameters::Parameters; use namada::ledger::pos::into_tm_voting_power; +use namada::ledger::storage_api::token::write_denom; use namada::ledger::storage_api::StorageWrite; use namada::types::key::*; #[cfg(not(feature = "dev"))] @@ -240,11 +241,21 @@ where // Initialize genesis token accounts for genesis::TokenAccount { address, + denom, vp_code_path, vp_sha256, balances, } in genesis.token_accounts { + // associate a token with its denomination. + write_denom( + &mut self.wl_storage, + &address, + // TODO: Should we support multi-tokens at genesis? + None, + denom, + ) + .unwrap(); let vp_code = vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index b0ae08ac83..cf4069e1cc 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -122,7 +122,7 @@ mod dev { ]; let token_addresses = address::tokens() .into_iter() - .map(|(addr, alias)| (alias.into(), addr)); + .map(|(addr, (alias, _))| (alias.into(), addr)); addresses.extend(token_addresses); addresses } diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 793cba632b..2d24a4081c 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -3,6 +3,7 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; +use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{Amount, Change}; @@ -26,11 +27,12 @@ where pub fn read_denom( storage: &S, token: &Address, + sub_prefix: Option<&Key>, ) -> storage_api::Result> where S: StorageRead, { - let key = token::denom_key(token); + let key = token::denom_key(token, sub_prefix); storage.read(&key) } @@ -38,12 +40,13 @@ where pub fn write_denom( storage: &mut S, token: &Address, + sub_prefix: Option<&Key>, denom: token::Denomination, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let key = token::denom_key(token); + let key = token::denom_key(token, sub_prefix); storage.write(&key, denom) } diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 9c3985ff81..b22a141678 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -14,7 +14,7 @@ use thiserror::Error; use crate::types::key; use crate::types::key::PublicKeyHash; -use crate::types::token::MaspDenom; +use crate::types::token::{Denomination, MaspDenom}; /// The length of an established [`Address`] encoded with Borsh. pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 45; @@ -558,16 +558,16 @@ pub fn masp_tx_key() -> crate::types::key::common::SecretKey { } /// Temporary helper for testing, a hash map of tokens addresses with their -/// informal currency codes. -pub fn tokens() -> HashMap { +/// informal currency codes and number of decimal places. +pub fn tokens() -> HashMap { vec![ - (nam(), "NAM"), - (btc(), "BTC"), - (eth(), "ETH"), - (dot(), "DOT"), - (schnitzel(), "Schnitzel"), - (apfel(), "Apfel"), - (kartoffel(), "Kartoffel"), + (nam(), ("NAM", 6.into())), + (btc(), ("BTC", 8.into())), + (eth(), ("ETH", 18.into())), + (dot(), ("DOT", 10.into())), + (schnitzel(), ("Schnitzel", 6.into())), + (apfel(), ("Apfel", 6.into())), + (kartoffel(), ("Kartoffel", 6.into())), ] .into_iter() .collect() diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 0851d8463f..6da010c603 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -216,10 +216,11 @@ impl Amount { pub fn denominated( &self, token: &Address, + sub_prefix: Option<&Key>, storage: &impl StorageRead, ) -> Option { - let denom = - read_denom(storage, token).expect("Should be able to read storage"); + let denom = read_denom(storage, token, sub_prefix) + .expect("Should be able to read storage"); denom.map(|denom| DenominatedAmount { amount: *self, denom, @@ -248,7 +249,10 @@ impl Amount { BorshSerialize, BorshDeserialize, BorshSchema, + Serialize, + Deserialize, )] +#[serde(transparent)] pub struct Denomination(pub u8); impl From for Denomination { @@ -749,23 +753,29 @@ pub fn is_balance_key<'a>( } /// Check if the given storage key is balance key for unspecified token. If it -/// is, returns the owner. -pub fn is_any_token_balance_key(key: &Key) -> Option<&Address> { +/// is, returns the token and owner address. +pub fn is_any_token_balance_key(key: &Key) -> Option<[&Address; 2]> { match &key.segments[..] { [ - DbKeySeg::AddressSeg(_), + DbKeySeg::AddressSeg(token), DbKeySeg::StringSeg(key), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => Some(owner), + ] if key == BALANCE_STORAGE_KEY => Some([token, owner]), _ => None, } } /// Obtain a storage key denomination of a token. -pub fn denom_key(token_addr: &Address) -> Key { - Key::from(token_addr.to_db_key()) - .push(&DENOM_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +pub fn denom_key(token_addr: &Address, sub_prefix: Option<&Key>) -> Key { + match sub_prefix { + Some(sub) => Key::from(token_addr.to_db_key()) + .join(sub) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key"), + None => Key::from(token_addr.to_db_key()) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key"), + } } /// Check if the given storage key is a denomination key for the given token. @@ -773,6 +783,7 @@ pub fn is_denom_key(token_addr: &Address, key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), + .., DbKeySeg::StringSeg(key), ] if key == DENOM_STORAGE_KEY && addr == token_addr) } @@ -802,10 +813,13 @@ pub fn is_multitoken_balance_key<'a>( } /// Check if the given storage key is multitoken balance key for unspecified -/// token. If it is, returns the sub prefix and the owner. -pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { +/// token. If it is, returns the sub prefix and the token and owner addresses. +pub fn is_any_multitoken_balance_key( + key: &Key, +) -> Option<(Key, [&Address; 2])> { match key.segments.first() { - Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), + Some(DbKeySeg::AddressSeg(token)) => multitoken_balance_owner(key) + .map(|(sub, owner)| (sub, [token, owner])), _ => None, } } @@ -1085,12 +1099,12 @@ pub mod testing { /// Generate an arbitrary token amount pub fn arb_amount() -> impl Strategy { - any::().prop_map(Amount::native_whole) + any::().prop_map(|val| Amount::from_uint(val, 0).unwrap()) } /// Generate an arbitrary token amount up to and including given `max` value pub fn arb_amount_ceiled(max: u64) -> impl Strategy { - (0..=max).prop_map(Amount::native_whole) + (0..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) } /// Generate an arbitrary non-zero token amount up to and including given @@ -1098,6 +1112,6 @@ pub mod testing { pub fn arb_amount_non_zero_ceiled( max: u64, ) -> impl Strategy { - (1..=max).prop_map(Amount::native_whole) + (1..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 223044387a..fb6e72376c 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -106,7 +106,7 @@ impl SignedUint { } /// Adds two [`SignedUint`]'s if the absolute value does - /// not exceed [`MAX_SIGNED_AMOUNT`], else returns `None`. + /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. pub fn checked_add(&self, other: &Self) -> Option { if self.non_negative() == other.non_negative() { self.abs().checked_add(other.abs()).and_then(|val| { @@ -120,7 +120,7 @@ impl SignedUint { } /// Subtracts two [`SignedUint`]'s if the absolute value does - /// not exceed [`MAX_SIGNED_AMOUNT`], else returns `None`. + /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. pub fn checked_sub(&self, other: &Self) -> Option { self.checked_add(&other.neg()) } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 173d7f6b62..bb2965ded6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.68.0" +channel = "1.65.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] targets = ['wasm32-unknown-unknown'] \ No newline at end of file diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index b28019849f..19858e0255 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -92,11 +92,14 @@ where token::is_any_multitoken_balance_key(k), Some(( _, - Address::Internal( - InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint - ) + [ + _, + Address::Internal( + InternalAddress::IbcEscrow + | InternalAddress::IbcBurn + | InternalAddress::IbcMint + ) + ] )) ) }) diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index 930a9412c5..71ef3a52b4 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -2,13 +2,13 @@ use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; +use namada_core::types::storage::Key; use namada_core::types::token; -use namada_core::types::token::Denomination; use crate::ledger::queries::RequestCtx; router! {TOKEN, - ( "denomination" / [addr: Address] ) -> Option = denomination, + ( "denomination" / [addr: Address] / [sub_prefix: opt Key] ) -> Option = denomination, } /// Get the number of decimal places (in base 10) for a @@ -16,10 +16,11 @@ router! {TOKEN, fn denomination( ctx: RequestCtx<'_, D, H>, addr: Address, -) -> storage_api::Result> + sub_prefix: Option, +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - read_denom(ctx.wl_storage, &addr) + read_denom(ctx.wl_storage, &addr, sub_prefix.as_ref()) } diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 44d775843f..78335b7925 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -142,10 +142,10 @@ pub fn transfer_with_keys( ) -> TxResult { let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); let src_bal: Option = match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { Some(Amount::max_signed()) } - Some(Address::Internal(InternalAddress::IbcBurn)) => { + Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { log_string("invalid transfer from the burn address"); unreachable!() } @@ -168,7 +168,7 @@ pub fn transfer_with_keys( src_bal.spend(&amount); let dest_owner = is_any_multitoken_balance_key(dest_key).map(|(_, o)| o); let mut dest_bal: Amount = match dest_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { log_string("invalid transfer to the mint address"); unreachable!() } @@ -183,13 +183,13 @@ pub fn transfer_with_keys( }; dest_bal.receive(&amount); match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { ctx.write_temp(src_key, src_bal)?; } _ => ctx.write(src_key, src_bal)?, } match dest_owner { - Some(Address::Internal(InternalAddress::IbcBurn)) => { + Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { ctx.write_temp(dest_key, dest_bal)?; } _ => ctx.write(dest_key, dest_bal)?, diff --git a/wasm/checksums.json b/wasm/checksums.json index 82e5672487..c3eef316ff 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.4353af6713777b2738d2c8b5050e32687a63423ea4827c3dce31895f04cf17a8.wasm", + "tx_bond.wasm": "tx_bond.6875d76a927658665699a0c9e35a3505e8ccbfbc999cf1ea8d88ca8dd4550267.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.3012d6e8322ed0caf8f818f6f47ac70d40d7bf5366ab49b6594cbb160efd31a6.wasm", - "tx_ibc.wasm": "tx_ibc.91f160e0a518eab3e2b6adb54a19b7d50b30b6936c43de3e3cdd83c2b931cd06.wasm", + "tx_ibc.wasm": "tx_ibc.9f5d6878a77ab942fb6c479e4d777dd30b3b7c6b297fc4190bf7b7fd3317c648.wasm", "tx_init_account.wasm": "tx_init_account.eee57525a9759281a0ed1579ff13440d09ccb6187ff8a7c9cc7d7027a1611f97.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6b7dc8ddffad3ad01d666ef738e3839a54af7c73b23f24a9c1e6d27b68449dec.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0cbd682144335b3a112030a5d7eb9acead5c7547e54d7837299f48f42669f0a9.wasm", "tx_init_validator.wasm": "tx_init_validator.f4c7d983bd6076dfe7ca9d6ad497eb352c047543ee01d577e6f56c3b9309e56e.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.5a86cc0bd44d9755a4ce326ed1e178614311704468380ba7b297ed0f62018778.wasm", - "tx_transfer.wasm": "tx_transfer.06235371b028d3a4fafb3e35fd7b7b111763ed630647a39cd5e0655b91b37995.wasm", - "tx_unbond.wasm": "tx_unbond.f1f32820e024be0053abbaf0f5c30622cb0cc2470360b3e0894285fcf982e413.wasm", + "tx_transfer.wasm": "tx_transfer.a3a02481c513dfa142f4b25ea5177bbde60998e42dfc0f298ef7f38d15438922.wasm", + "tx_unbond.wasm": "tx_unbond.c691e7ccba1add857e8c1adf2b37d288ee09b2ca6403f6b6f14831185a854aa7.wasm", "tx_update_vp.wasm": "tx_update_vp.2e319438adbc5256a29f1e8817c1cba69ffd5a8d3a51887f50383a471231d388.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.4cc5a9dde9dd1fef3f0dbc6cc2defb500c194c3934cd055d6e46f9b82c528368.wasm", - "tx_withdraw.wasm": "tx_withdraw.368f8bc90a2381169008590ba14a6cf6dce7cfd3f86a76e591eb4c5ed804d5a6.wasm", - "vp_implicit.wasm": "vp_implicit.64e6048d45d832360d59ac3437bebf947313d93ea75dd7538cf55f29b3e29cd6.wasm", - "vp_masp.wasm": "vp_masp.31688087bba04ca8b4c57a17ce7532ad2eb58f19221a94d3a7b4748cae79f420.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.21f0f5e0ed2d604fa291512fb6937aacef77c8e6303da306577a07a6db168564.wasm", + "tx_withdraw.wasm": "tx_withdraw.d47ef25ea19d1f696ede4f9be5bfbcad89dda8d5901e3e09686c82b90df779f0.wasm", + "vp_implicit.wasm": "vp_implicit.1d3e362e5e27bfc6fdd19ade86125a70529d245605fd9e843ec9f7efcb12e7b0.wasm", + "vp_masp.wasm": "vp_masp.59bc45054f964c3ed044215928b0760cf529c3669429453da4c0f16b287e4632.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4409133d316355e5701e3e049283e059933e281719f6b6820cc6ed3130be5999.wasm", "vp_token.wasm": "vp_token.2ca9b0e8791315a8d29579011d194270bd007fe49e72874f3bbdcef58622361e.wasm", - "vp_user.wasm": "vp_user.225064a9c6b4f1f06d8843daa5fa8795f010d72320dee076b15a7c7e17b1bb3f.wasm", - "vp_validator.wasm": "vp_validator.0202d4cd90930910ffb0a9f12ada0952b4e095968e05ef9d636916115b4d5648.wasm" + "vp_user.wasm": "vp_user.c002d586a8741bc8ccadfbc0bb9b7e2e05e80ef0accbe9f78c273e1acf1a1462.wasm", + "vp_validator.wasm": "vp_validator.4ec929399961906f2499fb39991ef46dbb4101ed59d346ef9b373bbc609982d6.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/proptest-regressions/vp_testnet_faucet.txt b/wasm/wasm_source/proptest-regressions/vp_testnet_faucet.txt new file mode 100644 index 0000000000..ed127f296a --- /dev/null +++ b/wasm/wasm_source/proptest-regressions/vp_testnet_faucet.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc a2303427ef50a7c459fcdc71279f4ab37a6ad1854c0d999d3bdaf0e4b3039e9f # shrinks to amount = 0 diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 97b50c2511..e43e598d37 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -11,14 +11,18 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::storage::{Key, KeySeg}; use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { /// Public key - written once revealed Pk(&'a Address), - Token(&'a Address), + Token { + token: &'a Address, + sub_prefix: Option, + owner: &'a Address, + }, PoS, GovernanceVote(&'a Address), Unknown, @@ -28,12 +32,22 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + } else if let Some([token, owner]) = + token::is_any_token_balance_key(key) + { + Self::Token { + token, + owner, + sub_prefix: None, + } + } else if let Some((sub, [token, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { + token, + owner, + sub_prefix: Some(sub), + } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -115,7 +129,11 @@ fn validate_tx( } true } - KeyType::Token(owner) => { + KeyType::Token { + token, + owner, + sub_prefix, + } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -126,7 +144,7 @@ fn validate_tx( let valid = change.non_negative() || *valid_sig; let sign = if change.non_negative() { "" } else { "-" }; let denom_amount = token::Amount::from_change(change) - .denominated(owner, &ctx.pre()) + .denominated(token, sub_prefix.as_ref(), &ctx.pre()) .unwrap(); debug_log!( "token key: {}, change: {}{}, valid_sig: {}, valid \ @@ -213,7 +231,7 @@ mod tests { use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; - use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_tx_prelude::{storage_api, StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -343,13 +361,17 @@ mod tests { let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the source before running the transaction to be // able to transfer from it @@ -394,11 +416,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -421,21 +439,9 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -443,7 +449,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); - + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -474,11 +487,7 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); let commission_rate = rust_decimal::Decimal::new(5, 2); let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); @@ -501,21 +510,9 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let bond_amount = token::Amount::from_uint( - 5_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - let unbond_amount = token::Amount::from_uint( - 3_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -523,6 +520,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); @@ -563,11 +568,7 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -575,6 +576,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); let amount = token::DenominatedAmount { amount, @@ -620,11 +629,7 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -632,6 +637,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); @@ -682,11 +695,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from_uint( - 10_098_123, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -694,6 +703,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); let amount = token::DenominatedAmount { amount, diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 61a80d681e..b116be4ad2 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -49,7 +49,8 @@ fn validate_tx( } for key in keys_changed.iter() { - let is_valid = if let Some(owner) = token::is_any_token_balance_key(key) + let is_valid = if let Some([_, owner]) = + token::is_any_token_balance_key(key) { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -353,6 +354,8 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom(&mut tx_env.wl_storage, &token, None, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); tx_env.commit_genesis(); // Construct a PoW solution like a client would diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a44d99ddaa..f7c59f2dc8 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -9,12 +9,16 @@ //! Any other storage key changes are allowed only with a valid signature. use namada_vp_prelude::address::masp; -use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::storage::{Key, KeySeg}; use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token(&'a Address), + Token { + token: &'a Address, + sub_prefix: Option, + owner: &'a Address, + }, PoS, Vp(&'a Address), Masp, @@ -24,12 +28,20 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + if let Some([token, owner]) = token::is_any_token_balance_key(key) { + Self::Token { + token, + owner, + sub_prefix: None, + } + } else if let Some((sub, [token, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { + token, + owner, + sub_prefix: Some(sub), + } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -90,7 +102,11 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token(owner) => { + KeyType::Token { + token, + owner, + sub_prefix, + } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -101,7 +117,7 @@ fn validate_tx( let valid = change.non_negative() || addr == masp() || *valid_sig; let denom_amount = token::Amount::from(change) - .denominated(owner, &ctx.pre()) + .denominated(token, sub_prefix.as_ref(), &ctx.pre()) .unwrap(); debug_log!( "token key: {}, change: {}, valid_sig: {}, valid \ diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 18aa679fb3..9f9a152159 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -12,12 +12,16 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::storage::{Key, KeySeg}; use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token(&'a Address), + Token { + token: &'a Address, + sub_prefix: Option, + owner: &'a Address, + }, PoS, Vp(&'a Address), GovernanceVote(&'a Address), @@ -26,12 +30,20 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + if let Some([token, owner]) = token::is_any_token_balance_key(key) { + Self::Token { + token, + owner, + sub_prefix: None, + } + } else if let Some((sub, [token, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { + token, + owner, + sub_prefix: Some(sub), + } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -90,7 +102,11 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token(owner) => { + KeyType::Token { + token, + owner, + sub_prefix, + } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -100,7 +116,7 @@ fn validate_tx( // debit has to signed, credit doesn't let valid = change.non_negative() || *valid_sig; let amount = token::Amount::from(change) - .denominated(owner, &ctx.pre()) + .denominated(token, sub_prefix.as_ref(), &ctx.pre()) .unwrap(); debug_log!( "token key: {}, change: {}, valid_sig: {}, valid \ From 5d10c6c6d67429bc54879d92f5cba1856f952a7d Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Apr 2023 15:13:03 +0200 Subject: [PATCH 20/69] [fix]: Fixed wasm tests --- core/src/types/uint.rs | 22 ++++++++++++++- wasm/wasm_source/src/vp_user.rs | 40 +++++++++++++++++++++++++++ wasm/wasm_source/src/vp_validator.rs | 41 ++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index fb6e72376c..e91c497fba 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -165,11 +165,16 @@ impl PartialOrd for SignedUint { match (self.non_negative(), other.non_negative()) { (true, false) => Some(Ordering::Greater), (false, true) => Some(Ordering::Less), - _ => { + (true, true) => { let this = self.abs(); let that = other.abs(); this.0.partial_cmp(&that.0) } + (false, false) => { + let this = self.abs(); + let that = other.abs(); + that.0.partial_cmp(&this.0) + } } } } @@ -359,4 +364,19 @@ mod test_uint { assert_eq!(neg_eight - two, -ten); assert!((two - two).is_zero()); } + + /// Test that ordering is correctly implemented + #[test] + fn test_ord() { + let this = Amount::from_uint(1, 0).unwrap().change(); + let that = Amount::native_whole(1000).change(); + assert!(this <= that); + assert!(-this <= that); + assert!(-this >= -that); + assert!(this >= -that); + assert!(that >= this); + assert!(that >= -this); + assert!(-that <= -this); + assert!(-that <= this); + } } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index f7c59f2dc8..d76792521f 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -262,6 +262,14 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); let amount = token::DenominatedAmount { amount, @@ -308,6 +316,14 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -364,6 +380,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); @@ -439,6 +463,14 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -503,6 +535,14 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 9f9a152159..271b87e8a1 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -270,6 +270,15 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -323,6 +332,14 @@ mod tests { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), }; + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -371,6 +388,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); let amount = token::DenominatedAmount { @@ -449,6 +474,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -519,6 +552,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); From a8d9b72536e2b372492c2085b21b3acf4426335c Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Apr 2023 12:00:11 +0200 Subject: [PATCH 21/69] [feat]: Added multitoken support to MASP --- apps/src/lib/cli.rs | 9 + apps/src/lib/client/rpc.rs | 253 ++++++++++++-------- apps/src/lib/client/tx.rs | 60 +++-- core/src/ledger/storage/masp_conversions.rs | 57 +++-- core/src/types/address.rs | 18 +- shared/src/ledger/queries/shell.rs | 6 +- tests/src/e2e/ledger_tests.rs | 47 ++-- wasm/checksums.json | 36 +-- wasm/wasm_source/src/vp_masp.rs | 5 +- 9 files changed, 305 insertions(+), 186 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08816f6d5a..6cfb99269d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2778,6 +2778,8 @@ pub mod args { pub owner: Option, /// Address of a token pub token: Option, + /// sub-prefix if querying a multi-token + pub sub_prefix: Option, } impl Args for QueryTransfers { @@ -2785,10 +2787,12 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, + sub_prefix, } } @@ -2800,6 +2804,11 @@ pub mod args { .arg(TOKEN_OPT.def().about( "The token address that queried transfers must involve.", )) + .arg( + SUB_PREFIX.def().about( + "The token's sub prefix whose balance to query.", + ), + ) } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7c2212fd86..1fd77f109c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -257,7 +257,11 @@ pub async fn query_tx_deltas( denom.denominate(&transfer.amount); if denominated != 0 { let tfer_delta = Amount::from_nonnegative( - (transfer.token.clone(), denom), + ( + transfer.token.clone(), + transfer.sub_prefix.clone(), + denom, + ), denominated, ) .expect("invalid value for amount"); @@ -291,6 +295,7 @@ pub async fn query_tx_deltas( /// Query the specified accepted transfers from the ledger pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { let query_token = args.token.as_ref().map(|x| ctx.get(x)); + let sub_prefix = args.sub_prefix.map(|s| Key::parse(s).unwrap()); let query_owner = args.owner.as_ref().map(|x| ctx.get_cached(x)); // Obtain the effects of all shielded and transparent transactions let transfers = query_tx_deltas( @@ -340,19 +345,21 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token - relevant &= match &query_token { - Some(token) => { - tfer_delta - .values() - .zip(MaspDenom::iter()) - .any(|(x, denom)| x[&(token.clone(), denom)] != 0) - || shielded_accounts - .values() - .zip(MaspDenom::iter()) - .any(|(x, denom)| x[&(token.clone(), denom)] != 0) - } - None => true, - }; + relevant &= + match &query_token { + Some(token) => { + tfer_delta.values().zip(MaspDenom::iter()).any( + |(x, denom)| { + x[&(token.clone(), sub_prefix.clone(), denom)] != 0 + }, + ) || shielded_accounts.values().zip(MaspDenom::iter()).any( + |(x, denom)| { + x[&(token.clone(), sub_prefix.clone(), denom)] != 0 + }, + ) + } + None => true, + }; // Filter out those entries that do not satisfy user query if !relevant { continue; @@ -362,7 +369,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for (account, amt) in tfer_delta { if account != masp() { print!(" {}:", account); - for ((addr, denom), val) in amt.components() { + for ((addr, sub_prefix, denom), val) in amt.components() { let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", @@ -370,14 +377,12 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { Ordering::Equal => "", }; print!( - " {}{} {}", + " {}{} {}{}", sign, format_denominated_amount( &client, addr, - // TODO: apparently MASP doesn't support - // multi-tokens? - &None, + sub_prefix, token::Amount::from_masp_denominated( val.unsigned_abs(), *denom @@ -385,6 +390,10 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { ) .await, token_alias, + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), ); } println!(); @@ -395,7 +404,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for (account, amt) in shielded_accounts { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); - for ((addr, denom), val) in amt.components() { + for ((addr, sub_prefix, denom), val) in amt.components() { let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", @@ -403,19 +412,23 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { Ordering::Equal => "", }; print!( - " {}{} {}", + " {}{} {}{}", sign, format_denominated_amount( &client, addr, - &None, + sub_prefix, token::Amount::from_masp_denominated( val.unsigned_abs(), *denom ) ) .await, - token_alias + token_alias, + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() ); } println!(); @@ -670,22 +683,27 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .await } // Now print out the received quantities according to CLI arguments - match (balance, args.token.as_ref()) { - (Err(PinnedBalanceError::InvalidViewingKey), _) => println!( + match (balance, args.token.as_ref(), args.sub_prefix.as_ref()) { + (Err(PinnedBalanceError::InvalidViewingKey), _, _) => println!( "Supplied viewing key cannot decode transactions to given \ payment address." ), - (Err(PinnedBalanceError::NoTransactionPinned), _) => { + (Err(PinnedBalanceError::NoTransactionPinned), _, _) => { println!("Payment address {} has not yet been consumed.", owner) } - (Ok((balance, epoch)), Some(token)) => { + (Ok((balance, epoch)), Some(token), sub_prefix) => { let token = ctx.get(token); let token_alias = lookup_alias(ctx, &token); let mut total_balance = token::Amount::default(); for denom in MaspDenom::iter() { // Extract and print only the specified token from the total - let (_asset_type, value) = - value_by_address(&balance, token.clone(), denom, epoch); + let (_asset_type, value) = value_by_address( + &balance, + token.clone(), + sub_prefix, + denom, + epoch, + ); total_balance += token::Amount::from_masp_denominated( value as u64, denom, @@ -694,33 +712,43 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { if total_balance.is_zero() { println!( "Payment address {} was consumed during epoch {}. \ - Received no shielded {}", - owner, epoch, token_alias + Received no shielded {}{}", + owner, + epoch, + token_alias, + sub_prefix + .map(|k| format!("/{}", k)) + .unwrap_or_default() ); } else { let formatted = format_denominated_amount( &client, &token, - // TODO: Is this correct? - &None, + &sub_prefix.map(|k| Key::parse(k).unwrap()), total_balance, ) .await; println!( "Payment address {} was consumed during epoch {}. \ - Received {} {}", - owner, epoch, formatted, token_alias + Received {} {}{}", + owner, + epoch, + formatted, + token_alias, + sub_prefix + .map(|k| format!("/{}", k)) + .unwrap_or_default() ); } } - (Ok((balance, epoch)), None) => { + (Ok((balance, epoch)), None, _) => { let mut found_any = false; // Print balances by human-readable token names let balance = ctx .shielded .decode_amount(client.clone(), balance, epoch) .await; - for ((addr, denom), value) in balance.components() { + for ((addr, sub_prefix, denom), value) in balance.components() { let asset_value = token::Amount::from_masp_denominated( *value as u64, *denom, @@ -736,17 +764,20 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { let formatted = format_denominated_amount( &client, addr, - // TODO: Is this correct? - &None, + sub_prefix, asset_value, ) .await; println!( - " {}: {}", + " {}{}: {}", tokens .get(addr) .cloned() .unwrap_or_else(|| addr.clone()), + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), formatted, ); } @@ -966,17 +997,12 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { pub fn value_by_address( amt: &masp_primitives::transaction::components::Amount, token: Address, + sub_prefix: Option<&String>, denom: MaspDenom, epoch: Epoch, ) -> (AssetType, i64) { // Compute the unique asset identifier from the token address - let asset_type = AssetType::new( - (token, denom, epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + let asset_type = make_asset_type(epoch, &token, &sub_prefix, denom); (asset_type, amt[&asset_type]) } @@ -1039,13 +1065,8 @@ pub async fn query_shielded_balance( .expect("context should contain viewing key") }; // Compute the unique asset identifier from the token address - let asset_type = AssetType::new( - (token.clone(), denom, epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + let asset_type = + make_asset_type(epoch, &token, &args.sub_prefix, denom); total_balance += token::Amount::from_masp_denominated( balance[&asset_type] as u64, denom, @@ -1055,18 +1076,25 @@ pub async fn query_shielded_balance( let token_alias = lookup_alias(ctx, &token); if total_balance.is_zero() { println!( - "No shielded {} balance found for given key", - token_alias + "No shielded {}{} balance found for given key", + token_alias, + args.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), ); } else { println!( - "{}: {}", + "{}{}: {}", token_alias, + args.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), format_denominated_amount( &client, &token, - // TODO: Is this correct? - &None, + &args.sub_prefix.map(|k| Key::parse(k).unwrap()), total_balance ) .await @@ -1112,16 +1140,20 @@ pub async fn query_shielded_balance( .decode_asset_type(client.clone(), asset_type) .await; match decoded { - Some((addr, denom, asset_epoch)) + Some((addr, sub_prefix, denom, asset_epoch)) if asset_epoch == epoch => { // Only assets with the current timestamp count println!( - "Shielded Token {}:", + "Shielded Token {}{}:", tokens .get(&addr) .cloned() - .unwrap_or_else(|| addr.clone()) + .unwrap_or_else(|| addr.clone()), + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), ); read_tokens.insert(addr.clone()); let mut found_any = false; @@ -1131,9 +1163,10 @@ pub async fn query_shielded_balance( denom, ); let formatted = format_denominated_amount( - &client, &addr, - // TODO: Is this correct? - &None, value, + &client, + &addr, + &sub_prefix, + value, ) .await; println!(" {}, owned by {}", formatted, fvk); @@ -1169,17 +1202,19 @@ pub async fn query_shielded_balance( let token = ctx.get(&token); let mut found_any = false; let token_alias = lookup_alias(ctx, &token); - println!("Shielded Token {}:", token_alias); + println!( + "Shielded Token {}{}:", + token_alias, + args.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() + ); for fvk in viewing_keys { let mut balance = token::Amount::default(); for denom in MaspDenom::iter() { - let asset_type = AssetType::new( - (token.clone(), denom, epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + let asset_type = + make_asset_type(epoch, &token, &args.sub_prefix, denom); // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let denom_balance = if no_conversions { @@ -1205,8 +1240,10 @@ pub async fn query_shielded_balance( } } let formatted = format_denominated_amount( - &client, &token, // TODO: Is this correct? - &None, balance, + &client, + &token, + &args.sub_prefix.as_ref().map(|k| Key::parse(k).unwrap()), + balance, ) .await; println!(" {}, owned by {}", formatted, fvk); @@ -1263,26 +1300,27 @@ pub async fn print_decoded_balance( decoded_balance: MaspDenominatedAmount, ) { let mut balances = HashMap::new(); - for ((addr, denom), value) in decoded_balance.components() { + for ((addr, sub_prefix, denom), value) in decoded_balance.components() { let asset_value = token::Amount::from_masp_denominated(*value as u64, *denom); balances - .entry(addr) + .entry((addr, sub_prefix)) .and_modify(|val| *val += asset_value) .or_insert(asset_value); } if balances.is_empty() { println!("No shielded balance found for given key"); } else { - for (addr, amount) in balances { + for ((addr, sub_prefix), amount) in balances { println!( - "{} : {}", + "{}{} : {}", lookup_alias(ctx, addr), - format_denominated_amount( - client, addr, // TODO: Is this correct? - &None, amount, - ) - .await, + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), + format_denominated_amount(client, addr, sub_prefix, amount,) + .await, ); } } @@ -1291,31 +1329,34 @@ pub async fn print_decoded_balance( pub async fn print_decoded_balance_with_epoch( ctx: &mut Context, client: &HttpClient, - decoded_balance: Amount<(Address, MaspDenom, Epoch)>, + decoded_balance: Amount<(Address, Option, MaspDenom, Epoch)>, ) { let tokens = ctx.tokens(); let mut balances = HashMap::new(); - for ((addr, denom, epoch), value) in decoded_balance.components() { + for ((addr, sub_prefix, denom, epoch), value) in + decoded_balance.components() + { let asset_value = token::Amount::from_masp_denominated(*value as u64, *denom); balances - .entry((addr, epoch)) + .entry((addr, sub_prefix, epoch)) .and_modify(|val| *val += asset_value) .or_insert(asset_value); } if balances.is_empty() { println!("No shielded balance found for given key"); } else { - for ((addr, epoch), amount) in balances { + for ((addr, sub_prefix, epoch), amount) in balances { println!( - "{} | {} : {}", + "{}{} | {} : {}", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), epoch, - format_denominated_amount( - client, addr, // TODO: Is this correct? - &None, amount, - ) - .await, + format_denominated_amount(client, addr, sub_prefix, amount,) + .await, ); } } @@ -2082,7 +2123,7 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found let mut conversions_found = false; - for ((addr, _), epoch, conv, _) in conv_state.assets.values() { + for ((addr, sub, _), epoch, conv, _) in conv_state.assets.values() { let amt: masp_primitives::transaction::components::Amount = conv.clone().into(); // If the user has specified any targets, then meet them @@ -2096,8 +2137,9 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { conversions_found = true; // Print the asset to which the conversion applies print!( - "{}[{}]: ", + "{}{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch, ); // Now print out the components of the allowed conversion @@ -2105,13 +2147,14 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { for (asset_type, val) in amt.components() { // Look up the address and epoch of asset to facilitate pretty // printing - let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; + let ((addr, sub, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion print!( - "{}{} {}[{}]", + "{}{} {}{}[{}]", prefix, val, tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch ); // Future iterations need to be prefixed with + @@ -2131,6 +2174,7 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, + Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -2845,13 +2889,22 @@ pub(super) async fn format_denominated_amount( } /// Make asset type corresponding to given address and epoch -pub fn make_asset_type( +pub fn make_asset_type( epoch: Epoch, token: &Address, + sub_prefix: &Option, denom: MaspDenom, ) -> AssetType { // Typestamp the chosen token with the current epoch - let token_bytes = (token, denom, epoch.0) + let token_bytes = ( + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + epoch.0, + ) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 3470e3b5c7..2ce1771afd 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -512,10 +512,11 @@ pub type Conversions = HashMap, i64)>; /// Represents an amount that is -pub type MaspDenominatedAmount = Amount<(Address, MaspDenom)>; +pub type MaspDenominatedAmount = Amount<(Address, Option, MaspDenom)>; /// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = HashMap>; +pub type TransferDelta = + HashMap, MaspDenom)>>; /// Represents the changes that were made to a list of shielded accounts pub type TransactionDelta = HashMap; @@ -551,7 +552,7 @@ pub struct ShieldedContext { /// The set of note positions that have been spent spents: HashSet, /// Maps asset types to their decodings - asset_types: HashMap, + asset_types: HashMap, MaspDenom, Epoch)>, /// Maps note positions to their corresponding viewing keys vk_map: HashMap, } @@ -881,7 +882,7 @@ impl ShieldedContext { let mut transfer_delta = TransferDelta::new(); for denom in MaspDenom::iter() { let transparent_delta = Amount::from_nonnegative( - (tx.token.clone(), denom), + (tx.token.clone(), tx.sub_prefix.clone(), denom), denom.denominate(&tx.amount.amount), ) .expect("invalid value for amount"); @@ -943,22 +944,23 @@ impl ShieldedContext { &mut self, client: HttpClient, asset_type: AssetType, - ) -> Option<(Address, MaspDenom, Epoch)> { + ) -> Option<(Address, Option, MaspDenom, Epoch)> { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); } // Query for the ID of the last accepted transaction - let (addr, denom, ep, _conv, _path): ( + let (addr, sub_prefix, denom, ep, _conv, _path): ( Address, + Option, MaspDenom, _, Amount, MerklePath, ) = query_conversion(client, asset_type).await?; self.asset_types - .insert(asset_type, (addr.clone(), denom, ep)); - Some((addr, denom, ep)) + .insert(asset_type, (addr.clone(), sub_prefix.clone(), denom, ep)); + Some((addr, sub_prefix, denom, ep)) } /// Query the ledger for the conversion that is allowed for the given asset @@ -973,9 +975,16 @@ impl ShieldedContext { Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), Entry::Vacant(conv_entry) => { // Query for the ID of the last accepted transaction - let (addr, denom, ep, conv, path): (Address, _, _, _, _) = - query_conversion(client, asset_type).await?; - self.asset_types.insert(asset_type, (addr, denom, ep)); + let (addr, sub_prefix, denom, ep, conv, path): ( + Address, + _, + _, + _, + _, + _, + ) = query_conversion(client, asset_type).await?; + self.asset_types + .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv == Amount::zero() { None @@ -1071,8 +1080,8 @@ impl ShieldedContext { let target_asset_type = self .decode_asset_type(client.clone(), asset_type) .await - .map(|(addr, denom, _epoch)| { - make_asset_type(target_epoch, &addr, denom) + .map(|(addr, sub, denom, _epoch)| { + make_asset_type(target_epoch, &addr, &sub, denom) }) .unwrap_or(asset_type); let at_target_asset_type = asset_type == target_asset_type; @@ -1317,8 +1326,8 @@ impl ShieldedContext { self.decode_asset_type(client.clone(), *asset_type).await; // Only assets with the target timestamp count match decoded { - Some((addr, denom, epoch)) if epoch == target_epoch => { - res += &Amount::from_pair((addr, denom), *val).unwrap() + Some((addr, sub, denom, epoch)) if epoch == target_epoch => { + res += &Amount::from_pair((addr, sub, denom), *val).unwrap() } _ => {} } @@ -1332,15 +1341,16 @@ impl ShieldedContext { &mut self, client: HttpClient, amt: Amount, - ) -> Amount<(Address, MaspDenom, Epoch)> { + ) -> Amount<(Address, Option, MaspDenom, Epoch)> { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client.clone(), *asset_type).await; // Only assets with the target timestamp count - if let Some((addr, denom, epoch)) = decoded { - res += &Amount::from_pair((addr, denom, epoch), *val).unwrap() + if let Some((addr, sub, denom, epoch)) = decoded { + res += + &Amount::from_pair((addr, sub, denom, epoch), *val).unwrap() } } res @@ -1351,10 +1361,11 @@ impl ShieldedContext { fn convert_amount( epoch: Epoch, token: &Address, + sub_prefix: &Option<&String>, val: u64, denom: MaspDenom, ) -> (AssetType, Amount) { - let asset_type = make_asset_type(epoch, token, denom); + let asset_type = make_asset_type(epoch, token, sub_prefix, denom); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, val) .expect("invalid value for amount"); @@ -1424,6 +1435,7 @@ async fn gen_shielded_transfer( let (_, fee) = convert_amount( epoch, &ctx.get(&args.tx.fee_token), + &args.sub_prefix.as_ref(), MaspDenom::Zero.denominate(&fee.amount), MaspDenom::Zero, ); @@ -1446,8 +1458,13 @@ async fn gen_shielded_transfer( continue; } // Convert transaction amount into MASP types - let (asset_type, amount) = - convert_amount(epoch, &ctx.get(&args.token), denom_amount, denom); + let (asset_type, amount) = convert_amount( + epoch, + &ctx.get(&args.token), + &args.sub_prefix.as_ref(), + denom_amount, + denom, + ); // If there are shielded inputs if let Some(sk) = spending_key { @@ -1546,7 +1563,6 @@ async fn gen_shielded_transfer( } } // Build and return the constructed transaction - // Build and return the constructed transaction builder .build(consensus_branch_id, &prover) .map(|(a, b)| Some((a, b, epoch))) diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 903a4088ac..73f11e0520 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -9,7 +9,7 @@ use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling::Node; use crate::types::address::Address; -use crate::types::storage::Epoch; +use crate::types::storage::{Epoch, Key}; use crate::types::token::MaspDenom; /// A representation of the conversion state @@ -20,9 +20,15 @@ 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, MaspDenom), Epoch, AllowedConversion, usize), + ( + (Address, Option, MaspDenom), + Epoch, + AllowedConversion, + usize, + ), >, } @@ -59,20 +65,29 @@ where // with the zeroth epoch to minimize the number of convert notes clients // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. - let reward_asset_bytes = (address::nam(), MaspDenom::Zero, 0u64) - .try_to_vec() - .expect("unable to serialize address and epoch"); + let reward_asset_bytes = + (address::nam(), None::, MaspDenom::Zero, 0u64) + .try_to_vec() + .expect("unable to serialize address and epoch"); let reward_asset = AssetType::new(reward_asset_bytes.as_ref()) .expect("unable to derive asset identifier"); // Conversions from the previous to current asset for each address let mut current_convs = - BTreeMap::<(Address, MaspDenom), AllowedConversion>::new(); + BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for ((addr, denom), reward) in &masp_rewards { + for ((addr, sub_prefix, denom), reward) in &masp_rewards { // Dispense a transparent reward in parallel to the shielded rewards - let addr_bal: token::Amount = wl_storage - .read(&token::balance_key(addr, &masp_addr))? - .unwrap_or_default(); + let addr_bal: token::Amount = match sub_prefix { + None => wl_storage + .read(&token::balance_key(addr, &masp_addr))? + .unwrap_or_default(), + Some(sub) => wl_storage + .read(&token::multitoken_balance_key( + &token::multitoken_balance_prefix(addr, sub), + &masp_addr, + ))? + .unwrap_or_default(), + }; // The reward for each reward.1 units of the current asset is // reward.0 units of the reward token // Since floor(a) + floor(b) <= floor(a+b), there will always be @@ -83,16 +98,18 @@ where // cancelled out/replaced with the new asset let old_asset = encode_asset_type( addr.clone(), + sub_prefix, *denom, wl_storage.storage.last_epoch, ); let new_asset = encode_asset_type( addr.clone(), + sub_prefix, *denom, wl_storage.storage.block.epoch, ); current_convs.insert( - (addr.clone(), *denom), + (addr.clone(), sub_prefix.clone(), *denom), (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + MaspAmount::from_pair(new_asset, reward.1).unwrap() + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) @@ -102,7 +119,7 @@ where wl_storage.storage.conversion_state.assets.insert( old_asset, ( - (addr.clone(), *denom), + (addr.clone(), sub_prefix.clone(), *denom), wl_storage.storage.last_epoch, MaspAmount::zero().into(), 0, @@ -173,18 +190,19 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for (addr, denom) in masp_rewards.keys() { + for (addr, sub_prefix, denom) in masp_rewards.keys() { // Add the decoding entry for the new asset type. An uncommited // node position is used since this is not a conversion. let new_asset = encode_asset_type( addr.clone(), + sub_prefix, *denom, wl_storage.storage.block.epoch, ); wl_storage.storage.conversion_state.assets.insert( new_asset, ( - (addr.clone(), *denom), + (addr.clone(), sub_prefix.clone(), *denom), wl_storage.storage.block.epoch, MaspAmount::zero().into(), wl_storage.storage.conversion_state.tree.size(), @@ -211,10 +229,19 @@ where /// Construct MASP asset type with given epoch for given token pub fn encode_asset_type( addr: Address, + sub_prefix: &Option, denom: MaspDenom, epoch: Epoch, ) -> AssetType { - let new_asset_bytes = (addr, denom, epoch.0) + let new_asset_bytes = ( + addr, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + epoch.0, + ) .try_to_vec() .expect("unable to serialize address and epoch"); AssetType::new(new_asset_bytes.as_ref()) diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 84fe2aac18..eea8692ac3 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -14,6 +14,7 @@ use thiserror::Error; use crate::types::key; use crate::types::key::PublicKeyHash; +use crate::types::storage::Key; use crate::types::token::{Denomination, MaspDenom}; /// The length of an established [`Address`] encoded with Borsh. @@ -596,15 +597,16 @@ pub fn tokens() -> HashMap { /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap<(Address, MaspDenom), (u64, u64)> { +pub fn masp_rewards() -> HashMap<(Address, Option, MaspDenom), (u64, u64)> +{ vec![ - ((nam(), MaspDenom::Zero), (0, 100)), - ((btc(), MaspDenom::Zero), (1, 100)), - ((eth(), MaspDenom::Zero), (2, 100)), - ((dot(), MaspDenom::Zero), (3, 100)), - ((schnitzel(), MaspDenom::Zero), (4, 100)), - ((apfel(), MaspDenom::Zero), (5, 100)), - ((kartoffel(), MaspDenom::Zero), (6, 100)), + ((nam(), None, MaspDenom::Zero), (0, 100)), + ((btc(), None, MaspDenom::Zero), (1, 100)), + ((eth(), None, MaspDenom::Zero), (2, 100)), + ((dot(), None, MaspDenom::Zero), (3, 100)), + ((schnitzel(), None, MaspDenom::Zero), (4, 100)), + ((apfel(), None, MaspDenom::Zero), (5, 100)), + ((kartoffel(), None, MaspDenom::Zero), (6, 100)), ] .into_iter() .collect() diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 1b28741e97..bfb98e9be7 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -4,7 +4,7 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockHeight, BlockResults}; +use namada_core::types::storage::{BlockHeight, BlockResults, Key}; use namada_core::types::token::MaspDenom; use crate::ledger::events::log::dumb_queries; @@ -21,6 +21,7 @@ use crate::types::transaction::TxResult; type Conversion = ( Address, + Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -142,7 +143,7 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some(((addr, denom), epoch, conv, pos)) = ctx + if let Some(((addr, sub_prefix, denom), epoch, conv, pos)) = ctx .wl_storage .storage .conversion_state @@ -151,6 +152,7 @@ where { Ok(( addr.clone(), + sub_prefix.clone(), *denom, *epoch, Into::::into( diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 336b968c18..8ec44c5e9f 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1153,7 +1153,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep1.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep1.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1175,7 +1176,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep1.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep1.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1218,7 +1220,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep2.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep2.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1240,7 +1243,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep2.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep2.0 - ep0.0)) .to_string_native(), ))?; client.assert_success(); @@ -1344,7 +1348,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep4.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 + * (ep4.0 - ep3.0)) .to_string_native(), ))?; client.assert_success(); @@ -1367,9 +1372,9 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 * (ep4.0 - ep3.0))) .to_string_native() ))?; @@ -1439,7 +1444,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 + * (ep.0 - ep3.0)) .to_string_native() ))?; client.assert_success(); @@ -1463,9 +1469,9 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 * (ep.0 - ep3.0))) .to_string_native() ))?; @@ -1533,7 +1539,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep6.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1556,9 +1563,9 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 * (ep5.0 - ep3.0))) .to_string_native() ))?; @@ -1584,7 +1591,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 + * (ep6.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1606,7 +1614,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 * (ep5.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 + * (ep5.0 - ep3.0)) .to_string_native() ))?; client.assert_success(); @@ -1629,9 +1638,9 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 * (ep5.0 - ep3.0))) .to_string_native() ))?; @@ -1654,7 +1663,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt30 * masp_rewards[&(eth(), MaspDenom::Zero)]).0 + &((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 * (ep5.0 - ep3.0)) .to_string_native(), "--signer", @@ -1683,7 +1692,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&(btc(), MaspDenom::Zero)]).0 + &((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 * (ep6.0 - ep0.0)) .to_string_native(), "--signer", diff --git a/wasm/checksums.json b/wasm/checksums.json index 6fc3ef593a..470a405e0a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.b9bc8b99b99b8cec29ad84e2b8013a295e2853c92faa853a62fb97726d909cef.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.8cae1cd2f5853e47110dac6881bdf96f763b70b265bbe6718470c05268926991.wasm", - "tx_ibc.wasm": "tx_ibc.e900dd9f63efed368f276b37ae3a23d39d2d2e414f27e1661beb694d3962384b.wasm", - "tx_init_account.wasm": "tx_init_account.13c6195f839adb0fc90b34f0a7e3427252bc85cb0822547fa9ef9eef820b1003.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0731458ba3c1deff1455e01d20d5668356120192daae971da5cfe65a0eb8426e.wasm", - "tx_init_validator.wasm": "tx_init_validator.f361859a37f50affde8c513bdbe7fc45ff1585428569513a07f9ad873b39aba4.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.4043bcfcf456b927fd31f43a3804963462c1ca6e33d1e81a9bac8d80b0dd0e06.wasm", - "tx_transfer.wasm": "tx_transfer.2f349031d24d9221d411e50f3ee083586cc1b528e3d828d971b7ca3e85eff5b2.wasm", - "tx_unbond.wasm": "tx_unbond.f8f6070d9df88837745a4ee97a11388ade03b961c095dfb49966fa08d6769123.wasm", - "tx_update_vp.wasm": "tx_update_vp.633b6611bde975ca59ce2e770d4e94ce00c21f7dac1eb7e0e152741657cf1e60.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c999d4fd180741a2fc8d6d204d9c3bd9bbebcba816175381378d054d52d08ee2.wasm", - "tx_withdraw.wasm": "tx_withdraw.8489877bb8593646bd5192ea5287a2e4eabe99e21655d704e361cba7ca9b0d53.wasm", - "vp_implicit.wasm": "vp_implicit.30795d13881147198344387748a5b26eb947dc7b94b81f69beef4b9ac0cddc55.wasm", - "vp_masp.wasm": "vp_masp.0f7fa1acb7927e9dc4e7b36272b9b7cb74f883d6b9404d47330cac9c3d3320a8.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c3caefef87bdfd3d6adb453ace9a95b6b50be4ba5d0e1b6c70b836cfe626a677.wasm", - "vp_token.wasm": "vp_token.4c8ff7e2a4fd19d0aea5e40acceaf9ff05c134abe27626a6a796491ba55ab763.wasm", - "vp_user.wasm": "vp_user.2774adf1449643a7eba6b67f691e242c9e593b4efd5db53fac13fed05b68705f.wasm", - "vp_validator.wasm": "vp_validator.610c4d76872e9e2f5d6fa1cae61fd685e0cd4e0f54653d1c49dc36d004c99090.wasm" + "tx_bond.wasm": "tx_bond.204dd016d48999ce2bd65a2cc8ba7ba6eec6a2e9878b544e4d508ce8a6c4619e.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.820da4e9d5cd00200e1a88808c5d0cce79bbf9c6a42c4bcf69cc0094d44053eb.wasm", + "tx_ibc.wasm": "tx_ibc.9d95be7b97770cee6665bfb9a53cfb10a4bb162be3bd813fb0720c5b70bf75d7.wasm", + "tx_init_account.wasm": "tx_init_account.92adf7dd170cde441f34001738e4854f6febeba416c73489ae31a75a09be9603.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e76dcfdea228ac37745de999feefa56ae881b6be101565b86d832d27d1598323.wasm", + "tx_init_validator.wasm": "tx_init_validator.087a519a8e9e0a88e7ff71556b9cab8296ba879e0da3dfe7dfb66ccf407db92e.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fcd7aca657cd5e6aac3af9752c24d31078a3b8bbccba845e843980fec677dea0.wasm", + "tx_transfer.wasm": "tx_transfer.4ac65052b5a699df823b773dc943dd582701f3e4b1a6b8e52da4e39c9fa31195.wasm", + "tx_unbond.wasm": "tx_unbond.558c7d3e0d46766be957187b9c6d3d44490c29c9a354e7297be831fe48383452.wasm", + "tx_update_vp.wasm": "tx_update_vp.b7791369274dea718756434ba323396a471ca27c94a52d6c182da94953d9b22b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ca9bff7f78c3accf84725cd4bcdaf3872022fd5f6390fc22a5e20d24f53d88b7.wasm", + "tx_withdraw.wasm": "tx_withdraw.650cfc67a10136eb73bc3ceaaad9e2b2b06e2b423989a86604222187be4a5865.wasm", + "vp_implicit.wasm": "vp_implicit.14afbabd825733511d404013f4bc1dd9078ff0b801d3dd1ebd5bf3b4e5b41dfd.wasm", + "vp_masp.wasm": "vp_masp.b9d93452eb037e411a9e103aef6de33a27ddf9578b0125b7044aae00ebcfcd93.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3e2f78bfa703c54e28d41ee12ca7869e60f0fe37b936ccc246a7aef39c95807a.wasm", + "vp_token.wasm": "vp_token.189080cb6137da24bc2ce9173ecac54e46b1dcb341d79aa66c9b6891fed92eb7.wasm", + "vp_user.wasm": "vp_user.909808779f6442ff5d08a4b195bb39b75402c5fe3d710c38ea17469c56cc9824.wasm", + "vp_validator.wasm": "vp_validator.ce440d41274ebc487f88a64843c27bb693093a3c2828a197b7975e82d27b295c.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 07c1bf41d8..5dd02a95b5 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -149,7 +149,7 @@ fn validate_tx( debug_log!( "Transparent output to a transaction to the masp must be \ beteween 1 and 4 but is {}", - shielded_tx.vin.len() + shielded_tx.vout.len() ); return reject(); } @@ -224,12 +224,13 @@ fn validate_tx( // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output + // Satisfies 1. if !shielded_tx.vout.is_empty() { debug_log!( "Transparent output to a transaction from the masp must \ be 0 but is {}", - shielded_tx.vin.len() + shielded_tx.vout.len() ); return reject(); } From 33b2bf0d161c62f28fcca6d6fadda458f6a6d9bc Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Apr 2023 12:26:23 +0200 Subject: [PATCH 22/69] [fix]: Fixed makefile recipes for abcipp --- Makefile | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 7722745f59..b65e8b698d 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ test-e2e: -Z unstable-options --report-time test-unit-abcipp: - $(cargo) test \ + $(cargo) +$(nightly) test \ --manifest-path ./apps/Cargo.toml \ $(jobs) \ --no-default-features \ @@ -140,7 +140,7 @@ test-unit-abcipp: -Z unstable-options \ $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ - $(cargo) test \ + $(cargo) +$(nightly) test \ --manifest-path \ ./proof_of_stake/Cargo.toml \ $(jobs) \ @@ -148,19 +148,11 @@ test-unit-abcipp: -Z unstable-options \ $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ - $(cargo) test \ + $(cargo) +$(nightly) test \ --manifest-path ./shared/Cargo.toml \ $(jobs) \ --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./vm_env/Cargo.toml \ - $(jobs) \ - --no-default-features \ - --features "abcipp" \ + --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" \ -Z unstable-options \ $(TEST_FILTER) -- \ -Z unstable-options --report-time From 9d20a96461ee9be35ce748be54d98110520ed3cd Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Apr 2023 12:11:23 +0200 Subject: [PATCH 23/69] [feat]: Replaced u128 in governance with u256 --- apps/src/lib/client/tx.rs | 47 ++++++-- core/src/ledger/storage/masp_conversions.rs | 102 +++++++++--------- core/src/types/address.rs | 19 ++-- core/src/types/governance.rs | 37 ++++--- core/src/types/uint.rs | 42 ++++++++ .../src/ledger/native_vp/governance/utils.rs | 36 ++++--- tests/src/e2e/ledger_tests.rs | 59 ++++------ 7 files changed, 206 insertions(+), 136 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2ce1771afd..50db5ec0fc 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1435,7 +1435,7 @@ async fn gen_shielded_transfer( let (_, fee) = convert_amount( epoch, &ctx.get(&args.tx.fee_token), - &args.sub_prefix.as_ref(), + &None, MaspDenom::Zero.denominate(&fee.amount), MaspDenom::Zero, ); @@ -1470,7 +1470,9 @@ async fn gen_shielded_transfer( if let Some(sk) = spending_key { // If the gas is coming from the shielded pool, then our shielded // inputs must also cover the gas fee - let required_amt = if shielded_gas { + let required_amt = if shielded_gas && denom == MaspDenom::Zero { + // TODO: This could overflow the amount. Need carry logic. + // TODO: Move this up out of the loop. amount + fee.clone() } else { amount @@ -1649,10 +1651,17 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { ) .await; eprintln!( - "The balance of the source {} of token {} is lower than \ + "The balance of the source {} of token {}{} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, token, validated_amount, balance_amount + source, + token, + validated_amount, + args.sub_prefix + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default(), + balance_amount ); if !args.tx.force { safe_exit(1) @@ -1661,8 +1670,13 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { } None => { eprintln!( - "No balance found for the source {} of token {}", - source, token + "No balance found for the source {} of token {}{}", + source, + token, + args.sub_prefix + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default(), ); if !args.tx.force { safe_exit(1) @@ -1729,11 +1743,15 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { Err(builder::Error::ChangeIsNegative(_)) => { eprintln!( "The balance of the source {} is lower than the amount to \ - be transferred and fees. Amount to transfer is {} {} and \ - fees are {} {}.", + be transferred and fees. Amount to transfer is {} {}{} \ + and fees are {} {}.", source.clone(), validated_amount, token, + args.sub_prefix + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default(), validate_fee, ctx.get(&args.tx.fee_token), ); @@ -1830,7 +1848,7 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { } // Check source balance let (sub_prefix, balance_key) = match args.sub_prefix { - Some(sub_prefix) => { + Some(ref sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( @@ -1860,10 +1878,17 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { ) .await; eprintln!( - "The balance of the source {} of token {} is lower than \ + "The balance of the source {} of token {}{} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, token, formatted_amount, formatted_balance + source, + token, + args.sub_prefix + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default(), + formatted_amount, + formatted_balance ); if !args.tx.force { safe_exit(1) diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 73f11e0520..37ce49892f 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -75,7 +75,7 @@ where let mut current_convs = BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for ((addr, sub_prefix, denom), reward) in &masp_rewards { + for ((addr, sub_prefix), reward) in &masp_rewards { // Dispense a transparent reward in parallel to the shielded rewards let addr_bal: token::Amount = match sub_prefix { None => wl_storage @@ -93,38 +93,40 @@ where // Since floor(a) + floor(b) <= floor(a+b), there will always be // enough rewards to reimburse users total_reward += (addr_bal * *reward).0; - // Provide an allowed conversion from previous timestamp. The - // negative sign allows each instance of the old asset to be - // cancelled out/replaced with the new asset - let old_asset = encode_asset_type( - addr.clone(), - sub_prefix, - *denom, - wl_storage.storage.last_epoch, - ); - let new_asset = encode_asset_type( - addr.clone(), - sub_prefix, - *denom, - wl_storage.storage.block.epoch, - ); - current_convs.insert( - (addr.clone(), sub_prefix.clone(), *denom), - (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() - + MaspAmount::from_pair(new_asset, reward.1).unwrap() - + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) - .into(), - ); - // Add a conversion from the previous asset type - wl_storage.storage.conversion_state.assets.insert( - old_asset, - ( - (addr.clone(), sub_prefix.clone(), *denom), + for denom in token::MaspDenom::iter() { + // Provide an allowed conversion from previous timestamp. The + // negative sign allows each instance of the old asset to be + // cancelled out/replaced with the new asset + let old_asset = encode_asset_type( + addr.clone(), + sub_prefix, + denom, wl_storage.storage.last_epoch, - MaspAmount::zero().into(), - 0, - ), - ); + ); + let new_asset = encode_asset_type( + addr.clone(), + sub_prefix, + denom, + wl_storage.storage.block.epoch, + ); + current_convs.insert( + (addr.clone(), sub_prefix.clone(), denom), + (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + + MaspAmount::from_pair(new_asset, reward.1).unwrap() + + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) + .into(), + ); + // Add a conversion from the previous asset type + wl_storage.storage.conversion_state.assets.insert( + old_asset, + ( + (addr.clone(), sub_prefix.clone(), denom), + wl_storage.storage.last_epoch, + MaspAmount::zero().into(), + 0, + ), + ); + } } // Try to distribute Merkle leaf updating as evenly as possible across @@ -190,24 +192,26 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for (addr, sub_prefix, denom) in masp_rewards.keys() { - // Add the decoding entry for the new asset type. An uncommited - // node position is used since this is not a conversion. - let new_asset = encode_asset_type( - addr.clone(), - sub_prefix, - *denom, - wl_storage.storage.block.epoch, - ); - wl_storage.storage.conversion_state.assets.insert( - new_asset, - ( - (addr.clone(), sub_prefix.clone(), *denom), + for (addr, sub_prefix) in masp_rewards.keys() { + for denom in token::MaspDenom::iter() { + // Add the decoding entry for the new asset type. An uncommited + // node position is used since this is not a conversion. + let new_asset = encode_asset_type( + addr.clone(), + sub_prefix, + denom, wl_storage.storage.block.epoch, - MaspAmount::zero().into(), - wl_storage.storage.conversion_state.tree.size(), - ), - ); + ); + wl_storage.storage.conversion_state.assets.insert( + new_asset, + ( + (addr.clone(), sub_prefix.clone(), denom), + wl_storage.storage.block.epoch, + MaspAmount::zero().into(), + wl_storage.storage.conversion_state.tree.size(), + ), + ); + } } // Save the current conversion state in order to avoid computing diff --git a/core/src/types/address.rs b/core/src/types/address.rs index eea8692ac3..79f65b014c 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -15,7 +15,7 @@ use thiserror::Error; use crate::types::key; use crate::types::key::PublicKeyHash; use crate::types::storage::Key; -use crate::types::token::{Denomination, MaspDenom}; +use crate::types::token::Denomination; /// The length of an established [`Address`] encoded with Borsh. pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 45; @@ -597,16 +597,15 @@ pub fn tokens() -> HashMap { /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap<(Address, Option, MaspDenom), (u64, u64)> -{ +pub fn masp_rewards() -> HashMap<(Address, Option), (u64, u64)> { vec![ - ((nam(), None, MaspDenom::Zero), (0, 100)), - ((btc(), None, MaspDenom::Zero), (1, 100)), - ((eth(), None, MaspDenom::Zero), (2, 100)), - ((dot(), None, MaspDenom::Zero), (3, 100)), - ((schnitzel(), None, MaspDenom::Zero), (4, 100)), - ((apfel(), None, MaspDenom::Zero), (5, 100)), - ((kartoffel(), None, MaspDenom::Zero), (6, 100)), + ((nam(), None), (0, 100)), + ((btc(), None), (1, 100)), + ((eth(), None), (2, 100)), + ((dot(), None), (3, 100)), + ((schnitzel(), None), (4, 100)), + ((apfel(), None), (5, 100)), + ((kartoffel(), None), (6, 100)), ] .into_iter() .collect() diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index e599a2e10f..d70cb58f51 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -4,7 +4,6 @@ use std::collections::{BTreeMap, HashSet}; use std::fmt::{self, Display}; use borsh::{BorshDeserialize, BorshSerialize}; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -13,10 +12,13 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; -use crate::types::token::{Amount, NATIVE_SCALE}; +use crate::types::token::{ + Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, +}; +use crate::types::uint::Uint; /// Type alias for vote power -pub type VotePower = u128; +pub type VotePower = Uint; /// A PGF cocuncil composed of the address and spending cap pub type Council = (Address, Amount); @@ -141,19 +143,30 @@ pub struct ProposalResult { impl Display for ProposalResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let percentage = Decimal::checked_div( - self.total_yay_power.into(), - self.total_voting_power.into(), - ) - .unwrap_or_default(); + let percentage = DenominatedAmount { + amount: Amount::from_uint( + self.total_yay_power + .fixed_precision_div(&self.total_voting_power, 4) + .unwrap_or_default(), + 0, + ) + .unwrap(), + denom: 2.into(), + }; write!( f, - "{} with {} yay votes over {} ({:.2}%)", + "{} with {} yay votes over {} ({}%)", self.result, - self.total_yay_power / NATIVE_SCALE as u128, - self.total_voting_power / NATIVE_SCALE as u128, - percentage.checked_mul(100.into()).unwrap_or_default() + DenominatedAmount { + amount: Amount::from_uint(self.total_yay_power, 0).unwrap(), + denom: NATIVE_MAX_DECIMAL_PLACES.into() + }, + DenominatedAmount { + amount: Amount::from_uint(self.total_voting_power, 0).unwrap(), + denom: NATIVE_MAX_DECIMAL_PLACES.into() + }, + percentage ) } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index e91c497fba..daf6925835 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -32,6 +32,19 @@ impl_uint_num_traits!(Uint, 4); pub const MAX_VALUE: Uint = Uint([u64::MAX; 4]); impl Uint { + /// Divide two [`Uint`]s with scaled to allow the `denom` number + /// of decimal places. + /// + /// This method is checked and will return `None` if + /// * `self` * 10^(`denom`) overflows 256 bits + /// * `other` is zero (`checked_div` will return `None`). + pub fn fixed_precision_div(&self, rhs: &Self, denom: u8) -> Option { + let lhs = Uint::from(10) + .checked_pow(Uint::from(denom)) + .and_then(|res| res.checked_mul(*self))?; + lhs.checked_div(*rhs) + } + /// Compute the two's complement of a number. fn negate(&self) -> Self { Self( @@ -264,6 +277,35 @@ impl TryFrom for i128 { mod test_uint { use super::*; + /// Test that dividing two [`Uint`]s with the specified precision + /// works correctly and performs correct checks. + #[test] + fn test_fixed_precision_div() { + let zero = Uint::zero(); + let two = Uint::from(2); + let three = Uint::from(3); + + assert_eq!( + zero.fixed_precision_div(&two, 10).expect("Test failed"), + zero + ); + assert!(two.fixed_precision_div(&zero, 3).is_none()); + assert_eq!( + three.fixed_precision_div(&two, 1).expect("Test failed"), + Uint::from(15) + ); + assert_eq!( + two.fixed_precision_div(&three, 2).expect("Test failed"), + Uint::from(66) + ); + assert_eq!( + two.fixed_precision_div(&three, 3).expect("Satan lives"), + Uint::from(666) + ); + assert!(two.fixed_precision_div(&three, 77).is_none()); + assert!(Uint::from(20).fixed_precision_div(&three, 76).is_none()); + } + /// Test that adding one to the max signed /// value gives zero. #[test] diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index c97c13f6b0..8397cb51af 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -98,7 +98,7 @@ pub fn compute_tally( for (_, (amount, validator_vote)) in yay_validators.iter() { if let ProposalVote::Yay(vote_type) = validator_vote { if proposal_type == vote_type { - total_yay_staked_tokens += amount; + total_yay_staked_tokens += *amount; } else { // Log the error and continue tracing::error!( @@ -129,7 +129,7 @@ pub fn compute_tally( if !yay_validators.contains_key(validator_address) { // YAY: Add delegator amount whose validator // didn't vote / voted nay - total_yay_staked_tokens += vote_power; + total_yay_staked_tokens += *vote_power; } } ProposalVote::Nay => { @@ -137,7 +137,7 @@ pub fn compute_tally( // validator vote yay if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; + total_yay_staked_tokens -= *vote_power; } } @@ -175,14 +175,14 @@ pub fn compute_tally( result: tally_result, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + total_nay_power: 0.into(), }) } else { Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + total_nay_power: 0.into(), }) } } @@ -193,8 +193,9 @@ pub fn compute_tally( validator_vote { for v in votes { - *total_yay_staked_tokens.entry(v).or_insert(0) += - amount; + *total_yay_staked_tokens + .entry(v) + .or_insert(VotePower::zero()) += *amount; } } else { // Log the error and continue @@ -235,7 +236,7 @@ pub fn compute_tally( total_yay_staked_tokens .get_mut(vote) { - *power -= vote_power; + *power -= *vote_power; } else { return Err(Error::Tally( format!( @@ -251,7 +252,9 @@ pub fn compute_tally( // this, add voting power *total_yay_staked_tokens .entry(vote) - .or_insert(0) += vote_power; + .or_insert( + VotePower::zero(), + ) += *vote_power; } } } else { @@ -271,7 +274,8 @@ pub fn compute_tally( for vote in delegator_votes { *total_yay_staked_tokens .entry(vote) - .or_insert(0) += vote_power; + .or_insert(VotePower::zero()) += + *vote_power; } } } @@ -294,7 +298,7 @@ pub fn compute_tally( total_yay_staked_tokens .get_mut(vote) { - *power -= vote_power; + *power -= *vote_power; } else { return Err(Error::Tally( format!( @@ -333,14 +337,16 @@ pub fn compute_tally( // At least 1/3 of the total voting power must vote Yay let total_yay_voted_power = total_yay_staked_tokens .iter() - .fold(0, |acc, (_, vote_power)| acc + vote_power); + .fold(VotePower::zero(), |acc, (_, vote_power)| { + acc + *vote_power + }); - match total_yay_voted_power.checked_mul(3) { + match total_yay_voted_power.checked_mul(3.into()) { Some(v) if v < total_stake => Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_voted_power, - total_nay_power: 0, + total_nay_power: VotePower::zero(), }), _ => { // Select the winner council based on approval voting @@ -359,7 +365,7 @@ pub fn compute_tally( result: TallyResult::Passed(Tally::PGFCouncil(council)), total_voting_power: total_stake, total_yay_power: total_yay_voted_power, - total_nay_power: 0, + total_nay_power: VotePower::zero(), }) } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8ec44c5e9f..2418deb884 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,7 +27,7 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_core::types::token::{MaspDenom, NATIVE_MAX_DECIMAL_PLACES}; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -1153,8 +1153,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep1.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1176,8 +1175,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep1.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1220,8 +1218,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep2.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1243,8 +1240,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep2.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0)) .to_string_native(), ))?; client.assert_success(); @@ -1348,8 +1344,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep4.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)) .to_string_native(), ))?; client.assert_success(); @@ -1372,10 +1367,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep4.0 - ep3.0))) + (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0))) .to_string_native() ))?; client.assert_success(); @@ -1444,8 +1437,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)) .to_string_native() ))?; client.assert_success(); @@ -1469,10 +1461,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep.0 - ep3.0))) + (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0))) .to_string_native() ))?; client.assert_success(); @@ -1539,8 +1529,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep6.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1563,10 +1552,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep5.0 - ep3.0))) + (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0))) .to_string_native() ))?; client.assert_success(); @@ -1591,8 +1578,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep6.0 - ep0.0)) + ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) .to_string_native() ))?; client.assert_success(); @@ -1614,8 +1600,7 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep5.0 - ep3.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) .to_string_native() ))?; client.assert_success(); @@ -1638,10 +1623,8 @@ fn masp_incentives() -> Result<()> { )?; client.exp_string(&format!( "nam: {}", - (((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep5.0 - ep3.0))) + (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0))) .to_string_native() ))?; client.assert_success(); @@ -1663,8 +1646,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt30 * masp_rewards[&(eth(), None, MaspDenom::Zero)]).0 - * (ep5.0 - ep3.0)) + &((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) .to_string_native(), "--signer", BERTHA, @@ -1692,8 +1674,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&(btc(), None, MaspDenom::Zero)]).0 - * (ep6.0 - ep0.0)) + &((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) .to_string_native(), "--signer", ALBERT, From adaf3a145bf9f6f15f130243f7d32d6cdc3a4e64 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Apr 2023 10:54:23 +0200 Subject: [PATCH 24/69] [fix]: Incorporating in review suggestions --- apps/src/lib/client/tx.rs | 27 +++++------ core/src/types/token.rs | 6 +-- core/src/types/uint.rs | 98 +++++++++++++++++++-------------------- 3 files changed, 63 insertions(+), 68 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 50db5ec0fc..cd457ddede 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1426,26 +1426,30 @@ async fn gen_shielded_transfer( LocalTxProver::with_default_location() .expect("unable to load MASP Parameters") }; - let fee = if spending_key.is_some() { + let fee_amount = if spending_key.is_some() { let InputAmount::Validated(fee) = args.tx.fee_amount else { unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") }; // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used. This amount should be <= `u64::MAX`. - let (_, fee) = convert_amount( + let (_, shielded_fee) = convert_amount( epoch, &ctx.get(&args.tx.fee_token), &None, MaspDenom::Zero.denominate(&fee.amount), MaspDenom::Zero, ); - builder.set_fee(fee.clone())?; - fee + builder.set_fee(shielded_fee)?; + if shielded_gas { + fee.amount + } else { + token::Amount::zero() + } } else { // No transfer fees come from the shielded transaction for non-MASP // sources builder.set_fee(Amount::zero())?; - Amount::zero() + token::Amount::zero() }; // break up a transfer into a number of transfers with suitable // denominations @@ -1453,12 +1457,12 @@ async fn gen_shielded_transfer( unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") }; for denom in MaspDenom::iter() { - let denom_amount = denom.denominate(&amt.amount); + let denom_amount = denom.denominate(&(amt.amount + fee_amount)); if denom_amount == 0 { continue; } // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount( + let (asset_type, required_amt) = convert_amount( epoch, &ctx.get(&args.token), &args.sub_prefix.as_ref(), @@ -1468,15 +1472,6 @@ async fn gen_shielded_transfer( // If there are shielded inputs if let Some(sk) = spending_key { - // If the gas is coming from the shielded pool, then our shielded - // inputs must also cover the gas fee - let required_amt = if shielded_gas && denom == MaspDenom::Zero { - // TODO: This could overflow the amount. Need carry logic. - // TODO: Move this up out of the loop. - amount + fee.clone() - } else { - amount - }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx .shielded diff --git a/core/src/types/token.rs b/core/src/types/token.rs index cdbe8ef32d..7e7ea3b20e 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -16,7 +16,7 @@ use crate::ledger::storage_api::StorageRead; use crate::types::address::{masp, Address, DecodeError as AddressError}; use crate::types::storage; use crate::types::storage::{DbKeySeg, Key, KeySeg}; -use crate::types::uint::{self, SignedUint, Uint}; +use crate::types::uint::{self, I256, Uint}; /// Amount in micro units. For different granularity another representation /// might be more appropriate. @@ -49,7 +49,7 @@ pub const NATIVE_MAX_DECIMAL_PLACES: u8 = 6; pub const NATIVE_SCALE: u64 = 1_000_000; /// A change in tokens amount -pub type Change = SignedUint; +pub type Change = I256; impl Amount { /// Get the amount as a [`Change`] @@ -207,7 +207,7 @@ impl Amount { pub fn to_string_native(&self) -> String { DenominatedAmount { amount: *self, - denom: 6.into(), + denom:NATIVE_MAX_DECIMAL_PLACES.into(), } .to_string_precise() } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index daf6925835..cb88c96844 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -71,7 +71,7 @@ impl Uint { } } -/// The maximum absolute value a [`SignedUint`] may have. +/// The maximum absolute value a [`I256`] may have. /// Note the the last digit is 2^63 - 1. We add this cap so /// we can use two's complement. pub const MAX_SIGNED_VALUE: Uint = @@ -83,9 +83,9 @@ const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); #[derive( Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, )] -pub struct SignedUint(Uint); +pub struct I256(Uint); -impl SignedUint { +impl I256 { /// Check if the amount is not negative (greater /// than or equal to zero) pub fn non_negative(&self) -> bool { @@ -118,7 +118,7 @@ impl SignedUint { sign } - /// Adds two [`SignedUint`]'s if the absolute value does + /// Adds two [`I256`]'s if the absolute value does /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. pub fn checked_add(&self, other: &Self) -> Option { if self.non_negative() == other.non_negative() { @@ -132,7 +132,7 @@ impl SignedUint { } } - /// Subtracts two [`SignedUint`]'s if the absolute value does + /// Subtracts two [`I256`]'s if the absolute value does /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. pub fn checked_sub(&self, other: &Self) -> Option { self.checked_add(&other.neg()) @@ -144,14 +144,14 @@ impl SignedUint { } } -impl From for SignedUint { +impl From for I256 { fn from(val: u64) -> Self { - SignedUint::try_from(Uint::from(val)) + I256::try_from(Uint::from(val)) .expect("A u64 will always fit in this type") } } -impl TryFrom for SignedUint { +impl TryFrom for I256 { type Error = Box; fn try_from(value: Uint) -> Result { @@ -165,7 +165,7 @@ impl TryFrom for SignedUint { } } -impl Neg for SignedUint { +impl Neg for I256 { type Output = Self; fn neg(self) -> Self::Output { @@ -173,7 +173,7 @@ impl Neg for SignedUint { } } -impl PartialOrd for SignedUint { +impl PartialOrd for I256 { fn partial_cmp(&self, other: &Self) -> Option { match (self.non_negative(), other.non_negative()) { (true, false) => Some(Ordering::Greater), @@ -192,16 +192,16 @@ impl PartialOrd for SignedUint { } } -impl Ord for SignedUint { +impl Ord for I256 { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } } -impl Add for SignedUint { +impl Add for I256 { type Output = Self; - fn add(self, rhs: SignedUint) -> Self::Output { + fn add(self, rhs: I256) -> Self::Output { match (self.non_negative(), rhs.non_negative()) { (true, true) => Self(self.0 + rhs.0), (false, false) => -Self(self.abs() + rhs.abs()), @@ -224,13 +224,13 @@ impl Add for SignedUint { } } -impl AddAssign for SignedUint { +impl AddAssign for I256 { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } -impl Sub for SignedUint { +impl Sub for I256 { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { @@ -238,7 +238,7 @@ impl Sub for SignedUint { } } -impl From for SignedUint { +impl From for I256 { fn from(val: i128) -> Self { if val < 0 { let abs = Self((-val).into()); @@ -249,22 +249,22 @@ impl From for SignedUint { } } -impl From for SignedUint { +impl From for I256 { fn from(val: i64) -> Self { Self::from(val as i128) } } -impl From for SignedUint { +impl From for I256 { fn from(val: i32) -> Self { Self::from(val as i128) } } -impl TryFrom for i128 { +impl TryFrom for i128 { type Error = std::io::Error; - fn try_from(value: SignedUint) -> Result { + fn try_from(value: I256) -> Result { if !value.non_negative() { Ok(-(u128::try_from(Amount::from_change(value))? as i128)) } else { @@ -311,12 +311,12 @@ mod test_uint { #[test] fn test_max_signed_value() { let signed = - SignedUint::try_from(MAX_SIGNED_VALUE).expect("Test failed"); - let one = SignedUint::try_from(Uint::from(1u64)).expect("Test failed"); + I256::try_from(MAX_SIGNED_VALUE).expect("Test failed"); + let one = I256::try_from(Uint::from(1u64)).expect("Test failed"); let overflow = signed + one; assert_eq!( overflow, - SignedUint::try_from(Uint::zero()).expect("Test failed") + I256::try_from(Uint::zero()).expect("Test failed") ); assert!(signed.checked_add(&one).is_none()); assert!((-signed).checked_sub(&one).is_none()); @@ -331,7 +331,7 @@ mod test_uint { assert!(larger > smaller); assert_eq!(smaller, MAX_SIGNED_VALUE); assert_eq!(larger, MINUS_ZERO); - assert!(SignedUint::try_from(MINUS_ZERO).is_err()); + assert!(I256::try_from(MINUS_ZERO).is_err()); let zero = Uint::zero(); assert_eq!(zero, zero.negate()); } @@ -340,25 +340,25 @@ mod test_uint { /// sign. #[test] fn test_non_negative() { - let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); + let zero = I256::try_from(Uint::zero()).expect("Test failed"); assert!(zero.non_negative()); assert!((-zero).non_negative()); - let negative = SignedUint(Uint([1u64, 0, 0, 2u64.pow(63)])); + let negative = I256(Uint([1u64, 0, 0, 2u64.pow(63)])); assert!(!negative.non_negative()); assert!((-negative).non_negative()); - let positive = SignedUint(MAX_SIGNED_VALUE); + let positive = I256(MAX_SIGNED_VALUE); assert!(positive.non_negative()); assert!(!(-positive).non_negative()); } - /// Test that the absolute vale is computed correctly + /// Test that the absolute value is computed correctly. #[test] fn test_abs() { - let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); - let neg_one = SignedUint(Uint::max_value()); - let neg_eight = SignedUint(Uint::max_value() - Uint::from(7)); - let two = SignedUint(Uint::from(2)); - let ten = SignedUint(Uint::from(10)); + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = I256(Uint::max_value()); + let neg_eight = I256(Uint::max_value() - Uint::from(7)); + let two = I256(Uint::from(2)); + let ten = I256(Uint::from(10)); assert_eq!(zero.abs(), Uint::zero()); assert_eq!(neg_one.abs(), Uint::from(1)); @@ -367,15 +367,15 @@ mod test_uint { assert_eq!(ten.abs(), Uint::from(10)); } - /// Test that the absolute vale is computed correctly + /// Test that the string representation is created correctly. #[test] fn test_to_string_native() { let native_scaling = Uint::exp10(6); - let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); - let neg_one = -SignedUint(native_scaling); - let neg_eight = -SignedUint(Uint::from(8) * native_scaling); - let two = SignedUint(Uint::from(2) * native_scaling); - let ten = SignedUint(Uint::from(10) * native_scaling); + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = -I256(native_scaling); + let neg_eight = -I256(Uint::from(8) * native_scaling); + let two = I256(Uint::from(2) * native_scaling); + let ten = I256(Uint::from(10) * native_scaling); assert_eq!(zero.to_string_native(), "0.000000"); assert_eq!(neg_one.to_string_native(), "-1.000000"); @@ -387,22 +387,22 @@ mod test_uint { /// Test that we correctly handle arithmetic with two's complement #[test] fn test_arithmetic() { - let zero = SignedUint::try_from(Uint::zero()).expect("Test failed"); - let neg_one = SignedUint(Uint::max_value()); - let neg_eight = SignedUint(Uint::max_value() - Uint::from(7)); - let two = SignedUint(Uint::from(2)); - let ten = SignedUint(Uint::from(10)); + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = I256(Uint::max_value()); + let neg_eight = I256(Uint::max_value() - Uint::from(7)); + let two = I256(Uint::from(2)); + let ten = I256(Uint::from(10)); assert_eq!(zero + neg_one, neg_one); assert_eq!(neg_one - zero, neg_one); - assert_eq!(zero - neg_one, SignedUint(Uint::one())); + assert_eq!(zero - neg_one, I256(Uint::one())); assert_eq!(two - neg_eight, ten); - assert_eq!(two + ten, SignedUint(Uint::from(12))); + assert_eq!(two + ten, I256(Uint::from(12))); assert_eq!(ten - two, -neg_eight); assert_eq!(two - ten, neg_eight); - assert_eq!(neg_eight + neg_one, -SignedUint(Uint::from(9))); - assert_eq!(neg_one - neg_eight, SignedUint(Uint::from(7))); - assert_eq!(neg_eight - neg_one, -SignedUint(Uint::from(7))); + assert_eq!(neg_eight + neg_one, -I256(Uint::from(9))); + assert_eq!(neg_one - neg_eight, I256(Uint::from(7))); + assert_eq!(neg_eight - neg_one, -I256(Uint::from(7))); assert_eq!(neg_eight - two, -ten); assert!((two - two).is_zero()); } From acc79d2f28cec29db39f3b0fa952664c08d20b48 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Apr 2023 14:47:40 +0200 Subject: [PATCH 25/69] [fix]: Refactored gen_sheilded_transfer to only denominate amounts at the masp crate boundary --- apps/src/lib/client/tx.rs | 198 +++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 101 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index cd457ddede..43093543a0 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1362,14 +1362,20 @@ fn convert_amount( epoch: Epoch, token: &Address, sub_prefix: &Option<&String>, - val: u64, - denom: MaspDenom, -) -> (AssetType, Amount) { - let asset_type = make_asset_type(epoch, token, sub_prefix, denom); - // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, val) - .expect("invalid value for amount"); - (asset_type, amount) + val: &token::Amount, +) -> ([AssetType; 4], Amount) { + let mut amount = Amount::zero(); + let asset_types: [AssetType; 4] = MaspDenom::iter().map(|denom| { + let asset_type = make_asset_type(epoch, token, sub_prefix, denom); + // Combine the value and unit into one amount + amount += Amount::from_nonnegative(asset_type, denom.denominate(val)) + .expect("invalid value for amount"); + asset_type + }) + .collect() + .try_into() + .expect("This can't fail"); + (asset_types, amount) } /// Make shielded components to embed within a Transfer object. If no shielded @@ -1426,7 +1432,23 @@ async fn gen_shielded_transfer( LocalTxProver::with_default_location() .expect("unable to load MASP Parameters") }; - let fee_amount = if spending_key.is_some() { + + // break up a transfer into a number of transfers with suitable + // denominations + let InputAmount::Validated(amt) = args.amount else { + unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") + }; + + // Convert transaction amount into MASP types + let (asset_types, amount) = convert_amount( + epoch, + &ctx.get(&args.token), + &args.sub_prefix.as_ref(), + &amt.amount, + ); + + // If there are shielded inputs + if let Some(sk) = spending_key { let InputAmount::Validated(fee) = args.tx.fee_amount else { unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") }; @@ -1436,111 +1458,83 @@ async fn gen_shielded_transfer( epoch, &ctx.get(&args.tx.fee_token), &None, - MaspDenom::Zero.denominate(&fee.amount), - MaspDenom::Zero, + &fee.amount, ); - builder.set_fee(shielded_fee)?; - if shielded_gas { - fee.amount - } else { - token::Amount::zero() + builder.set_fee(shielded_fee.clone())?; + let required_amt = if shielded_gas { amount + shielded_fee } else { amount }; + // Locate unspent notes that can help us meet the transaction amount + let (_, unspent_notes, used_convs) = ctx + .shielded + .collect_unspent_notes( + args.tx.ledger_address.clone(), + &to_viewing_key(&sk).vk, + required_amt, + epoch, + ) + .await; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { + builder.add_sapling_spend( + sk, + diversifier, + note, + merkle_path, + )?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if *value > 0 { + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; + } } } else { // No transfer fees come from the shielded transaction for non-MASP // sources builder.set_fee(Amount::zero())?; - token::Amount::zero() - }; - // break up a transfer into a number of transfers with suitable - // denominations - let InputAmount::Validated(amt) = args.amount else { - unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") - }; - for denom in MaspDenom::iter() { - let denom_amount = denom.denominate(&(amt.amount + fee_amount)); - if denom_amount == 0 { - continue; - } - // Convert transaction amount into MASP types - let (asset_type, required_amt) = convert_amount( - epoch, - &ctx.get(&args.token), - &args.sub_prefix.as_ref(), - denom_amount, - denom, - ); - - // If there are shielded inputs - if let Some(sk) = spending_key { - // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = ctx - .shielded - .collect_unspent_notes( - args.tx.ledger_address.clone(), - &to_viewing_key(&sk).vk, - required_amt, - epoch, - ) - .await; - // Commit the notes found to our transaction - for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend( - sk, - diversifier, - note, - merkle_path, - )?; - } - // Commit the conversion notes used during summation - for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; - } - } - } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of - // the parent Transfer object is used to validate fund - // availability - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); + // We add a dummy UTXO to our transaction, but only the source of + // the parent Transfer object is used to validate fund + // availability + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { builder.add_transparent_input( secp_sk, OutPoint::new([0u8; 32], 0), TxOut { - asset_type, - value: denom_amount, - script_pubkey: script, + asset_type: *asset_type, + value: denom.denominate(&amt), + script_pubkey: script.clone(), }, )?; } + } - // Now handle the outputs of this transaction - // If there is a shielded output - if let Some(pa) = payment_address { - let ovk_opt = spending_key.map(|x| x.expsk.ovk); + // Now handle the outputs of this transaction + // If there is a shielded output + if let Some(pa) = payment_address { + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { builder.add_sapling_output( ovk_opt, pa.into(), - asset_type, - denom_amount, + *asset_type, + denom.denominate(&amt), memo.clone(), )?; - } else { + } + } else { // Embed the transparent target address into the shielded // transaction so that it can be signed let target = ctx.get(&args.target); @@ -1552,13 +1546,15 @@ async fn gen_shielded_transfer( let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( target_enc.as_ref(), )); - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - asset_type, - denom_amount, - )?; + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + *asset_type, + denom.denominate(&amt), + )?; + } } - } + // Build and return the constructed transaction builder .build(consensus_branch_id, &prover) From 1e3e77dbbeae4d9984554d23ef560f85dbad181d Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 25 Apr 2023 21:55:37 +0800 Subject: [PATCH 26/69] Update query_sheilded_balance to handle the multiple denominations --- apps/src/lib/client/rpc.rs | 97 +++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1fd77f109c..807c523f22 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1131,67 +1131,86 @@ pub async fn query_shielded_balance( } // These are the asset types for which we have human-readable names - let mut read_tokens = HashSet::new(); + let mut read_tokens: HashMap>> = HashMap::new(); // Print non-zero balances whose asset types can be decoded + // TODO Implement a function for this + + let mut balance_map = HashMap::new(); for (asset_type, balances) in balances { - // Decode the asset type let decoded = ctx .shielded .decode_asset_type(client.clone(), asset_type) .await; + match decoded { Some((addr, sub_prefix, denom, asset_epoch)) if asset_epoch == epoch => { - // Only assets with the current timestamp count - println!( - "Shielded Token {}{}:", - tokens - .get(&addr) - .cloned() - .unwrap_or_else(|| addr.clone()), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), - ); - read_tokens.insert(addr.clone()); - let mut found_any = false; - for (fvk, value) in balances { - let value = token::Amount::from_masp_denominated( - value as u64, - denom, - ); - let formatted = format_denominated_amount( - &client, - &addr, - &sub_prefix, - value, - ) - .await; - println!(" {}, owned by {}", formatted, fvk); - found_any = true; - } - if !found_any { + // remove this from here, should not be making the hashtable creation any uglier + if balances.is_empty() { println!( "No shielded {} balance found for any wallet \ key", asset_type ); } + let asset_type = (addr, sub_prefix); + for (fvk, value) in balances { + let token_value = token::Amount::from_masp_denominated(value as u64, denom); + balance_map.entry((fvk, asset_type.clone())).and_modify(|value_2| *value_2 += token_value).or_insert(token_value); + } + } _ => {} - } + }; + } + for ((fvk, (addr, sub_prefix)), token_balance) in balance_map { + read_tokens.entry(addr.clone()).and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())).or_insert(vec![sub_prefix.clone()]); + // Only assets with the current timestamp count + println!( + "Shielded Token {}{}:", + tokens + .get(&addr) + .cloned() + .unwrap_or_else(|| addr.clone()), + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), + ); + let formatted = format_denominated_amount( + &client, + &addr, + &sub_prefix, + token_balance, + ) + .await; + println!(" {}, owned by {}", formatted, fvk); } // Print zero balances for remaining assets for token in tokens { - if !read_tokens.contains(&token) { + if let Some(sub_addrs) = read_tokens.get(&token) { let token_alias = lookup_alias(ctx, &token); - println!("Shielded Token {}:", token_alias); - println!( - "No shielded {} balance found for any wallet key", - token_alias - ); + for sub_addr in sub_addrs { + match sub_addr { + // abstract out these prints + Some(sub_addr) => { + println!("Shielded Token {}/{}:", token_alias, sub_addr); + println!( + "No shielded {}/{} balance found for any wallet key", + token_alias, sub_addr + ); + + } + None => { + println!("Shielded Token {}:", token_alias, ); + println!( + "No shielded {} balance found for any wallet key", + token_alias + ); + } + } + } } } } From e71cd44b2ff3b55fa64a7ad29f6be0923881ee81 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 25 Apr 2023 21:55:37 +0800 Subject: [PATCH 27/69] Update query_sheilded_balance to handle the multiple denominations --- apps/src/lib/client/rpc.rs | 109 ++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1fd77f109c..dc8c406810 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1131,67 +1131,98 @@ pub async fn query_shielded_balance( } // These are the asset types for which we have human-readable names - let mut read_tokens = HashSet::new(); + let mut read_tokens: HashMap>> = + HashMap::new(); // Print non-zero balances whose asset types can be decoded + // TODO Implement a function for this + + let mut balance_map = HashMap::new(); for (asset_type, balances) in balances { - // Decode the asset type let decoded = ctx .shielded .decode_asset_type(client.clone(), asset_type) .await; + match decoded { Some((addr, sub_prefix, denom, asset_epoch)) if asset_epoch == epoch => { - // Only assets with the current timestamp count - println!( - "Shielded Token {}{}:", - tokens - .get(&addr) - .cloned() - .unwrap_or_else(|| addr.clone()), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), - ); - read_tokens.insert(addr.clone()); - let mut found_any = false; - for (fvk, value) in balances { - let value = token::Amount::from_masp_denominated( - value as u64, - denom, - ); - let formatted = format_denominated_amount( - &client, - &addr, - &sub_prefix, - value, - ) - .await; - println!(" {}, owned by {}", formatted, fvk); - found_any = true; - } - if !found_any { + // remove this from here, should not be making the + // hashtable creation any uglier + if balances.is_empty() { println!( "No shielded {} balance found for any wallet \ key", asset_type ); } + let asset_type = (addr, sub_prefix); + for (fvk, value) in balances { + let token_value = + token::Amount::from_masp_denominated( + value as u64, + denom, + ); + balance_map + .entry((fvk, asset_type.clone())) + .and_modify(|value_2| *value_2 += token_value) + .or_insert(token_value); + } } _ => {} - } + }; + } + for ((fvk, (addr, sub_prefix)), token_balance) in balance_map { + read_tokens + .entry(addr.clone()) + .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) + .or_insert(vec![sub_prefix.clone()]); + // Only assets with the current timestamp count + println!( + "Shielded Token {}{}:", + tokens.get(&addr).cloned().unwrap_or_else(|| addr.clone()), + sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default(), + ); + let formatted = format_denominated_amount( + &client, + &addr, + &sub_prefix, + token_balance, + ) + .await; + println!(" {}, owned by {}", formatted, fvk); } // Print zero balances for remaining assets for token in tokens { - if !read_tokens.contains(&token) { + if let Some(sub_addrs) = read_tokens.get(&token) { let token_alias = lookup_alias(ctx, &token); - println!("Shielded Token {}:", token_alias); - println!( - "No shielded {} balance found for any wallet key", - token_alias - ); + for sub_addr in sub_addrs { + match sub_addr { + // abstract out these prints + Some(sub_addr) => { + println!( + "Shielded Token {}/{}:", + token_alias, sub_addr + ); + println!( + "No shielded {}/{} balance found for any \ + wallet key", + token_alias, sub_addr + ); + } + None => { + println!("Shielded Token {}:", token_alias,); + println!( + "No shielded {} balance found for any \ + wallet key", + token_alias + ); + } + } + } } } } From 8de6a448c8176f053bf90134e81c9faf71ce524e Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Apr 2023 17:58:14 +0200 Subject: [PATCH 28/69] Starting replacing rust_decimal crate in PoS --- core/src/types/token.rs | 8 ++ proof_of_stake/src/lib.rs | 49 ++++--- proof_of_stake/src/parameters.rs | 33 ++--- proof_of_stake/src/rewards.rs | 42 +++--- proof_of_stake/src/tests.rs | 10 +- proof_of_stake/src/types.rs | 222 +++++++++++++++++++++++++------ 6 files changed, 252 insertions(+), 112 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 7e7ea3b20e..fe16161a74 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -518,6 +518,14 @@ impl Add for Amount { } } +impl Add for Amount { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self { raw: self.raw + Uint::from(rhs)} + } +} + impl std::iter::Sum for Amount { fn sum>(iter: I) -> Self { iter.fold(Amount::zero(), |acc, amt| acc + amt) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 612be66c25..75e84cd87c 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -44,22 +44,22 @@ use namada_core::types::key::{ }; pub use namada_core::types::storage::Epoch; use namada_core::types::token; -use namada_core::types::token::Amount; +use namada_core::types::token::{Amount, DenominatedAmount}; use once_cell::unsync::Lazy; use parameters::PosParams; use rewards::PosRewardsCalculator; -use rust_decimal::Decimal; use storage::{ bonds_for_source_prefix, bonds_prefix, consensus_keys_key, get_validator_address_from_bond, into_tm_voting_power, is_bond_key, is_unbond_key, is_validator_slashes_key, last_block_proposer_key, - mult_amount, mult_change_to_amount, num_consensus_validators_key, - params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, + num_consensus_validators_key, params_key, slashes_prefix, + unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, }; use thiserror::Error; +use namada_core::types::uint::Uint; use types::{ BelowCapacityValidatorSet, BelowCapacityValidatorSets, BondId, Bonds, CommissionRates, ConsensusValidator, ConsensusValidatorSet, @@ -70,7 +70,7 @@ use types::{ WeightedValidator, }; -use crate::types::decimal_mult_u128; +use crate::types::Dec; /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -168,9 +168,9 @@ pub enum SlashError { #[derive(Error, Debug)] pub enum CommissionRateChangeError { #[error("Unexpected negative commission rate {0} for validator {1}")] - NegativeRate(Decimal, Address), + NegativeRate(DenominatedAmount, Address), #[error("Rate change of {0} is too large for validator {1}")] - RateChangeTooLarge(Decimal, Address), + RateChangeTooLarge(DenominatedAmount, Address), #[error( "There is no maximum rate change written in storage for validator {0}" )] @@ -477,7 +477,7 @@ where pub fn read_validator_max_commission_rate_change( storage: &S, validator: &Address, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { @@ -489,7 +489,7 @@ where pub fn write_validator_max_commission_rate_change( storage: &mut S, validator: &Address, - change: Decimal, + change: DenominatedAmount, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1585,8 +1585,8 @@ pub fn become_validator( address: &Address, consensus_key: &common::PublicKey, current_epoch: Epoch, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: DenominatedAmount, + max_commission_rate_change: DenominatedAmount, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1739,19 +1739,19 @@ where pub fn change_validator_commission_rate( storage: &mut S, validator: &Address, - new_rate: Decimal, + new_rate: DenominatedAmount, current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - if new_rate < Decimal::ZERO { - return Err(CommissionRateChangeError::NegativeRate( - new_rate, - validator.clone(), - ) - .into()); - } + // if new_rate < Uint::zero() { + // return Err(CommissionRateChangeError::NegativeRate( + // new_rate, + // validator.clone(), + // ) + // .into()); + // } let max_change = read_validator_max_commission_rate_change(storage, validator)?; @@ -2688,10 +2688,9 @@ where // Compute the fractional block rewards for each consensus validator and // update the reward accumulators - let consensus_stake_unscaled: Decimal = - total_consensus_stake.as_dec_unscaled(); - let signing_stake_unscaled: Decimal = total_signing_stake.as_dec_unscaled(); - let mut values: HashMap = HashMap::new(); + let consensus_stake_unscaled: Dec = total_consensus_stake.into(); + let signing_stake_unscaled: Dec = total_signing_stake.into(); + let mut values: HashMap= HashMap::new(); for validator in consensus_validators.iter(storage)? { let ( NestedSubKey::Data { @@ -2709,8 +2708,8 @@ where continue; } - let mut rewards_frac = Decimal::default(); - let stake_unscaled: Decimal = stake.as_dec_unscaled(); + let mut rewards_frac = Dec::zero(); + let stake_unscaled: Dec = stake.into(); // println!( // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = // {}", epoch, stake diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 1422971089..9eb5b6de05 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -5,6 +5,7 @@ use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use rust_decimal_macros::dec; use thiserror::Error; +use crate::types::Dec; /// Proof-of-Stake system parameters, set at genesis and can only be changed via /// governance @@ -23,22 +24,22 @@ pub struct PosParams { /// The voting power per fundamental unit of the staking token (namnam). /// Used in validators' voting power calculation to interface with /// tendermint. - pub tm_votes_per_token: Decimal, + pub tm_votes_per_token: Dec, /// Amount of tokens rewarded to a validator for proposing a block - pub block_proposer_reward: Decimal, + pub block_proposer_reward: Dec, /// Amount of tokens rewarded to each validator that voted on a block /// proposal - pub block_vote_reward: Decimal, + pub block_vote_reward: Dec, /// Maximum staking rewards rate per annum - pub max_inflation_rate: Decimal, + pub max_inflation_rate: Dec, /// Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: Decimal, + pub target_staked_ratio: Dec, /// Fraction of validator's stake that should be slashed on a duplicate /// vote. - pub duplicate_vote_min_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Dec, /// Fraction of validator's stake that should be slashed on a light client /// attack. - pub light_client_attack_min_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Dec, } impl Default for PosParams { @@ -49,17 +50,17 @@ impl Default for PosParams { unbonding_len: 21, // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per // namnam) - tm_votes_per_token: dec!(1.0), - block_proposer_reward: dec!(0.125), - block_vote_reward: dec!(0.1), + tm_votes_per_token: Dec::one(), + block_proposer_reward: Dec::new(125, 3).expect("Test failed"), + block_vote_reward: Dec::new(1, 1).expect("Test failed"), // PoS inflation of 10% - max_inflation_rate: dec!(0.1), + max_inflation_rate: Dec::new(1, 1).expect("Test failed"), // target staked ratio of 2/3 - target_staked_ratio: dec!(0.6667), + target_staked_ratio: Dec::new(6667, 4).expect("Test failed"), // slash 0.1% - duplicate_vote_min_slash_rate: dec!(0.001), + duplicate_vote_min_slash_rate: Dec::new(1, 3).expect("Test failed"), // slash 0.1% - light_client_attack_min_slash_rate: dec!(0.001), + light_client_attack_min_slash_rate: Dec::new(1, 3).expect("Test failed"), } } } @@ -73,7 +74,7 @@ pub enum ValidationError { )] TotalVotingPowerTooLarge(u64), #[error("Votes per token cannot be greater than 1, got {0}")] - VotesPerTokenGreaterThanOne(Decimal), + VotesPerTokenGreaterThanOne(Dec), #[error("Pipeline length must be >= 2, got {0}")] PipelineLenTooShort(u64), #[error( @@ -188,7 +189,7 @@ pub mod testing { max_validator_slots, pipeline_len, unbonding_len, - tm_votes_per_token: Decimal::from(tm_votes_per_token) / dec!(10_000), + tm_votes_per_token: Dec(Uint::from(tm_votes_per_token)) / Dec(Uint::from(10_000)), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index 6f830d9c52..e189cbc1a7 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -1,10 +1,12 @@ //! PoS rewards distribution. -use rust_decimal::Decimal; -use rust_decimal_macros::dec; use thiserror::Error; +use namada_core::types::token::{Amount, DenominatedAmount}; +use namada_core::types::uint::Uint; +use crate::types::Dec; -const MIN_PROPOSER_REWARD: Decimal = dec!(0.01); +/// This is equal to 0.01. +const MIN_PROPOSER_REWARD: Dec = Dec(Uint([10000u64, 0u64, 0u64, 0u64])); /// Errors during rewards calculation #[derive(Debug, Error)] @@ -16,8 +18,8 @@ pub enum RewardsError { least 2/3 of the total bonded stake)." )] InsufficientVotes { - votes_needed: u64, - signing_stake: u64, + votes_needed: Uint, + signing_stake: Uint, }, /// rewards coefficients are not set #[error("Rewards coefficients are not properly set.")] @@ -28,9 +30,9 @@ pub enum RewardsError { #[derive(Debug, Copy, Clone)] #[allow(missing_docs)] pub struct PosRewards { - pub proposer_coeff: Decimal, - pub signer_coeff: Decimal, - pub active_val_coeff: Decimal, + pub proposer_coeff: Dec, + pub signer_coeff: Dec, + pub active_val_coeff: Dec, } /// Holds relevant PoS parameters and is used to calculate the coefficients for @@ -38,13 +40,13 @@ pub struct PosRewards { #[derive(Debug, Copy, Clone)] pub struct PosRewardsCalculator { /// Rewards fraction that goes to the block proposer - pub proposer_reward: Decimal, + pub proposer_reward: Dec, /// Rewards fraction that goes to the block signers - pub signer_reward: Decimal, + pub signer_reward: Dec, /// Total stake of validators who signed the block - pub signing_stake: u64, + pub signing_stake: Amount, /// Total stake of the whole consensus set - pub total_stake: u64, + pub total_stake: Amount, } impl PosRewardsCalculator { @@ -64,18 +66,18 @@ impl PosRewardsCalculator { if signing_stake < votes_needed { return Err(RewardsError::InsufficientVotes { - votes_needed, - signing_stake, + votes_needed: votes_needed.into(), + signing_stake: signing_stake.into(), }); } // Logic for determining the coefficients. - let proposer_coeff = proposer_reward - * Decimal::from(signing_stake - votes_needed) - / Decimal::from(total_stake) + let proposer_coeff = Dec::from(proposer_reward + * (signing_stake - votes_needed)) + / Dec::from(total_stake) + MIN_PROPOSER_REWARD; let signer_coeff = signer_reward; - let active_val_coeff = dec!(1.0) - proposer_coeff - signer_coeff; + let active_val_coeff = Dec::one() - proposer_coeff - signer_coeff; let coeffs = PosRewards { proposer_coeff, @@ -87,7 +89,7 @@ impl PosRewardsCalculator { } /// Implement as ceiling of (2/3) * validator set stake - fn get_min_required_votes(&self) -> u64 { - ((2 * self.total_stake) + 3 - 1) / 3 + fn get_min_required_votes(&self) -> Amount { + ((self.total_stake * 2u64) + (3u64 - 1u64)) / 3u64 } } diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index fd24ce2042..e7a1ef4d75 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -29,11 +29,7 @@ use test_log::test; use crate::parameters::testing::arb_pos_params; use crate::parameters::PosParams; -use crate::types::{ - into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, - ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, - UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, -}; +use crate::types::{into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, Dec}; use crate::{ become_validator, below_capacity_validator_set_handle, bond_handle, bond_tokens, bonds_and_unbonds, consensus_validator_set_handle, @@ -1708,8 +1704,8 @@ fn arb_genesis_validators( let consensus_sk = common_sk_from_simple_seed(seed); let consensus_key = consensus_sk.to_public(); - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 3); + let commission_rate = Dec::new(5, 2).expect("Test failed"); + let max_commission_rate_change = Dec::new(1, 3).expect("Test failed"); GenesisValidator { address, tokens, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index fee3197c71..3d0ae8e489 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,7 +5,7 @@ mod rev_order; use core::fmt::Debug; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; -use std::fmt::Display; +use std::fmt::{Display, Formatter, Write}; use std::hash::Hash; use std::ops::Sub; @@ -21,8 +21,7 @@ use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; pub use rev_order::ReverseOrdTokenAmount; -use rust_decimal::prelude::{Decimal, ToPrimitive}; -use rust_decimal::RoundingStrategy; +use namada_core::types::uint::Uint; use crate::parameters::PosParams; @@ -124,7 +123,7 @@ pub type TotalDeltas = crate::epoched::EpochedDelta< /// Epoched validator commission rate pub type CommissionRates = - crate::epoched::Epoched; + crate::epoched::Epoched; /// Epoched validator's bonds pub type Bonds = crate::epoched::EpochedDelta< @@ -143,17 +142,17 @@ pub type ConsensusKeys = LazySet; /// Commission rate and max commission rate change per epoch for a validator pub struct CommissionPair { /// Validator commission rate - pub commission_rate: Decimal, + pub commission_rate: Dec, /// Validator max commission rate change per epoch - pub max_commission_change_per_epoch: Decimal, + pub max_commission_change_per_epoch: Dec, } /// Epoched rewards products -pub type RewardsProducts = LazyMap; +pub type RewardsProducts = LazyMap; /// Consensus validator rewards accumulator (for tracking the fractional block /// rewards owed over the course of an epoch) -pub type RewardsAccumulator = LazyMap; +pub type RewardsAccumulator = LazyMap; // -------------------------------------------------------------------------------------------- @@ -177,9 +176,9 @@ pub struct GenesisValidator { /// A public key used for signing validator's consensus actions pub consensus_key: common::PublicKey, /// Commission rate charged on rewards for delegators (bounded inside 0-1) - pub commission_rate: Decimal, + pub commission_rate: Dec, /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Decimal, + pub max_commission_rate_change: Dec, } /// An update of the consensus and below-capacity validator set. @@ -446,7 +445,7 @@ impl Display for BondId { impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. - pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { + pub fn get_slash_rate(&self, params: &PosParams) -> Dec { match self { SlashType::DuplicateVote => params.duplicate_vote_min_slash_rate, SlashType::LightClientAttack => { @@ -465,51 +464,186 @@ impl Display for SlashType { } } -/// Multiply a value of type Decimal with one of type u64 and then return the -/// truncated u64 -pub fn decimal_mult_u128(dec: Decimal, int: u128) -> u128 { - let prod = dec * Decimal::from(int); - // truncate the number to the floor - prod.to_u128().expect("Product is out of bounds") +// -------------------------------------------------------------------------------------------- + +/// The numbrer of Dec places for PoS rational calculations +pub const POS_DECIMAL_PRECISION: u8 = 6; + +/// A 256 bit number with [`POS_DECIMAL_PRECISION`] number of Dec places. +/// +/// To be precise, an instance X of this type should be interpeted as the Dec +/// X * 10 ^ (-[`POS_DECIMAL_PRECISION`]) +#[derive( + Clone, + Copy, + Default, + BorshSerialize, + BorshDeserialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, +)] +pub struct Dec(pub Uint); + +impl std::ops::Deref for Dec { + type Target = Uint; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -/// Multiply a value of type Decimal with one of type i128 and then return the -/// truncated i128 -pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { - let prod = dec * Decimal::from(int); - // truncate the number to the floor - prod.to_i128().expect("Product is out of bounds") +impl std::ops::DerefMut for Dec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } -/// Multiply a value of type Decimal with one of type Uint and then convert it -/// to an Amount type -pub fn mult_change_to_amount( - dec: Decimal, - change: token::Change, -) -> token::Amount { - // this function is used for slashing calculations. We want to err - // on the side of slashing more, not less. - let dec = - dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); - Amount::from_decimal(dec, NATIVE_MAX_DECIMAL_PLACES).unwrap() * change.abs() +impl Dec { + pub fn trunc_div(&self, rhs: &Self) -> Option { + self.0.fixed_precision_div(rhs, POS_DECIMAL_PRECISION) + .map(Self) + } + + pub fn zero() -> Self { + Self(Uint::zero()) + } + + pub fn one() -> Self { + Self(Uint::one()) + } + + pub fn new(matissa: u64, scale: u8) -> Option { + if scale > POS_DECIMAL_PRECISION { + None + } else { + Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) + .checked_mul(Uint::from(matissa)) + .map(Self) + } + } +} + +impl From for Dec { + fn from(amt: Amount) -> Self { + Self(amt.into()) + } } -/// Multiply a value of type Decimal with one of type Amount and then return the -/// truncated Amount -pub fn mult_amount(dec: Decimal, amount: token::Amount) -> token::Amount { - let dec = - dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); - Amount::from_decimal(dec, NATIVE_MAX_DECIMAL_PLACES).unwrap() * amount +impl std::ops::Add for Dec { + type Output = Self; + + fn add(self, rhs: Dec) -> Self::Output { + Self(self.0 + rhs.0) + } } +impl std::ops::Add for Dec { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + Uint::from(rhs)) + } +} + +impl std::ops::Sub for Dec { + type Output = Self; + + fn sub(self, rhs: Dec) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl std::ops::Mul for Dec { + type Output = Dec; + + fn mul(self, rhs: u128) -> Self::Output { + Self(self.0 * Uint::from(rhs)) + } +} + +impl std::ops::Mul for Dec { + type Output = Amount; + + fn mul(self, rhs: Amount) -> Self::Output { + self.0 * rhs + } +} + +impl std::ops::Mul for Dec { + type Output = Self; + + fn mul(self, rhs: Dec) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +impl std::ops::Div for Dec { + type Output = Self; + + fn div(self, rhs: Dec) -> Self::Output { + Self(self.0 / rhs.0) + } +} + +impl Display for Dec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let string = self.0.to_string(); + f.write_str(&string) + } +} + +// -------------------------------------------------------------------------------------------- + +// /// Multiply a value of type Dec with one of type u64 and then return the +// /// truncated u64 +// pub fn decimal_mult_u128(dec: Dec, int: u128) -> u128 { +// let prod = dec * Dec::from(int); +// // truncate the number to the floor +// prod.to_u128().expect("Product is out of bounds") +// } +// +// /// Multiply a value of type Dec with one of type i128 and then return the +// /// truncated i128 +// pub fn decimal_mult_i128(dec: Dec, int: i128) -> i128 { +// let prod = dec * Dec::from(int); +// // truncate the number to the floor +// prod.to_i128().expect("Product is out of bounds") +// } + +// /// Multiply a value of type Dec with one of type Uint and then convert it +// /// to an Amount type +// pub fn mult_change_to_amount( +// dec: Dec, +// change: token::Change, +// ) -> token::Amount { +// // this function is used for slashing calculations. We want to err +// // on the side of slashing more, not less. +// let dec = +// dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); +// Amount::from_Dec(dec, NATIVE_MAX_Dec_PLACES).unwrap() * change.abs() +// } + +// /// Multiply a value of type Dec with one of type Amount and then return the +// /// truncated Amount +// pub fn mult_amount(dec: Dec, amount: token::Amount) -> token::Amount { +// let dec = +// dec.round_dp_with_strategy(6, RoundingStrategy::ToPositiveInfinity); +// Amount::from_Dec(dec, NATIVE_MAX_Dec_PLACES).unwrap() * amount +// } + /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens pub fn into_tm_voting_power( - votes_per_token: Decimal, - tokens: impl Into, + votes_per_token: Dec, + tokens: Amount, ) -> i64 { - let prod = decimal_mult_u128(votes_per_token, tokens.into() as u128); - i64::try_from(prod).expect("Invalid voting power") + let pow = votes_per_token * u128::try_from(tokens).expect("Voting power out of bounds"); + i64::try_from(pow.0).expect("Invalid voting power") } #[cfg(test)] From 8920d0ad3949b98202990e5a43130bbfadfc6622 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Apr 2023 16:28:27 -0400 Subject: [PATCH 29/69] WIP continue replacing rust_decimal crate in PoS --- Cargo.lock | 1 - apps/src/lib/cli.rs | 16 +- apps/src/lib/client/tx.rs | 90 +++--- .../lib/node/ledger/shell/finalize_block.rs | 89 +++--- core/src/types/dec.rs | 271 ++++++++++++++++++ core/src/types/mod.rs | 1 + core/src/types/token.rs | 15 +- core/src/types/transaction/mod.rs | 6 +- core/src/types/transaction/pos.rs | 4 +- core/src/types/uint.rs | 33 ++- proof_of_stake/src/lib.rs | 95 +++--- proof_of_stake/src/parameters.rs | 34 ++- proof_of_stake/src/rewards.rs | 14 +- proof_of_stake/src/tests.rs | 64 ++--- proof_of_stake/src/tests/state_machine.rs | 6 +- proof_of_stake/src/types.rs | 148 +--------- shared/src/ledger/inflation.rs | 33 +-- shared/src/ledger/pos/mod.rs | 13 +- shared/src/types/mod.rs | 4 +- tx_prelude/Cargo.toml | 1 - tx_prelude/src/proof_of_stake.rs | 4 +- 21 files changed, 538 insertions(+), 404 deletions(-) create mode 100644 core/src/types/dec.rs diff --git a/Cargo.lock b/Cargo.lock index ff1836db36..8bba1dde6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4078,7 +4078,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.10.6", "thiserror", ] diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 6cfb99269d..26babc1538 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1622,6 +1622,7 @@ pub mod args { use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; + use namada::types::dec::Dec; use namada::types::key::*; use namada::types::masp::MaspValue; use namada::types::storage::{self, BlockHeight, Epoch}; @@ -1629,7 +1630,6 @@ pub mod args { use namada::types::token; use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::GasLimit; - use rust_decimal::Decimal; use super::context::*; use super::utils::*; @@ -1662,7 +1662,7 @@ pub mod args { const CHANNEL_ID: Arg = arg("channel-id"); const CODE_PATH: Arg = arg("code-path"); const CODE_PATH_OPT: ArgOpt = CODE_PATH.opt(); - const COMMISSION_RATE: Arg = arg("commission-rate"); + const COMMISSION_RATE: Arg = arg("commission-rate"); const CONSENSUS_TIMEOUT_COMMIT: ArgDefault = arg_default( "consensus-timeout-commit", DefaultFn(|| Timeout::from_str("1s").unwrap()), @@ -1709,7 +1709,7 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("node"); const LOCALHOST: ArgFlag = flag("localhost"); const MASP_VALUE: Arg = arg("value"); - const MAX_COMMISSION_RATE_CHANGE: Arg = + const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); @@ -2183,8 +2183,8 @@ pub mod args { pub account_key: Option, pub consensus_key: Option, pub protocol_key: Option, - pub commission_rate: Decimal, - pub max_commission_rate_change: Decimal, + pub commission_rate: Dec, + pub max_commission_rate_change: Dec, pub validator_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -2893,7 +2893,7 @@ pub mod args { /// Validator address (should be self) pub validator: WalletAddress, /// Value to which the tx changes the commission rate - pub rate: Decimal, + pub rate: Dec, } impl Args for TxCommissionRateChange { @@ -3674,8 +3674,8 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitGenesisValidator { pub alias: String, - pub commission_rate: Decimal, - pub max_commission_rate_change: Decimal, + pub commission_rate: Dec, + pub max_commission_rate_change: Dec, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, pub key_scheme: SchemeType, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 43093543a0..97e97094cf 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -42,6 +42,7 @@ use namada::ledger::masp; use namada::ledger::pos::{CommissionPair, PosParams}; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; +use namada::types::dec::Dec; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; @@ -61,7 +62,6 @@ use namada::types::transaction::governance::{ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; use rand_core::{CryptoRng, OsRng, RngCore}; -use rust_decimal::Decimal; use sha2::Digest; use tokio::time::{Duration, Instant}; @@ -332,7 +332,7 @@ pub async fn submit_init_validator( .unwrap(); // Validate the commission rate data - if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { + if commission_rate > Dec::one() || commission_rate < Dec::zero() { eprintln!( "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" @@ -341,8 +341,8 @@ pub async fn submit_init_validator( safe_exit(1) } } - if max_commission_rate_change > Decimal::ONE - || max_commission_rate_change < Decimal::ZERO + if max_commission_rate_change > Dec::one() + || max_commission_rate_change < Dec::zero() { eprintln!( "The validator maximum change in commission rate per epoch must \ @@ -1365,16 +1365,18 @@ fn convert_amount( val: &token::Amount, ) -> ([AssetType; 4], Amount) { let mut amount = Amount::zero(); - let asset_types: [AssetType; 4] = MaspDenom::iter().map(|denom| { - let asset_type = make_asset_type(epoch, token, sub_prefix, denom); - // Combine the value and unit into one amount - amount += Amount::from_nonnegative(asset_type, denom.denominate(val)) - .expect("invalid value for amount"); - asset_type - }) - .collect() - .try_into() - .expect("This can't fail"); + let asset_types: [AssetType; 4] = MaspDenom::iter() + .map(|denom| { + let asset_type = make_asset_type(epoch, token, sub_prefix, denom); + // Combine the value and unit into one amount + amount += + Amount::from_nonnegative(asset_type, denom.denominate(val)) + .expect("invalid value for amount"); + asset_type + }) + .collect() + .try_into() + .expect("This can't fail"); (asset_types, amount) } @@ -1461,7 +1463,11 @@ async fn gen_shielded_transfer( &fee.amount, ); builder.set_fee(shielded_fee.clone())?; - let required_amt = if shielded_gas { amount + shielded_fee } else { amount }; + let required_amt = if shielded_gas { + amount + shielded_fee + } else { + amount + }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx .shielded @@ -1474,12 +1480,7 @@ async fn gen_shielded_transfer( .await; // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend( - sk, - diversifier, - note, - merkle_path, - )?; + builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { @@ -1498,10 +1499,9 @@ async fn gen_shielded_transfer( // We add a dummy UTXO to our transaction, but only the source of // the parent Transfer object is used to validate fund // availability - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); + let secp_sk = + secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); + let secp_ctx = secp256k1::Secp256k1::::gen_new(); let secp_pk = secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) .serialize(); @@ -1535,25 +1535,25 @@ async fn gen_shielded_transfer( )?; } } else { - // Embed the transparent target address into the shielded - // transaction so that it can be signed - let target = ctx.get(&args.target); - let target_enc = target - .address() - .expect("target address should be transparent") - .try_to_vec() - .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( - target_enc.as_ref(), - )); - for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - *asset_type, - denom.denominate(&amt), - )?; - } + // Embed the transparent target address into the shielded + // transaction so that it can be signed + let target = ctx.get(&args.target); + let target_enc = target + .address() + .expect("target address should be transparent") + .try_to_vec() + .expect("target address encoding"); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + target_enc.as_ref(), + )); + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + *asset_type, + denom.denominate(&amt), + )?; } + } // Build and return the constructed transaction builder @@ -2912,7 +2912,7 @@ pub async fn submit_validator_commission_change( let validator = ctx.get(&args.validator); if rpc::is_validator(&client, &validator).await { - if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { + if args.rate < Dec::zero() || args.rate > Dec::one() { eprintln!("Invalid new commission rate, received {}", args.rate); if !args.tx.force { safe_exit(1) @@ -2932,7 +2932,7 @@ pub async fn submit_validator_commission_change( commission_rate, max_commission_change_per_epoch, }) => { - if (args.rate - commission_rate).abs() + if args.rate.abs_diff(&commission_rate) > max_commission_change_per_epoch { eprintln!( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e1b3665be2..ffb3db21fe 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -5,9 +5,7 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; use namada::ledger::parameters::storage as params_storage; use namada::ledger::pos::types::into_tm_voting_power; -use namada::ledger::pos::{ - decimal_mult_u128, namada_proof_of_stake, staking_token_address, -}; +use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage_api::token::credit_tokens; use namada::ledger::storage_api::{StorageRead, StorageWrite}; @@ -20,10 +18,10 @@ use namada::proof_of_stake::{ write_last_block_proposer_address, }; use namada::types::address::Address; +use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::token::{total_supply_key, Amount}; -use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; use super::*; @@ -616,14 +614,14 @@ where let epochs_per_year: u64 = self .read_storage_key(¶ms_storage::get_epochs_per_year_key()) .expect("Epochs per year should exist in storage"); - let pos_p_gain_nom: Decimal = self + let pos_p_gain_nom: Dec = self .read_storage_key(¶ms_storage::get_pos_gain_p_key()) .expect("PoS P-gain factor should exist in storage"); - let pos_d_gain_nom: Decimal = self + let pos_d_gain_nom: Dec = self .read_storage_key(¶ms_storage::get_pos_gain_d_key()) .expect("PoS D-gain factor should exist in storage"); - let pos_last_staked_ratio: Decimal = self + let pos_last_staked_ratio: Dec = self .read_storage_key(¶ms_storage::get_staked_ratio_key()) .expect("PoS staked ratio should exist in storage"); let pos_last_inflation_amount: u64 = self @@ -642,12 +640,12 @@ where // TODO: properly fetch these values (arbitrary for now) let masp_locked_supply: Amount = Amount::default(); - let masp_locked_ratio_target = Decimal::new(5, 1); - let masp_locked_ratio_last = Decimal::new(5, 1); - let masp_max_inflation_rate = Decimal::new(2, 1); - let masp_last_inflation_rate = Decimal::new(12, 2); - let masp_p_gain = Decimal::new(1, 1); - let masp_d_gain = Decimal::new(1, 1); + let masp_locked_ratio_target = Dec::new(5, 1); + let masp_locked_ratio_last = Dec::new(5, 1); + let masp_max_inflation_rate = Dec::new(2, 1); + let masp_last_inflation_rate = Dec::new(12, 2); + let masp_p_gain = Dec::new(1, 1); + let masp_d_gain = Dec::new(1, 1); // Run rewards PD controller let pos_controller = inflation::RewardsController { @@ -707,15 +705,14 @@ where // // TODO: think about changing the reward to Decimal let mut reward_tokens_remaining = inflation; - let mut new_rewards_products: HashMap = + let mut new_rewards_products: HashMap = HashMap::new(); for acc in rewards_accumulator_handle().iter(&self.wl_storage)? { let (address, value) = acc?; // Get reward token amount for this validator - let fractional_claim = - value / Decimal::from(num_blocks_in_last_epoch); - let reward = decimal_mult_u128(fractional_claim, inflation as u128); + let fractional_claim = value / Dec::from(num_blocks_in_last_epoch); + let reward = fractional_claim * inflation; // Get validator data at the last epoch let stake = read_validator_stake( @@ -724,25 +721,25 @@ where &address, last_epoch, )? - .map(|v| Decimal::try_from(v).unwrap_or_default()) + .map(Dec::from) .unwrap_or_default(); let last_rewards_product = validator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Decimal::ONE); + .unwrap_or(Dec::one()); let last_delegation_product = delegator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Decimal::ONE); + .unwrap_or(Dec::one()); let commission_rate = validator_commission_rate_handle(&address) .get(&self.wl_storage, last_epoch, ¶ms)? .expect("Should be able to find validator commission rate"); - let new_product = last_rewards_product - * (Decimal::ONE + Decimal::from(reward) / stake); + let new_product = + last_rewards_product * (Dec::one() + Dec::from(reward) / stake); let new_delegation_product = last_delegation_product - * (Decimal::ONE - + (Decimal::ONE - commission_rate) * Decimal::from(reward) + * (Dec::one() + + (Dec::one() - commission_rate) * Dec::from(reward) / stake); new_rewards_products .insert(address, (new_product, new_delegation_product)); @@ -768,8 +765,7 @@ where let staking_token = staking_token_address(&self.wl_storage); // Mint tokens to the PoS account for the last epoch's inflation - let pos_reward_tokens = - Amount::from_uint(inflation - reward_tokens_remaining, 0).unwrap(); + let pos_reward_tokens = inflation - reward_tokens_remaining; tracing::info!( "Minting tokens for PoS rewards distribution into the PoS \ account. Amount: {}.", @@ -782,7 +778,7 @@ where pos_reward_tokens, )?; - if reward_tokens_remaining > 0 { + if reward_tokens_remaining > token::Amount::zero() { let amount = Amount::from_uint(reward_tokens_remaining, 0).unwrap(); tracing::info!( "Minting tokens remaining from PoS rewards distribution into \ @@ -907,6 +903,7 @@ mod test_finalize_block { rewards_accumulator_handle, validator_consensus_key_handle, validator_rewards_products_handle, }; + use namada::types::dec::POS_DECIMAL_PRECISION; use namada::types::governance::ProposalVote; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; @@ -1509,18 +1506,18 @@ mod test_finalize_block { let rewards_prod_3 = validator_rewards_products_handle(&val3.address); let rewards_prod_4 = validator_rewards_products_handle(&val4.address); - let is_decimal_equal_enough = - |target: Decimal, to_compare: Decimal| -> bool { - // also return false if to_compare > target since this should - // never happen for the use cases - if to_compare < target { - let tolerance = Decimal::new(1, 9); - let res = Decimal::ONE - to_compare / target; - res < tolerance - } else { - to_compare == target - } - }; + let is_decimal_equal_enough = |target: Dec, to_compare: Dec| -> bool { + // also return false if to_compare > target since this should + // never happen for the use cases + if to_compare < target { + let tolerance = Dec::new(1, POS_DECIMAL_PRECISION) + .expect("Dec creation failed"); + let res = Dec::one() - to_compare / target; + res < tolerance + } else { + to_compare == target + } + }; // NOTE: Want to manually set the block proposer and the vote // information in a FinalizeBlock object. In non-abcipp mode, @@ -1553,7 +1550,7 @@ mod test_finalize_block { // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(Decimal::ONE, acc_sum)); + assert!(is_decimal_equal_enough(Dec::one(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert_eq!(acc.get(&val2.address), acc.get(&val3.address)); assert_eq!(acc.get(&val2.address), acc.get(&val4.address)); @@ -1572,7 +1569,7 @@ mod test_finalize_block { // should be the same as val1 now. Val3 and val4 should be equal as // well. let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(Decimal::TWO, acc_sum)); + assert!(is_decimal_equal_enough(Dec::two(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert_eq!(acc.get(&val1.address), acc.get(&val2.address)); assert_eq!(acc.get(&val3.address), acc.get(&val4.address)); @@ -1684,7 +1681,7 @@ mod test_finalize_block { assert!(rp3 > rp4); } - fn get_rewards_acc(storage: &S) -> HashMap + fn get_rewards_acc(storage: &S) -> HashMap where S: StorageRead, { @@ -1692,18 +1689,18 @@ mod test_finalize_block { .iter(storage) .unwrap() .map(|elem| elem.unwrap()) - .collect::>() + .collect::>() } - fn get_rewards_sum(storage: &S) -> Decimal + fn get_rewards_sum(storage: &S) -> Dec where S: StorageRead, { let acc = get_rewards_acc(storage); if acc.is_empty() { - Decimal::ZERO + Dec::zero() } else { - acc.iter().fold(Decimal::default(), |sum, elm| sum + *elm.1) + acc.iter().fold(Dec::zero(), |sum, elm| sum + *elm.1) } } diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs new file mode 100644 index 0000000000..0b7b0f20b1 --- /dev/null +++ b/core/src/types/dec.rs @@ -0,0 +1,271 @@ +//! A non-negative fixed precision decimal type for computation primarily in the +//! PoS module. +use core::fmt::{Debug, Formatter}; +use std::fmt::Display; +use std::ops::{Add, AddAssign, Div, Mul, Sub}; +use std::str::FromStr; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::types::token::{Amount, Change}; +use crate::types::uint::Uint; + +/// The number of Dec places for PoS rational calculations +pub const POS_DECIMAL_PRECISION: u8 = 6; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{error}")] + First { error: String }, +} + +/// A 256 bit number with [`POS_DECIMAL_PRECISION`] number of Dec places. +/// +/// To be precise, an instance X of this type should be interpeted as the Dec +/// X * 10 ^ (-[`POS_DECIMAL_PRECISION`]) +#[derive( + Clone, + Copy, + Default, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, +)] +pub struct Dec(pub Uint); + +impl std::ops::Deref for Dec { + type Target = Uint; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Dec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Dec { + /// Division with truncation (TDO: better description) + pub fn trunc_div(&self, rhs: &Self) -> Option { + self.0 + .fixed_precision_div(rhs, POS_DECIMAL_PRECISION) + .map(Self) + } + + /// The representation of 0 + pub fn zero() -> Self { + Self(Uint::zero()) + } + + /// The representation of 1 + pub fn one() -> Self { + Self(Uint::one()) + } + + /// The representation of 2 + pub fn two() -> Self { + Self(Uint::one() + Uint::one()) + } + + /// Create a new [`Dec`] using a mantissa and a scale. + pub fn new(mantissa: u64, scale: u8) -> Option { + if scale > POS_DECIMAL_PRECISION { + None + } else { + Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) + .checked_mul(Uint::from(mantissa)) + .map(Self) + } + } + + /// Get the non-negative difference between two [`Dec`]s. + pub fn abs_diff(&self, other: &Self) -> Self { + if self > other { + *self - *other + } else { + *other - *self + } + } +} + +// TODO: improve (actualyl do) error handling! +impl FromStr for Dec { + type Err = self::Error; + + fn from_str(s: &str) -> Result { + if s.starts_with('-') { + return Err(self::Error::First { + error: "Dec cannot be negative".to_string(), + }); + } + if let Some((large, mut small)) = s.split_once('.') { + let num_large = + u64::from_str(large).map_err(|_| self::Error::First { + error: "Error".to_string(), + })?; + let mut num_small = + u64::from_str(small).map_err(|_| self::Error::First { + error: "Error".to_string(), + })?; + + if num_small == 0u64 { + return Ok(Dec(Uint::from(num_large))); + } + + small = small.trim_end_matches('0'); + let mut num_dec_places = small.len(); + if num_dec_places > POS_DECIMAL_PRECISION as usize { + // truncate to the first `POS_DECIMAL_PRECISION` places + num_dec_places = POS_DECIMAL_PRECISION as usize; + small = &small[..POS_DECIMAL_PRECISION as usize]; + num_small = + u64::from_str(small).map_err(|_| self::Error::First { + error: "Error".to_string(), + })?; + } + if num_large == 0u64 { + return Ok(Dec::new(num_small, num_dec_places as u8) + .expect("Dec creation failed")); + } + let tot_num = format!("{}{}", num_large, num_small); + let tot_num = u64::from_str(tot_num.as_str()).map_err(|_| { + self::Error::First { + error: "Error".to_string(), + } + })?; + Ok(Dec::new(tot_num, num_dec_places as u8) + .expect("Dec creation failed")) + } else { + Err(self::Error::First { + error: "Error".to_string(), + }) + } + } +} + +impl From for Dec { + fn from(amt: Amount) -> Self { + Self(amt.into()) + } +} + +impl From for Dec { + fn from(num: u64) -> Self { + Self(Uint::from(num * 10u64.pow(POS_DECIMAL_PRECISION as u32))) + } +} + +impl Add for Dec { + type Output = Self; + + fn add(self, rhs: Dec) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Add for Dec { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + Uint::from(rhs)) + } +} + +impl AddAssign for Dec { + fn add_assign(&mut self, rhs: Dec) { + *self = *self + rhs; + } +} + +impl Sub for Dec { + type Output = Self; + + fn sub(self, rhs: Dec) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl Mul for Dec { + type Output = Uint; + + fn mul(self, rhs: Uint) -> Self::Output { + self.0 * rhs + } +} + +impl Mul for Dec { + type Output = Dec; + + fn mul(self, rhs: u128) -> Self::Output { + Self(self.0 * Uint::from(rhs)) + } +} + +impl Mul for Dec { + type Output = Amount; + + fn mul(self, rhs: Amount) -> Self::Output { + (rhs * self.0) / 10u64.pow(POS_DECIMAL_PRECISION as u32) + } +} + +impl Mul for Dec { + type Output = Change; + + fn mul(self, rhs: Change) -> Self::Output { + let tot = rhs * self.0; + let denom = Uint::from(10u64.pow(POS_DECIMAL_PRECISION as u32)); + tot / denom + } +} + +impl Mul for Dec { + type Output = Self; + + fn mul(self, rhs: Dec) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +impl Div for Dec { + type Output = Self; + + fn div(self, rhs: Dec) -> Self::Output { + Self(self.0 / rhs.0) + } +} + +impl Display for Dec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let string = self.0.to_string(); + f.write_str(&string) + } +} + +#[cfg(test)] +mod test_dec { + use super::*; + + /// Fill in tests later + #[test] + fn test_basic() { + assert_eq!( + Dec::one() + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), + Dec::new(16, 1).unwrap() + ); + } +} diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index b414efc89a..8bbd73eaf2 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,6 +2,7 @@ pub mod address; pub mod chain; +pub mod dec; pub mod governance; pub mod hash; pub mod ibc; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index fe16161a74..b28b2af399 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -14,9 +14,10 @@ use thiserror::Error; use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::StorageRead; use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::dec::Dec; use crate::types::storage; use crate::types::storage::{DbKeySeg, Key, KeySeg}; -use crate::types::uint::{self, I256, Uint}; +use crate::types::uint::{self, Uint, I256}; /// Amount in micro units. For different granularity another representation /// might be more appropriate. @@ -207,7 +208,7 @@ impl Amount { pub fn to_string_native(&self) -> String { DenominatedAmount { amount: *self, - denom:NATIVE_MAX_DECIMAL_PLACES.into(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), } .to_string_precise() } @@ -492,6 +493,12 @@ impl From for Amount { } } +impl From for Amount { + fn from(dec: Dec) -> Amount { + Amount { raw: dec.0 } + } +} + impl TryFrom for u128 { type Error = std::io::Error; @@ -522,7 +529,9 @@ impl Add for Amount { type Output = Self; fn add(self, rhs: u64) -> Self::Output { - Self { raw: self.raw + Uint::from(rhs)} + Self { + raw: self.raw + Uint::from(rhs), + } } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 7d8e587011..8bf80b032a 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -21,13 +21,13 @@ pub use decrypted::*; #[cfg(feature = "ferveo-tpke")] pub use encrypted::EncryptionKey; pub use protocol::UpdateDkgSessionKey; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub use wrapper::*; use crate::ledger::gas::VpsGas; use crate::types::address::Address; +use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::key::*; @@ -190,10 +190,10 @@ pub struct InitValidator { /// Serialization of the public session key used in the DKG pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, /// The initial commission rate charged for delegation rewards - pub commission_rate: Decimal, + pub commission_rate: Dec, /// The maximum change allowed per epoch to the commission rate. This is /// immutable once set here. - pub max_commission_rate_change: Decimal, + pub max_commission_rate_change: Dec, /// The VP code for validator account pub validator_vp_code_hash: Hash, } diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index 8119eb2310..d334047ffe 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -1,10 +1,10 @@ //! Types used for PoS system transactions use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use crate::types::address::Address; +use crate::types::dec::Dec; use crate::types::token; /// A bond is a validator's self-bond or a delegation from non-validator to a @@ -72,5 +72,5 @@ pub struct CommissionChange { /// Validator address pub validator: Address, /// The new commission rate - pub new_rate: Decimal, + pub new_rate: Dec, } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index cb88c96844..39d0839c71 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -2,7 +2,7 @@ //! An unsigned 256 integer type. Used for, among other things, //! the backing type of token amounts. use std::cmp::Ordering; -use std::ops::{Add, AddAssign, BitXor, Neg, Sub}; +use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; @@ -92,6 +92,11 @@ impl I256 { self.0.0[3].leading_zeros() > 0 } + /// Check if the amount is negative (less than zero) + pub fn is_negative(&self) -> bool { + !self.non_negative() + } + /// Get the absolute value pub fn abs(&self) -> Uint { if self.non_negative() { @@ -238,6 +243,29 @@ impl Sub for I256 { } } +impl Mul for I256 { + type Output = Self; + + fn mul(self, rhs: Uint) -> Self::Output { + let is_neg = self.is_negative(); + let prod = self.abs() * rhs; + if is_neg { -Self(prod) } else { Self(prod) } + } +} + +impl Div for I256 { + type Output = Self; + + fn div(self, rhs: Uint) -> Self::Output { + let is_neg = self.is_negative(); + let quot = self + .abs() + .fixed_precision_div(&rhs, 0u8) + .unwrap_or_default(); + if is_neg { -Self(quot) } else { Self(quot) } + } +} + impl From for I256 { fn from(val: i128) -> Self { if val < 0 { @@ -310,8 +338,7 @@ mod test_uint { /// value gives zero. #[test] fn test_max_signed_value() { - let signed = - I256::try_from(MAX_SIGNED_VALUE).expect("Test failed"); + let signed = I256::try_from(MAX_SIGNED_VALUE).expect("Test failed"); let one = I256::try_from(Uint::from(1u64)).expect("Test failed"); let overflow = signed + one; assert_eq!( diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 75e84cd87c..eed29cdfe1 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -39,12 +39,12 @@ use namada_core::ledger::storage_api::{ self, OptionExt, ResultExt, StorageRead, StorageWrite, }; use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::dec::Dec; use namada_core::types::key::{ common, tm_consensus_key_raw_hash, PublicKeyTmRawHash, }; pub use namada_core::types::storage::Epoch; use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount}; use once_cell::unsync::Lazy; use parameters::PosParams; use rewards::PosRewardsCalculator; @@ -53,13 +53,12 @@ use storage::{ get_validator_address_from_bond, into_tm_voting_power, is_bond_key, is_unbond_key, is_validator_slashes_key, last_block_proposer_key, num_consensus_validators_key, params_key, slashes_prefix, - unbonds_for_source_prefix, unbonds_prefix, - validator_address_raw_hash_key, validator_max_commission_rate_change_key, - BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, - ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, + unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, + validator_max_commission_rate_change_key, BondDetails, + BondsAndUnbondsDetail, BondsAndUnbondsDetails, ReverseOrdTokenAmount, + RewardsAccumulator, UnbondDetails, }; use thiserror::Error; -use namada_core::types::uint::Uint; use types::{ BelowCapacityValidatorSet, BelowCapacityValidatorSets, BondId, Bonds, CommissionRates, ConsensusValidator, ConsensusValidatorSet, @@ -70,8 +69,6 @@ use types::{ WeightedValidator, }; -use crate::types::Dec; - /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -168,9 +165,9 @@ pub enum SlashError { #[derive(Error, Debug)] pub enum CommissionRateChangeError { #[error("Unexpected negative commission rate {0} for validator {1}")] - NegativeRate(DenominatedAmount, Address), + NegativeRate(Dec, Address), #[error("Rate change of {0} is too large for validator {1}")] - RateChangeTooLarge(DenominatedAmount, Address), + RateChangeTooLarge(Dec, Address), #[error( "There is no maximum rate change written in storage for validator {0}" )] @@ -477,7 +474,7 @@ where pub fn read_validator_max_commission_rate_change( storage: &S, validator: &Address, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { @@ -489,7 +486,7 @@ where pub fn write_validator_max_commission_rate_change( storage: &mut S, validator: &Address, - change: DenominatedAmount, + change: Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1585,8 +1582,8 @@ pub fn become_validator( address: &Address, consensus_key: &common::PublicKey, current_epoch: Epoch, - commission_rate: DenominatedAmount, - max_commission_rate_change: DenominatedAmount, + commission_rate: Dec, + max_commission_rate_change: Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1692,14 +1689,7 @@ where .unwrap_or_default() { let slash_rate = slash_type.get_slash_rate(¶ms); - let to_slash = token::Amount::from_uint( - decimal_mult_u128( - slash_rate, - u128::try_from(amount).expect("Amount out of bounds"), - ), - 0, - ) - .expect("Amount out of bounds"); + let to_slash = slash_rate * amount; slashed += to_slash; } } @@ -1739,7 +1729,7 @@ where pub fn change_validator_commission_rate( storage: &mut S, validator: &Address, - new_rate: DenominatedAmount, + new_rate: Dec, current_epoch: Epoch, ) -> storage_api::Result<()> where @@ -1775,8 +1765,16 @@ where let rate_before_pipeline = commission_handle .get(storage, pipeline_epoch - 1, ¶ms)? .expect("Could not find a rate in given epoch"); - let change_from_prev = new_rate - rate_before_pipeline; - if change_from_prev.abs() > max_change.unwrap() { + + // TODO: change this back if we use `Dec` type with a signed integer + // let change_from_prev = new_rate - rate_before_pipeline; + // if change_from_prev.abs() > max_change.unwrap() { + let change_from_prev = if new_rate > rate_before_pipeline { + new_rate - rate_before_pipeline + } else { + rate_before_pipeline - new_rate + }; + if change_from_prev > max_change.unwrap() { return Err(CommissionRateChangeError::RateChangeTooLarge( change_from_prev, validator.clone(), @@ -1810,14 +1808,7 @@ where let current_stake = read_validator_stake(storage, params, validator, current_epoch)? .unwrap_or_default(); - let slashed_amount = Amount::from_uint( - decimal_mult_u128( - rate, - u128::try_from(current_stake).expect("Amount out of bounds"), - ), - 0, - ) - .expect("Amount out of bounds"); + let slashed_amount = rate * current_stake; let token_change = -token::Change::from(slashed_amount); // Update validator sets and deltas at the pipeline length @@ -1958,9 +1949,7 @@ where if slash_epoch > &bond_epoch { continue; } - let current_slashed = - mult_change_to_amount(slash_type.get_slash_rate(params), delta) - .change(); + let current_slashed = slash_type.get_slash_rate(params) * delta; let delta = token::Amount::from_change(delta - current_slashed); total += delta; if bond_epoch <= epoch { @@ -2024,17 +2013,11 @@ where .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, - u128::try_from(prev_validator_stake) - .expect("Amount out of bounds") - as u64, + prev_validator_stake, ) }); let cur_tm_voting_power = Lazy::new(|| { - into_tm_voting_power( - params.tm_votes_per_token, - u128::try_from(cur_stake).expect("Amount out of bounds") - as u64, - ) + into_tm_voting_power(params.tm_votes_per_token, cur_stake) }); // If it was in `Consensus` before and voting power has not @@ -2104,8 +2087,7 @@ where .unwrap_or_default(); let prev_tm_voting_power = into_tm_voting_power( params.tm_votes_per_token, - u128::try_from(prev_validator_stake) - .expect("Amount out of bounds") as u64, + prev_validator_stake, ); // If the validator previously had no voting power, it wasn't in @@ -2544,10 +2526,8 @@ fn make_bond_details( } return Some( acc.unwrap_or_default() - + mult_change_to_amount( - slash.r#type.get_slash_rate(params), - change, - ), + + slash.r#type.get_slash_rate(params) + * token::Amount::from_change(change), ); } None @@ -2585,10 +2565,7 @@ fn make_unbond_details( } return Some( acc.unwrap_or_default() - + mult_amount( - slash.r#type.get_slash_rate(params), - amount, - ), + + slash.r#type.get_slash_rate(params) * amount, ); } None @@ -2655,7 +2632,7 @@ where debug_assert_eq!( into_tm_voting_power( params.tm_votes_per_token, - u128::try_from(stake_from_deltas).unwrap() as u64, + stake_from_deltas, ), i64::try_from(validator_vp).unwrap_or_default(), ); @@ -2670,8 +2647,8 @@ where let rewards_calculator = PosRewardsCalculator { proposer_reward: params.block_proposer_reward, signer_reward: params.block_vote_reward, - signing_stake: u128::try_from(total_signing_stake).unwrap() as u64, - total_stake: u128::try_from(total_consensus_stake).unwrap() as u64, + signing_stake: total_signing_stake, + total_stake: total_consensus_stake, }; let coeffs = rewards_calculator .get_reward_coeffs() @@ -2688,9 +2665,9 @@ where // Compute the fractional block rewards for each consensus validator and // update the reward accumulators - let consensus_stake_unscaled: Dec = total_consensus_stake.into(); + let consensus_stake_unscaled: Dec = total_consensus_stake.into(); let signing_stake_unscaled: Dec = total_signing_stake.into(); - let mut values: HashMap= HashMap::new(); + let mut values: HashMap = HashMap::new(); for validator in consensus_validators.iter(storage)? { let ( NestedSubKey::Data { diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 9eb5b6de05..a5b75ebd80 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,11 +1,9 @@ //! Proof-of-Stake system parameters use borsh::{BorshDeserialize, BorshSerialize}; -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use namada_core::types::dec::Dec; +use namada_core::types::uint::Uint; use thiserror::Error; -use crate::types::Dec; /// Proof-of-Stake system parameters, set at genesis and can only be changed via /// governance @@ -60,7 +58,8 @@ impl Default for PosParams { // slash 0.1% duplicate_vote_min_slash_rate: Dec::new(1, 3).expect("Test failed"), // slash 0.1% - light_client_attack_min_slash_rate: Dec::new(1, 3).expect("Test failed"), + light_client_attack_min_slash_rate: Dec::new(1, 3) + .expect("Test failed"), } } } @@ -72,7 +71,7 @@ pub enum ValidationError { "Maximum total voting power is too large: got {0}, expected at most \ {MAX_TOTAL_VOTING_POWER}" )] - TotalVotingPowerTooLarge(u64), + TotalVotingPowerTooLarge(Uint), #[error("Votes per token cannot be greater than 1, got {0}")] VotesPerTokenGreaterThanOne(Dec), #[error("Pipeline length must be >= 2, got {0}")] @@ -118,24 +117,24 @@ impl PosParams { // TODO: decide if this is still a check we want to do (in its current // state with our latest voting power conventions, it will fail // always) - let max_total_voting_power = Decimal::from(self.max_validator_slots) - * self.tm_votes_per_token - * Decimal::from(TOKEN_MAX_AMOUNT); + let max_total_voting_power = *self.tm_votes_per_token + * Uint::from(TOKEN_MAX_AMOUNT) + * Uint::from(self.max_validator_slots); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power.to_u64().unwrap(), + max_total_voting_power, )) } } Err(_) => errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power.to_u64().unwrap(), + max_total_voting_power, )), } // Check that there is no more than 1 vote per token - if self.tm_votes_per_token > dec!(1.0) { + if self.tm_votes_per_token > Dec::one() { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.tm_votes_per_token, )) @@ -170,8 +169,8 @@ mod tests { /// Testing helpers #[cfg(any(test, feature = "testing"))] pub mod testing { + use namada_core::types::dec::Dec; use proptest::prelude::*; - use rust_decimal::Decimal; use super::*; @@ -189,7 +188,7 @@ pub mod testing { max_validator_slots, pipeline_len, unbonding_len, - tm_votes_per_token: Dec(Uint::from(tm_votes_per_token)) / Dec(Uint::from(10_000)), + tm_votes_per_token: Dec::new(tm_votes_per_token, 4).expect("Test failed"), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() @@ -197,10 +196,9 @@ pub mod testing { } } - /// Get an arbitrary rate - a Decimal value between 0 and 1 inclusive, with + /// Get an arbitrary rate - a Dec value between 0 and 1 inclusive, with /// some fixed precision - pub fn arb_rate() -> impl Strategy { - (0..=100_000_u64) - .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) + pub fn arb_rate() -> impl Strategy { + (0..=100_000_u64).prop_map(|num| Dec::new(num, 5).expect("Test failed")) } } diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index e189cbc1a7..acfca50673 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -1,9 +1,9 @@ //! PoS rewards distribution. -use thiserror::Error; -use namada_core::types::token::{Amount, DenominatedAmount}; +use namada_core::types::dec::Dec; +use namada_core::types::token::Amount; use namada_core::types::uint::Uint; -use crate::types::Dec; +use thiserror::Error; /// This is equal to 0.01. const MIN_PROPOSER_REWARD: Dec = Dec(Uint([10000u64, 0u64, 0u64, 0u64])); @@ -72,10 +72,10 @@ impl PosRewardsCalculator { } // Logic for determining the coefficients. - let proposer_coeff = Dec::from(proposer_reward - * (signing_stake - votes_needed)) - / Dec::from(total_stake) - + MIN_PROPOSER_REWARD; + let proposer_coeff = + Dec::from(proposer_reward * (signing_stake - votes_needed)) + / Dec::from(total_stake) + + MIN_PROPOSER_REWARD; let signer_coeff = signer_reward; let active_val_coeff = Dec::one() - proposer_coeff - signer_coeff; diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index e7a1ef4d75..63d2b84eb2 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -13,6 +13,7 @@ use namada_core::types::address::testing::{ address_from_simple_seed, arb_established_address, }; use namada_core::types::address::{Address, EstablishedAddressGen}; +use namada_core::types::dec::Dec; use namada_core::types::key::common::{PublicKey, SecretKey}; use namada_core::types::key::testing::{ arb_common_keypair, common_sk_from_simple_seed, @@ -21,15 +22,17 @@ use namada_core::types::storage::Epoch; use namada_core::types::{address, key, token}; use proptest::prelude::*; use proptest::test_runner::Config; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; use crate::parameters::testing::arb_pos_params; use crate::parameters::PosParams; -use crate::types::{into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, Dec}; +use crate::types::{ + into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, + ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, + UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, +}; use crate::{ become_validator, below_capacity_validator_set_handle, bond_handle, bond_tokens, bonds_and_unbonds, consensus_validator_set_handle, @@ -741,8 +744,8 @@ fn test_become_validator_aux( &new_validator, &consensus_key, current_epoch, - Decimal::new(5, 2), - Decimal::new(5, 2), + Dec::new(5, 2).expect("Dec creation failed"), + Dec::new(5, 2).expect("Dec creation failed"), ) .unwrap(); @@ -927,15 +930,17 @@ fn test_validator_sets() { address: val1.clone(), tokens: stake1, consensus_key: pk1.clone(), - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, GenesisValidator { address: val2.clone(), tokens: stake2, consensus_key: pk2.clone(), - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, ] .into_iter(), @@ -1472,7 +1477,7 @@ fn test_validator_sets_swap() { let params = PosParams { max_validator_slots: 2, // Set 0.1 votes per token - tm_votes_per_token: dec!(0.1), + tm_votes_per_token: Dec::new(1, 1).expect("Dec creation failed"), ..Default::default() }; let addr_seed = "seed"; @@ -1537,15 +1542,17 @@ fn test_validator_sets_swap() { address: val1, tokens: stake1, consensus_key: pk1, - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, GenesisValidator { address: val2.clone(), tokens: stake2, consensus_key: pk2, - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, ] .into_iter(), @@ -1570,20 +1577,8 @@ fn test_validator_sets_swap() { let stake3 = stake3 + bond3; assert!(stake2 < stake3); - assert_eq!( - into_tm_voting_power( - params.tm_votes_per_token, - u128::try_from(stake2).unwrap() as u64 - ), - 0 - ); - assert_eq!( - into_tm_voting_power( - params.tm_votes_per_token, - u128::try_from(stake3).unwrap() as u64 - ), - 0 - ); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake2), 0); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake3), 0); update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch) .unwrap(); @@ -1606,14 +1601,8 @@ fn test_validator_sets_swap() { let stake3 = stake3 + bonds; assert!(stake2 < stake3); assert_eq!( - into_tm_voting_power( - params.tm_votes_per_token, - u128::try_from(stake2).unwrap() as u64 - ), - into_tm_voting_power( - params.tm_votes_per_token, - u128::try_from(stake3).unwrap() as u64 - ) + into_tm_voting_power(params.tm_votes_per_token, stake2), + into_tm_voting_power(params.tm_votes_per_token, stake3) ); update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch) @@ -1705,7 +1694,8 @@ fn arb_genesis_validators( let consensus_key = consensus_sk.to_public(); let commission_rate = Dec::new(5, 2).expect("Test failed"); - let max_commission_rate_change = Dec::new(1, 3).expect("Test failed"); + let max_commission_rate_change = + Dec::new(1, 3).expect("Test failed"); GenesisValidator { address, tokens, diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 3662f4e382..879e99403c 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -6,6 +6,7 @@ use itertools::Itertools; use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::ledger::storage_api::{token, StorageRead}; use namada_core::types::address::{self, Address}; +use namada_core::types::dec::Dec; use namada_core::types::key; use namada_core::types::key::common::PublicKey; use namada_core::types::storage::Epoch; @@ -14,7 +15,6 @@ use proptest::prelude::*; use proptest::prop_state_machine; use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; use proptest::test_runner::Config; -use rust_decimal::Decimal; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; @@ -81,8 +81,8 @@ enum Transition { InitValidator { address: Address, consensus_key: PublicKey, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: Dec, + max_commission_rate_change: Dec, }, Bond { id: BondId, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 3d0ae8e489..6d1a0bdf3b 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,7 +5,7 @@ mod rev_order; use core::fmt::Debug; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; -use std::fmt::{Display, Formatter, Write}; +use std::fmt::Display; use std::hash::Hash; use std::ops::Sub; @@ -16,15 +16,18 @@ use namada_core::ledger::storage_api::collections::{ }; use namada_core::ledger::storage_api::{self, StorageRead}; use namada_core::types::address::Address; +use namada_core::types::dec::Dec; use namada_core::types::key::common; use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; -use namada_core::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_core::types::token::Amount; pub use rev_order::ReverseOrdTokenAmount; -use namada_core::types::uint::Uint; use crate::parameters::PosParams; +// TODO: replace `POS_MAX_DECIMAL_PLACES` with +// core::types::token::NATIVE_MAX_DECIMAL_PLACES?? + // const U64_MAX: u64 = u64::MAX; // TODO: add this to the spec @@ -466,137 +469,6 @@ impl Display for SlashType { // -------------------------------------------------------------------------------------------- -/// The numbrer of Dec places for PoS rational calculations -pub const POS_DECIMAL_PRECISION: u8 = 6; - -/// A 256 bit number with [`POS_DECIMAL_PRECISION`] number of Dec places. -/// -/// To be precise, an instance X of this type should be interpeted as the Dec -/// X * 10 ^ (-[`POS_DECIMAL_PRECISION`]) -#[derive( - Clone, - Copy, - Default, - BorshSerialize, - BorshDeserialize, - BorshSchema, - PartialEq, - Eq, - PartialOrd, - Ord, - Debug, - Hash, -)] -pub struct Dec(pub Uint); - -impl std::ops::Deref for Dec { - type Target = Uint; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for Dec { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Dec { - pub fn trunc_div(&self, rhs: &Self) -> Option { - self.0.fixed_precision_div(rhs, POS_DECIMAL_PRECISION) - .map(Self) - } - - pub fn zero() -> Self { - Self(Uint::zero()) - } - - pub fn one() -> Self { - Self(Uint::one()) - } - - pub fn new(matissa: u64, scale: u8) -> Option { - if scale > POS_DECIMAL_PRECISION { - None - } else { - Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) - .checked_mul(Uint::from(matissa)) - .map(Self) - } - } -} - -impl From for Dec { - fn from(amt: Amount) -> Self { - Self(amt.into()) - } -} - -impl std::ops::Add for Dec { - type Output = Self; - - fn add(self, rhs: Dec) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl std::ops::Add for Dec { - type Output = Self; - - fn add(self, rhs: u64) -> Self::Output { - Self(self.0 + Uint::from(rhs)) - } -} - -impl std::ops::Sub for Dec { - type Output = Self; - - fn sub(self, rhs: Dec) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl std::ops::Mul for Dec { - type Output = Dec; - - fn mul(self, rhs: u128) -> Self::Output { - Self(self.0 * Uint::from(rhs)) - } -} - -impl std::ops::Mul for Dec { - type Output = Amount; - - fn mul(self, rhs: Amount) -> Self::Output { - self.0 * rhs - } -} - -impl std::ops::Mul for Dec { - type Output = Self; - - fn mul(self, rhs: Dec) -> Self::Output { - Self(self.0 * rhs.0) - } -} - -impl std::ops::Div for Dec { - type Output = Self; - - fn div(self, rhs: Dec) -> Self::Output { - Self(self.0 / rhs.0) - } -} - -impl Display for Dec { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let string = self.0.to_string(); - f.write_str(&string) - } -} - // -------------------------------------------------------------------------------------------- // /// Multiply a value of type Dec with one of type u64 and then return the @@ -638,11 +510,9 @@ impl Display for Dec { /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens -pub fn into_tm_voting_power( - votes_per_token: Dec, - tokens: Amount, -) -> i64 { - let pow = votes_per_token * u128::try_from(tokens).expect("Voting power out of bounds"); +pub fn into_tm_voting_power(votes_per_token: Dec, tokens: Amount) -> i64 { + let pow = votes_per_token + * u128::try_from(tokens).expect("Voting power out of bounds"); i64::try_from(pow.0).expect("Invalid voting power") } diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index c935c0b019..11415ae96e 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -2,9 +2,7 @@ //! proof-of-stake, providing liquity to shielded asset pools, and public goods //! funding. -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use namada_core::types::dec::Dec; use crate::types::token; @@ -21,8 +19,8 @@ pub enum RewardsType { /// Holds the PD controller values that should be updated in storage #[allow(missing_docs)] pub struct ValsToUpdate { - pub locked_ratio: Decimal, - pub inflation: u64, + pub locked_ratio: Dec, + pub inflation: token::Amount, } /// PD controller used to dynamically adjust the rewards rates @@ -33,17 +31,17 @@ pub struct RewardsController { /// Total token supply pub total_tokens: token::Amount, /// PD target locked ratio - pub locked_ratio_target: Decimal, + pub locked_ratio_target: Dec, /// PD last locked ratio - pub locked_ratio_last: Decimal, + pub locked_ratio_last: Dec, /// Maximum reward rate - pub max_reward_rate: Decimal, + pub max_reward_rate: Dec, /// Last inflation amount pub last_inflation_amount: token::Amount, /// Nominal proportional gain - pub p_gain_nom: Decimal, + pub p_gain_nom: Dec, /// Nominal derivative gain - pub d_gain_nom: Decimal, + pub d_gain_nom: Dec, /// Number of epochs per year pub epochs_per_year: u64, } @@ -63,9 +61,9 @@ impl RewardsController { epochs_per_year, } = self; - let locked = locked_tokens.as_dec_unscaled(); - let total = total_tokens.as_dec_unscaled(); - let epochs_py: Decimal = (epochs_per_year).into(); + let locked = Dec::from(locked_tokens); + let total = Dec::from(total_tokens); + let epochs_py: Dec = epochs_per_year.into(); let locked_ratio = locked / total; let max_inflation = total * max_reward_rate / epochs_py; @@ -76,16 +74,15 @@ impl RewardsController { let delta_error = locked_ratio_last - locked_ratio; let control_val = p_gain * error - d_gain * delta_error; - let last_inflation_amount = - Decimal::try_from(last_inflation_amount).unwrap(); + let last_inflation_amount = Dec::from(last_inflation_amount); let inflation = if last_inflation_amount + control_val > max_inflation { max_inflation - } else if last_inflation_amount + control_val > dec!(0.0) { + } else if last_inflation_amount + control_val > Dec::zero() { last_inflation_amount + control_val } else { - dec!(0.0) + Dec::zero() }; - let inflation: u64 = inflation.to_u64().unwrap(); + let inflation = token::Amount::from(inflation); ValsToUpdate { locked_ratio, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index fc3c32233b..152112874b 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -5,13 +5,13 @@ pub mod vp; pub use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::address; +pub use namada_core::types::dec::Dec; pub use namada_core::types::key::common; pub use namada_core::types::token; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::storage::*; pub use namada_proof_of_stake::{staking_token_address, types}; -use rust_decimal::Decimal; pub use vp::PosVP; use crate::types::address::{Address, InternalAddress}; @@ -27,14 +27,13 @@ pub const SLASH_POOL_ADDRESS: Address = /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens pub fn into_tm_voting_power( - votes_per_token: Decimal, + votes_per_token: Dec, tokens: token::Amount, ) -> i64 { - let prod = decimal_mult_u128( - votes_per_token, - u128::try_from(tokens).expect("TODO(Tomas): handle overflow"), - ); - i64::try_from(prod).expect("Invalid validator voting power (i64)") + let tokens = tokens.change(); + let prod = votes_per_token * tokens; + let res = i128::try_from(prod).expect("Failed conversion to i128"); + i64::try_from(res).expect("Invalid validator voting power (i64)") } /// Initialize storage in the genesis block. diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 1b73329efe..ba1265025b 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -4,6 +4,6 @@ pub mod ibc; pub mod key; pub use namada_core::types::{ - address, chain, governance, hash, internal, masp, storage, time, token, - transaction, validity_predicate, + address, chain, dec, governance, hash, internal, masp, storage, time, + token, transaction, validity_predicate, }; diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 7359f528bf..e669fdefee 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -23,4 +23,3 @@ namada_vm_env = {path = "../vm_env", default-features = false} borsh = "0.9.0" sha2 = "0.10.1" thiserror = "1.0.30" -rust_decimal = "1.26.1" diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 22c00ca599..f002b4dc37 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -1,5 +1,6 @@ //! Proof of Stake system integration with functions for transactions +use namada_core::types::dec::Dec; use namada_core::types::transaction::InitValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; @@ -8,7 +9,6 @@ use namada_proof_of_stake::{ read_pos_params, unbond_tokens, withdraw_tokens, }; pub use namada_proof_of_stake::{parameters, types}; -use rust_decimal::Decimal; use super::*; @@ -55,7 +55,7 @@ impl Ctx { pub fn change_validator_commission_rate( &mut self, validator: &Address, - rate: &Decimal, + rate: &Dec, ) -> TxResult { let current_epoch = self.get_block_epoch()?; change_validator_commission_rate(self, validator, *rate, current_epoch) From b0419f5342d7b117d202053f554bacc8e261fec1 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Apr 2023 11:53:26 +0200 Subject: [PATCH 30/69] [fix]: Added tests for Dec, fixed division bug, cleaned up from_str method, added tests, removed some more Decimal types --- Cargo.lock | 1 + apps/src/lib/client/tx.rs | 2 +- apps/src/lib/client/utils.rs | 13 +-- apps/src/lib/config/genesis.rs | 58 +++++------ core/Cargo.toml | 1 + core/src/types/dec.rs | 169 +++++++++++++++++++++------------ proof_of_stake/src/types.rs | 4 - 7 files changed, 142 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bba1dde6d..448eb364e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3931,6 +3931,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 97e97094cf..743bb4c600 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1374,7 +1374,7 @@ fn convert_amount( .expect("invalid value for amount"); asset_type }) - .collect() + .collect::>() .try_into() .expect("This can't fail"); (asset_types, amount) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8e774ca572..9e901cfaf1 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -12,11 +12,11 @@ use flate2::write::GzEncoder; use flate2::Compression; use namada::types::address; use namada::types::chain::ChainId; +use namada::types::dec::Dec; use namada::types::key::*; use prost::bytes::Bytes; use rand::prelude::ThreadRng; use rand::thread_rng; -use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; @@ -897,16 +897,11 @@ pub fn init_genesis_validator( }: args::InitGenesisValidator, ) { // Validate the commission rate data - if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { - eprintln!( - "The validator commission rate must not exceed 1.0 or 100%, and \ - it must be 0 or positive" - ); + if commission_rate > Dec::one() { + eprintln!("The validator commission rate must not exceed 1.0 or 100%"); cli::safe_exit(1) } - if max_commission_rate_change > Decimal::ONE - || max_commission_rate_change < Decimal::ZERO - { + if max_commission_rate_change > Dec::one() { eprintln!( "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index aa0330b0a8..b4ef132ca4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -10,7 +10,7 @@ use derivative::Derivative; use namada::core::ledger::testnet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; -use namada::ledger::pos::{GenesisValidator, PosParams}; +use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -20,7 +20,6 @@ use namada::types::key::*; use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::Denomination; use namada::types::{storage, token}; -use rust_decimal::Decimal; /// Genesis configuration file format pub mod genesis_config { @@ -36,7 +35,7 @@ pub mod genesis_config { use namada::core::ledger::testnet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; - use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; use namada::types::chain::ProposalBytes; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -193,9 +192,9 @@ pub mod genesis_config { pub non_staked_balance: Option, /// Commission rate charged on rewards for delegators (bounded inside /// 0-1) - pub commission_rate: Option, + pub commission_rate: Option, /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Option, + pub max_commission_rate_change: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, // IP:port of the validator. (used in generation only) @@ -289,26 +288,26 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token. // XXX: u64 doesn't work with toml-rs! - pub tm_votes_per_token: Decimal, + pub tm_votes_per_token: Dec, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! - pub block_proposer_reward: Decimal, + pub block_proposer_reward: Dec, // Reward for voting on a block. // XXX: u64 doesn't work with toml-rs! - pub block_vote_reward: Decimal, + pub block_vote_reward: Dec, // Maximum staking APY // XXX: u64 doesn't work with toml-rs! - pub max_inflation_rate: Decimal, + pub max_inflation_rate: Dec, // Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: Decimal, + pub target_staked_ratio: Dec, // Portion of a validator's stake that should be slashed on a // duplicate vote. // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_min_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Dec, // Portion of a validator's stake that should be slashed on a // light client attack. // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_min_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Dec, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -340,21 +339,13 @@ pub mod genesis_config { commission_rate: config .commission_rate .and_then(|rate| { - if rate >= Decimal::ZERO && rate <= Decimal::ONE { - Some(rate) - } else { - None - } + if rate <= Dec::one() { Some(rate) } else { None } }) .expect("Commission rate must be between 0.0 and 1.0"), max_commission_rate_change: config .max_commission_rate_change .and_then(|rate| { - if rate >= Decimal::ZERO && rate <= Decimal::ONE { - Some(rate) - } else { - None - } + if rate <= Dec::one() { Some(rate) } else { None } }) .expect( "Max commission rate change must be between 0.0 and \ @@ -864,11 +855,11 @@ pub struct Parameters { /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d (read only) - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) - pub staked_ratio: Decimal, + pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) pub pos_inflation_amount: u64, /// Fixed Wrapper tx fees @@ -888,7 +879,6 @@ pub fn genesis(num_validators: u64) -> Genesis { use namada::types::address::{ self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, }; - use rust_decimal_macros::dec; use crate::wallet; @@ -911,8 +901,9 @@ pub fn genesis(num_validators: u64) -> Genesis { address, tokens: token::Amount::native_whole(200_000), consensus_key: consensus_keypair.ref_to(), - commission_rate: dec!(0.05), - max_commission_rate_change: dec!(0.01), + commission_rate: Dec::new(5, 2).expect("This can't fail"), + max_commission_rate_change: Dec::new(1, 2) + .expect("This can't fail"), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -939,8 +930,9 @@ pub fn genesis(num_validators: u64) -> Genesis { address, tokens: token::Amount::native_whole(200_000), consensus_key: consensus_keypair.ref_to(), - commission_rate: dec!(0.05), - max_commission_rate_change: dec!(0.01), + commission_rate: Dec::new(5, 2).expect("This can't fail"), + max_commission_rate_change: Dec::new(1, 2) + .expect("This can't fail"), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -966,9 +958,9 @@ pub fn genesis(num_validators: u64) -> Genesis { implicit_vp_sha256: Default::default(), epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ - pos_gain_p: dec!(0.1), - pos_gain_d: dec!(0.1), - staked_ratio: dec!(0.0), + pos_gain_p: Dec::new(1, 2).expect("This can't fail"), + pos_gain_d: Dec::new(1, 2).expect("This can't fail"), + staked_ratio: Dec::zero(), pos_inflation_amount: 0, wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; diff --git a/core/Cargo.toml b/core/Cargo.toml index 2849e8c9b2..5e97f59a41 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -68,6 +68,7 @@ chrono = {version = "0.4.22", default-features = false, features = ["clock", "st data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" +eyre = "0.6.8" ethabi = "18.0.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 0b7b0f20b1..4dfb00f72e 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -1,13 +1,12 @@ //! A non-negative fixed precision decimal type for computation primarily in the //! PoS module. -use core::fmt::{Debug, Formatter}; -use std::fmt::Display; +use std::fmt::{Debug, Display, Formatter}; use std::ops::{Add, AddAssign, Div, Mul, Sub}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::eyre; use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::types::token::{Amount, Change}; use crate::types::uint::Uint; @@ -15,12 +14,13 @@ use crate::types::uint::Uint; /// The number of Dec places for PoS rational calculations pub const POS_DECIMAL_PRECISION: u8 = 6; -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("{error}")] - First { error: String }, -} +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error [`Dec`] operations can return +pub struct Error(#[from] eyre::Error); + +/// Generic result type for fallible [`Dec`] operations +pub type Result = std::result::Result; /// A 256 bit number with [`POS_DECIMAL_PRECISION`] number of Dec places. /// @@ -59,7 +59,7 @@ impl std::ops::DerefMut for Dec { } impl Dec { - /// Division with truncation (TDO: better description) + /// Division with truncation (TODO: better description) pub fn trunc_div(&self, rhs: &Self) -> Option { self.0 .fixed_precision_div(rhs, POS_DECIMAL_PRECISION) @@ -102,58 +102,50 @@ impl Dec { } } -// TODO: improve (actualyl do) error handling! impl FromStr for Dec { - type Err = self::Error; + type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { if s.starts_with('-') { - return Err(self::Error::First { - error: "Dec cannot be negative".to_string(), - }); + return Err(eyre!("Dec cannot be negative").into()); } - if let Some((large, mut small)) = s.split_once('.') { - let num_large = - u64::from_str(large).map_err(|_| self::Error::First { - error: "Error".to_string(), - })?; - let mut num_small = - u64::from_str(small).map_err(|_| self::Error::First { - error: "Error".to_string(), - })?; - - if num_small == 0u64 { - return Ok(Dec(Uint::from(num_large))); - } - - small = small.trim_end_matches('0'); - let mut num_dec_places = small.len(); - if num_dec_places > POS_DECIMAL_PRECISION as usize { - // truncate to the first `POS_DECIMAL_PRECISION` places - num_dec_places = POS_DECIMAL_PRECISION as usize; - small = &small[..POS_DECIMAL_PRECISION as usize]; - num_small = - u64::from_str(small).map_err(|_| self::Error::First { - error: "Error".to_string(), - })?; - } - if num_large == 0u64 { - return Ok(Dec::new(num_small, num_dec_places as u8) - .expect("Dec creation failed")); - } - let tot_num = format!("{}{}", num_large, num_small); - let tot_num = u64::from_str(tot_num.as_str()).map_err(|_| { - self::Error::First { - error: "Error".to_string(), - } - })?; - Ok(Dec::new(tot_num, num_dec_places as u8) - .expect("Dec creation failed")) - } else { - Err(self::Error::First { - error: "Error".to_string(), - }) + + let (large, small) = s.split_once('.').unwrap_or((s, "0")); + let num_large = Uint::from_str_radix(large, 10).map_err(|e| { + eyre!("Could not parse {} as an integer: {}", large, e) + })?; + + // In theory we could allow this, but it is aesthetically offensive. + // Thus we don't. + if small.is_empty() { + return Err(eyre!( + "Failed to parse Dec from string as there were no numbers \ + following the decimal point." + ) + .into()); } + + let trimmed = small + .trim_end_matches('0') + .chars() + .take(POS_DECIMAL_PRECISION as usize) + .collect::(); + let decimal_part = if trimmed.is_empty() { + Uint::zero() + } else { + Uint::from_str_radix(&trimmed, 10).map_err(|e| { + eyre!("Could not parse .{} as decimals: {}", small, e) + })? * Uint::exp10(POS_DECIMAL_PRECISION as usize - trimmed.len()) + }; + let int_part = Uint::exp10(POS_DECIMAL_PRECISION as usize) + .checked_mul(num_large) + .ok_or_else(|| { + eyre!( + "The number {} is too large to fit in the Dec type.", + num_large + ) + })?; + Ok(Dec(int_part + decimal_part)) } } @@ -244,8 +236,15 @@ impl Mul for Dec { impl Div for Dec { type Output = Self; + /// Unchecked fixed precision division. + /// + /// # Panics: + /// + /// * Denominator is zero + /// * Scaling the left hand side by 10^([`POS_DECIMAL_PRECISION`]) + /// overflows 256 bits fn div(self, rhs: Dec) -> Self::Output { - Self(self.0 / rhs.0) + self.trunc_div(&rhs).unwrap() } } @@ -264,8 +263,60 @@ mod test_dec { #[test] fn test_basic() { assert_eq!( - Dec::one() + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), + Dec::new(1, 0).unwrap() + + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), Dec::new(16, 1).unwrap() ); } + + /// Test that parsing from string is correct. + #[test] + fn test_from_string() { + // Fewer than six decimal places and non-zero integer part + assert_eq!( + Dec::from_str("3.14").expect("Test failed"), + Dec::new(314, 2).expect("Test failed"), + ); + + // more than six decimal places and zero integer part + assert_eq!( + Dec::from_str("0.1234567").expect("Test failed"), + Dec::new(123456, 6).expect("Test failed"), + ); + + // No zero before the decimal + assert_eq!( + Dec::from_str(".333333").expect("Test failed"), + Dec::new(333333, 6).expect("Test failed"), + ); + + // No decimal places + assert_eq!( + Dec::from_str("50").expect("Test failed"), + Dec::new(50, 0).expect("Test failed"), + ); + + // Test zero representations + assert_eq!(Dec::from_str("0").expect("Test failed"), Dec::zero()); + assert_eq!(Dec::from_str("0.0").expect("Test failed"), Dec::zero()); + assert_eq!(Dec::from_str(".0").expect("Test failed"), Dec::zero()); + + // Error conditions + + // Test that a decimal point must be followed by numbers + assert!(Dec::from_str("0.").is_err()); + // Test that multiple decimal points get caught + assert!(Dec::from_str("1.2.3").is_err()); + // Test that negative numbers are rejected + assert!(Dec::from_str("-1").is_err()); + // Test that non-numerics are caught + assert!(Dec::from_str("DEADBEEF.12").is_err()); + assert!(Dec::from_str("23.DEADBEEF").is_err()); + // Test that we catch strings overflowing 256 bits + let mut yuge = String::from("1"); + for _ in 0..80 { + yuge.push('0'); + } + assert!(Dec::from_str(&yuge).is_err()); + } } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 6d1a0bdf3b..a441c9312e 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -467,10 +467,6 @@ impl Display for SlashType { } } -// -------------------------------------------------------------------------------------------- - -// -------------------------------------------------------------------------------------------- - // /// Multiply a value of type Dec with one of type u64 and then return the // /// truncated u64 // pub fn decimal_mult_u128(dec: Dec, int: u128) -> u128 { From bb93c858221875ee582a8a5f2abc3904d57fedbc Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Apr 2023 15:23:01 +0200 Subject: [PATCH 31/69] [chore]: Added more tests and fixes --- core/src/types/dec.rs | 11 +++++++++++ core/src/types/token.rs | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 4dfb00f72e..59074e7506 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -267,6 +267,17 @@ mod test_dec { + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), Dec::new(16, 1).unwrap() ); + + // Fixed precision division is more thoroughly tested for the `Uint` type. These + // are sanity checks that the precision is correct. + assert_eq!( + Dec::new(1, 6).expect("Test failed") / Dec::new(1, 0).expect("Test failed"), + Dec::one(), + ); + assert_eq!( + Dec::new(1, 6).expect("Test failed") / (Dec::new(1, 0).expect("Test failed") + Dec::one()), + Dec::zero(), + ); } /// Test that parsing from string is correct. diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b28b2af399..1608e6de28 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -233,17 +233,6 @@ impl Amount { pub fn from_string_precise(string: &str) -> Result { DenominatedAmount::from_str(string).map(|den| den.amount) } - - /// Convert the amount to [`Decimal`] ignoring its scale (i.e. as an integer - /// in micro units). - /// - /// # Panics - /// - /// Panics if the stored amount overflows either a u128 or the [`Decimal`] - /// type. - pub fn as_dec_unscaled(&self) -> Decimal { - Into::::into(u128::try_from(self.raw).unwrap()) - } } /// Given a number represented as `M*B^D`, then From 1968660466809d369005cc3974fc5c3798eba5cf Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Apr 2023 13:06:39 -0400 Subject: [PATCH 32/69] WIP more `Dec` integration and testing (compiling!) --- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/config/genesis.rs | 13 ++- .../lib/node/ledger/shell/finalize_block.rs | 28 +++--- apps/src/lib/node/ledger/shell/init_chain.rs | 5 +- core/src/ledger/parameters/mod.rs | 24 ++--- core/src/ledger/storage/mod.rs | 10 +- core/src/types/dec.rs | 94 ++++++++++++++++--- core/src/types/token.rs | 13 ++- proof_of_stake/src/lib.rs | 4 +- proof_of_stake/src/tests.rs | 10 +- proof_of_stake/src/types.rs | 4 +- shared/src/types/mod.rs | 2 +- tests/src/native_vp/pos.rs | 10 +- wasm/Cargo.lock | 2 +- wasm/wasm_source/src/tx_bond.rs | 6 +- .../src/tx_change_validator_commission.rs | 47 +++++----- wasm/wasm_source/src/tx_unbond.rs | 5 +- wasm/wasm_source/src/tx_withdraw.rs | 5 +- wasm/wasm_source/src/vp_implicit.rs | 9 +- wasm/wasm_source/src/vp_user.rs | 9 +- wasm/wasm_source/src/vp_validator.rs | 14 +-- wasm_for_tests/wasm_source/Cargo.lock | 2 +- 22 files changed, 199 insertions(+), 119 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index dc8c406810..f574661cf1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1176,7 +1176,7 @@ pub async fn query_shielded_balance( read_tokens .entry(addr.clone()) .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) - .or_insert(vec![sub_prefix.clone()]); + .or_insert_with(|| vec![sub_prefix.clone()]); // Only assets with the current timestamp count println!( "Shielded Token {}{}:", diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b4ef132ca4..38762bbd76 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -43,7 +43,6 @@ pub mod genesis_config { use namada::types::time::Rfc3339String; use namada::types::token::Denomination; use namada::types::{storage, token}; - use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -267,9 +266,9 @@ pub mod genesis_config { /// Expected number of epochs per year pub epochs_per_year: u64, /// PoS gain p - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, #[cfg(not(feature = "mainnet"))] /// Fix wrapper tx fees pub wrapper_tx_fees: Option, @@ -612,8 +611,8 @@ pub mod genesis_config { epochs_per_year: parameters.epochs_per_year, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, - staked_ratio: Decimal::ZERO, - pos_inflation_amount: 0, + staked_ratio: Dec::zero(), + pos_inflation_amount: token::Amount::zero(), wrapper_tx_fees: parameters.wrapper_tx_fees, }; @@ -861,7 +860,7 @@ pub struct Parameters { /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) - pub pos_inflation_amount: u64, + pub pos_inflation_amount: token::Amount, /// Fixed Wrapper tx fees #[cfg(not(feature = "mainnet"))] pub wrapper_tx_fees: Option, @@ -961,7 +960,7 @@ pub fn genesis(num_validators: u64) -> Genesis { pos_gain_p: Dec::new(1, 2).expect("This can't fail"), pos_gain_d: Dec::new(1, 2).expect("This can't fail"), staked_ratio: Dec::zero(), - pos_inflation_amount: 0, + pos_inflation_amount: token::Amount::zero(), wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index ffb3db21fe..242ac3d619 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -640,12 +640,12 @@ where // TODO: properly fetch these values (arbitrary for now) let masp_locked_supply: Amount = Amount::default(); - let masp_locked_ratio_target = Dec::new(5, 1); - let masp_locked_ratio_last = Dec::new(5, 1); - let masp_max_inflation_rate = Dec::new(2, 1); - let masp_last_inflation_rate = Dec::new(12, 2); - let masp_p_gain = Dec::new(1, 1); - let masp_d_gain = Dec::new(1, 1); + let masp_locked_ratio_target = Dec::new(5, 1).expect("Cannot fail"); + let masp_locked_ratio_last = Dec::new(5, 1).expect("Cannot fail"); + let masp_max_inflation_rate = Dec::new(2, 1).expect("Cannot fail"); + let masp_last_inflation_rate = Dec::new(12, 2).expect("Cannot fail"); + let masp_p_gain = Dec::new(1, 1).expect("Cannot fail"); + let masp_d_gain = Dec::new(1, 1).expect("Cannot fail"); // Run rewards PD controller let pos_controller = inflation::RewardsController { @@ -669,11 +669,9 @@ where locked_ratio_target: masp_locked_ratio_target, locked_ratio_last: masp_locked_ratio_last, max_reward_rate: masp_max_inflation_rate, - last_inflation_amount: token::Amount::from_decimal( + last_inflation_amount: token::Amount::from( masp_last_inflation_rate, - token::NATIVE_MAX_DECIMAL_PLACES, - ) - .expect("Amount out of bounds"), + ), p_gain_nom: masp_p_gain, d_gain_nom: masp_d_gain, epochs_per_year, @@ -726,11 +724,11 @@ where let last_rewards_product = validator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Dec::one()); + .unwrap_or_else(Dec::one); let last_delegation_product = delegator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Dec::one()); + .unwrap_or_else(Dec::one); let commission_rate = validator_commission_rate_handle(&address) .get(&self.wl_storage, last_epoch, ¶ms)? .expect("Should be able to find validator commission rate"); @@ -743,7 +741,7 @@ where / stake); new_rewards_products .insert(address, (new_product, new_delegation_product)); - reward_tokens_remaining -= reward as u64; + reward_tokens_remaining -= reward; } for ( address, @@ -912,8 +910,8 @@ mod test_finalize_block { InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::uint::Uint; use namada_test_utils::TestWasms; - use rust_decimal_macros::dec; use test_log::test; use super::*; @@ -1626,7 +1624,7 @@ mod test_finalize_block { assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(dec!(3), acc_sum)); + assert!(is_decimal_equal_enough(Dec(Uint::from(3)), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert!( acc.get(&val1.address).cloned().unwrap() diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 1bd9ff9e6d..3eb891b6d6 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -10,9 +10,9 @@ use namada::ledger::storage_api::token::{ credit_tokens, read_balance, read_total_supply, write_denom, }; use namada::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; +use namada::types::dec::Dec; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; -use rust_decimal::Decimal; use super::*; use crate::facade::tendermint_proto::abci; @@ -407,8 +407,7 @@ where // Set the ratio of staked to total NAM tokens in the parameters storage parameters::update_staked_ratio_parameter( &mut self.wl_storage, - &(Decimal::try_from(total_staked_nam).unwrap() - / Decimal::try_from(total_nam).unwrap()), + &(Dec::from(total_staked_nam) / Dec::from(total_nam)), ) .expect("unable to set staked ratio of NAM in storage"); diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index d835e1878f..ab72527522 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -2,7 +2,6 @@ pub mod storage; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::Decimal; use thiserror::Error; use super::storage::types; @@ -10,6 +9,7 @@ use super::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::chain::ProposalBytes; +use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::time::DurationSecs; use crate::types::token; @@ -45,13 +45,13 @@ pub struct Parameters { /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d (read only) - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) - pub staked_ratio: Decimal, + pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) - pub pos_inflation_amount: u64, + pub pos_inflation_amount: token::Amount, #[cfg(not(feature = "mainnet"))] /// Faucet account for free token withdrawal pub faucet_account: Option
, @@ -276,7 +276,7 @@ where /// cost. pub fn update_pos_gain_p_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -289,7 +289,7 @@ where /// cost. pub fn update_pos_gain_d_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -302,7 +302,7 @@ where /// gas cost. pub fn update_staked_ratio_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -435,28 +435,28 @@ where // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); let value = storage.read(&pos_gain_p_key)?; - let pos_gain_p: Decimal = value + let pos_gain_p = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read PoS gain D let pos_gain_d_key = storage::get_pos_gain_d_key(); let value = storage.read(&pos_gain_d_key)?; - let pos_gain_d: Decimal = value + let pos_gain_d = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read staked ratio let staked_ratio_key = storage::get_staked_ratio_key(); let value = storage.read(&staked_ratio_key)?; - let staked_ratio: Decimal = value + let staked_ratio = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read PoS inflation rate let pos_inflation_key = storage::get_pos_inflation_amount_key(); let value = storage.read(&pos_inflation_key)?; - let pos_inflation_amount: u64 = value + let pos_inflation_amount = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 69c159b1e1..0560844d6a 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1003,11 +1003,11 @@ mod tests { use chrono::{TimeZone, Utc}; use proptest::prelude::*; use proptest::test_runner::Config; - use rust_decimal_macros::dec; use super::testing::*; use super::*; use crate::ledger::parameters::{self, Parameters}; + use crate::types::dec::Dec; use crate::types::time::{self, Duration}; prop_compose! { @@ -1086,10 +1086,10 @@ mod tests { tx_whitelist: vec![], implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, - pos_gain_p: dec!(0.1), - pos_gain_d: dec!(0.1), - staked_ratio: dec!(0.1), - pos_inflation_amount: 0, + pos_gain_p: Dec::new(1,1).expect("Cannot fail"), + pos_gain_d: Dec::new(1,1).expect("Cannot fail"), + staked_ratio: Dec::new(1,1).expect("Cannot fail"), + pos_inflation_amount: token::Amount::zero(), #[cfg(not(feature = "mainnet"))] faucet_account: None, #[cfg(not(feature = "mainnet"))] diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 59074e7506..493d5c3321 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -73,12 +73,15 @@ impl Dec { /// The representation of 1 pub fn one() -> Self { - Self(Uint::one()) + Self(Uint::one() * Uint::exp10(POS_DECIMAL_PRECISION as usize)) } /// The representation of 2 pub fn two() -> Self { - Self(Uint::one() + Uint::one()) + Self( + (Uint::one() + Uint::one()) + * Uint::exp10(POS_DECIMAL_PRECISION as usize), + ) } /// Create a new [`Dec`] using a mantissa and a scale. @@ -100,6 +103,11 @@ impl Dec { *other - *self } } + + /// Convert the Dec type into a Uint with truncation + pub fn to_uint(&self) -> Uint { + self.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize) + } } impl FromStr for Dec { @@ -161,6 +169,19 @@ impl From for Dec { } } +// impl TryFrom for u64 { +// type Error = Error; + +// fn try_from(value: Dec) -> std::result::Result { +// let int = value.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize); +// if int > Uint::from(u64::MAX) { +// Err(eyre!("Dec value is too large to fit in a u64").into()) +// } else { +// Ok(int.into()) +// } +// } +// } + impl Add for Dec { type Output = Self; @@ -250,7 +271,18 @@ impl Div for Dec { impl Display for Dec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let string = self.0.to_string(); + let mut string = self.0.to_string(); + if string.len() > POS_DECIMAL_PRECISION as usize { + let idx = string.len() - POS_DECIMAL_PRECISION as usize; + string.insert(idx, '.'); + } else { + let mut str_pre = "0.".to_string(); + for _ in 0..(POS_DECIMAL_PRECISION as usize - string.len()) { + str_pre.push('0'); + } + str_pre.push_str(string.as_str()); + string = str_pre; + }; f.write_str(&string) } } @@ -258,31 +290,71 @@ impl Display for Dec { #[cfg(test)] mod test_dec { use super::*; + use crate::types::token::{Amount, Change}; /// Fill in tests later #[test] - fn test_basic() { + fn test_dec_basics() { assert_eq!( - Dec::new(1, 0).unwrap() - + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), + Dec::one() + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), Dec::new(16, 1).unwrap() ); + assert_eq!(Dec::new(1, 0).expect("Test failed"), Dec::one()); + assert_eq!(Dec::new(2, 0).expect("Test failed"), Dec::two()); + assert_eq!( + Dec(Uint::from(1653)), + Dec::new(1653, POS_DECIMAL_PRECISION).expect("Test failed") + ); + assert_eq!( + Dec::new(123456789, 4).expect("Test failed").to_uint(), + Uint::from(12345) + ); + assert_eq!( + Dec::new(123, 4).expect("Test failed").to_uint(), + Uint::zero() + ); + assert_eq!( + Dec::from_str("4876.3855").expect("Test failed").to_uint(), + Uint::from(4876) + ); - // Fixed precision division is more thoroughly tested for the `Uint` type. These - // are sanity checks that the precision is correct. + // Fixed precision division is more thoroughly tested for the `Uint` + // type. These are sanity checks that the precision is correct. assert_eq!( - Dec::new(1, 6).expect("Test failed") / Dec::new(1, 0).expect("Test failed"), + Dec::new(1, 6).expect("Test failed") + / Dec::new(1, 6).expect("Test failed"), Dec::one(), ); assert_eq!( - Dec::new(1, 6).expect("Test failed") / (Dec::new(1, 0).expect("Test failed") + Dec::one()), + Dec::new(1, 6).expect("Test failed") + / (Dec::new(1, 0).expect("Test failed") + Dec::one()), + Dec::zero(), + ); + assert_eq!( + Dec::new(1, 6).expect("Test failed") / Dec::two(), Dec::zero(), ); } + /// Test the `Dec` and `Amount` interplay + #[test] + fn test_dec_and_amount() { + let amt = Amount::from(1018u64); + let dec = Dec::from_str("2.76").unwrap(); + + debug_assert_eq!( + Dec::from(amt), + Dec::new(1018, 0).expect("Test failed") + ); + debug_assert_eq!(dec * amt, Amount::from(2809u64)); + + let chg = -amt.change(); + debug_assert_eq!(dec * chg, Change::from(-2809i64)); + } + /// Test that parsing from string is correct. #[test] - fn test_from_string() { + fn test_dec_from_string() { // Fewer than six decimal places and non-zero integer part assert_eq!( Dec::from_str("3.14").expect("Test failed"), diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 1608e6de28..734ca998c0 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -11,6 +11,7 @@ use rust_decimal::prelude::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; +use super::dec::POS_DECIMAL_PRECISION; use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::StorageRead; use crate::types::address::{masp, Address, DecodeError as AddressError}; @@ -482,9 +483,19 @@ impl From for Amount { } } +impl From for Amount { + fn from(val: u64) -> Amount { + Amount { + raw: Uint::from(val), + } + } +} + impl From for Amount { fn from(dec: Dec) -> Amount { - Amount { raw: dec.0 } + Amount { + raw: dec.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize), + } } } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index eed29cdfe1..e3ea5a2077 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2052,9 +2052,7 @@ where ); Some(ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key, - bonded_stake: u128::try_from(cur_stake) - .expect("Amount out of bounds") - as u64, + bonded_stake: cur_stake, })) }); let cur_below_capacity_validators = diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 63d2b84eb2..cfc4d244a0 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -1091,7 +1091,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: u128::try_from(stake3).unwrap() as u64, + bonded_stake: stake3, }) ); @@ -1146,7 +1146,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk5, - bonded_stake: u128::try_from(stake5).unwrap() as u64, + bonded_stake: stake5, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk2)); @@ -1342,7 +1342,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk4.clone(), - bonded_stake: u128::try_from(stake4).unwrap() as u64, + bonded_stake: stake4, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk1)); @@ -1458,7 +1458,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk6, - bonded_stake: u128::try_from(stake6).unwrap() as u64, + bonded_stake: stake6, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); @@ -1640,7 +1640,7 @@ fn test_validator_sets_swap() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: u128::try_from(stake3).unwrap() as u64, + bonded_stake: stake3, }) ); } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index a441c9312e..eb87cb22f1 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -200,7 +200,7 @@ pub struct ConsensusValidator { /// A public key used for signing validator's consensus actions pub consensus_key: common::PublicKey, /// Total bonded stake of the validator - pub bonded_stake: u64, + pub bonded_stake: token::Amount, } /// ID of a bond and/or an unbond. @@ -509,7 +509,7 @@ impl Display for SlashType { pub fn into_tm_voting_power(votes_per_token: Dec, tokens: Amount) -> i64 { let pow = votes_per_token * u128::try_from(tokens).expect("Voting power out of bounds"); - i64::try_from(pow.0).expect("Invalid voting power") + i64::try_from(pow.to_uint()).expect("Invalid voting power") } #[cfg(test)] diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index ba1265025b..4d50ee4ac7 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -5,5 +5,5 @@ pub mod key; pub use namada_core::types::{ address, chain, dec, governance, hash, internal, masp, storage, time, - token, transaction, validity_predicate, + token, transaction, uint, validity_predicate, }; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 5a59ce0494..3e7fff1cb7 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -588,10 +588,10 @@ pub mod testing { use namada::types::key::RefTo; use namada::types::storage::Epoch; use namada::types::{address, key, token}; + use namada_core::types::dec::Dec; use namada_core::types::token::{Amount, Change}; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; - use rust_decimal::Decimal; use crate::tx::{self, tx_host_env}; @@ -612,8 +612,8 @@ pub mod testing { InitValidator { address: Address, consensus_key: PublicKey, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: Dec, + max_commission_rate_change: Dec, }, Bond { amount: token::Amount, @@ -700,11 +700,11 @@ pub mod testing { }, ValidatorCommissionRate { address: Address, - rate: Decimal, + rate: Dec, }, ValidatorMaxCommissionRateChange { address: Address, - change: Decimal, + change: Dec, }, } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 200bba3eab..73cf3f057b 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2657,6 +2657,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "ibc", "ibc-proto", @@ -2765,7 +2766,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.10.6", "thiserror", ] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index a4c459c873..c4d4602985 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -26,6 +26,7 @@ mod tests { }; use namada::proto::Tx; use namada::types::chain::ChainId; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -41,7 +42,6 @@ mod tests { use namada_tx_prelude::token; use namada_vp_prelude::proof_of_stake::WeightedValidator; use proptest::prelude::*; - use rust_decimal; use super::*; @@ -73,8 +73,8 @@ mod tests { let is_delegation = matches!(&bond.source, Some(source) if *source != bond.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 2ab0e25f11..e2a41bc69e 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -12,7 +12,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { validator, new_rate, } = transaction::pos::CommissionChange::try_from_slice(&data[..]) - .wrap_err("failed to decode Decimal value")?; + .wrap_err("failed to decode Dec value")?; ctx.change_validator_commission_rate(&validator, &new_rate) } @@ -24,6 +24,7 @@ mod tests { use namada::proof_of_stake::validator_commission_rate_handle; use namada::proto::Tx; use namada::types::chain::ChainId; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -36,8 +37,6 @@ mod tests { use namada_tx_prelude::token; use namada_vp_prelude::proof_of_stake::GenesisValidator; use proptest::prelude::*; - use rust_decimal::prelude::ToPrimitive; - use rust_decimal::Decimal; use super::*; @@ -61,8 +60,8 @@ mod tests { fn test_tx_change_validator_commission_aux( commission_change: transaction::pos::CommissionChange, - initial_rate: Decimal, - max_change: Decimal, + initial_rate: Dec, + max_change: Dec, key: key::common::SecretKey, pos_params: PosParams, ) -> TxResult { @@ -87,7 +86,7 @@ mod tests { let commission_rate_handle = validator_commission_rate_handle(&commission_change.validator); - let mut commission_rates_pre = Vec::>::new(); + let mut commission_rates_pre = Vec::>::new(); for epoch in Epoch::default().iter_range(pos_params.unbonding_len + 1) { commission_rates_pre.push(commission_rate_handle.get( ctx(), @@ -152,20 +151,20 @@ mod tests { Ok(()) } - fn arb_rate(min: Decimal, max: Decimal) -> impl Strategy { - let int_min: u64 = (min * Decimal::from(100_000_u64)) - .to_u64() - .unwrap_or_default(); - let int_max: u64 = (max * Decimal::from(100_000_u64)).to_u64().unwrap(); - (int_min..=int_max) - .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) + fn arb_rate(min: Dec, max: Dec) -> impl Strategy { + let scale = Dec::new(100_000, 0).expect("Test failed"); + let int_min = (min * scale).to_uint(); + let int_min = u64::try_from(int_min).unwrap(); + let int_max = (max * scale).to_uint(); + let int_max = u64::try_from(int_max).unwrap(); + (int_min..=int_max).prop_map(move |num| Dec::from(num) / scale) } fn arb_new_rate( - min: Decimal, - max: Decimal, - rate_pre: Decimal, - ) -> impl Strategy { + min: Dec, + max: Dec, + rate_pre: Dec, + ) -> impl Strategy { arb_rate(min, max).prop_filter( "New rate must not be equal to the previous epoch's rate", move |v| v != &rate_pre, @@ -173,11 +172,11 @@ mod tests { } fn arb_commission_change( - rate_pre: Decimal, - max_change: Decimal, + rate_pre: Dec, + max_change: Dec, ) -> impl Strategy { - let min = cmp::max(rate_pre - max_change, Decimal::ZERO); - let max = cmp::min(rate_pre + max_change, Decimal::ONE); + let min = cmp::max(rate_pre - max_change, Dec::zero()); + let max = cmp::min(rate_pre + max_change, Dec::one()); (arb_established_address(), arb_new_rate(min, max, rate_pre)).prop_map( |(validator, new_rate)| transaction::pos::CommissionChange { validator: Address::Established(validator), @@ -187,10 +186,10 @@ mod tests { } fn arb_commission_info() - -> impl Strategy + -> impl Strategy { - let min = Decimal::ZERO; - let max = Decimal::ONE; + let min = Dec::zero(); + let max = Dec::one(); (arb_rate(min, max), arb_rate(min, max)).prop_flat_map( |(rate, change)| { ( diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 82fb25ff6f..9d3b467098 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -26,6 +26,7 @@ mod tests { }; use namada::proto::Tx; use namada::types::chain::ChainId; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -70,8 +71,8 @@ mod tests { &unbond.source, Some(source) if *source != unbond.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index f6386f104c..242ce0fcf1 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -25,6 +25,7 @@ mod tests { use namada::proof_of_stake::unbond_handle; use namada::proto::Tx; use namada::types::chain::ChainId; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -73,8 +74,8 @@ mod tests { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 985f4f7a80..3f1ea331f2 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -225,6 +225,7 @@ fn validate_tx( mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_test_utils::TestWasms; use namada_tests::log::test; @@ -416,8 +417,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -487,8 +488,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 812bbd7bee..1cbb2b4d94 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -213,6 +213,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -435,8 +436,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -507,8 +508,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 17cbb5e51f..24169fb17d 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -220,6 +220,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -231,7 +232,6 @@ mod tests { use namada_tx_prelude::{StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; - use rust_decimal::Decimal; use storage::testing::arb_account_storage_key_no_vp; use super::*; @@ -442,8 +442,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -494,7 +494,7 @@ mod tests { tx::ctx() .change_validator_commission_rate( &validator, - &Decimal::new(6, 2), + &Dec::new(6, 2).unwrap(), ) .unwrap(); }); @@ -520,8 +520,8 @@ mod tests { let validator = address::testing::established_address_3(); let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -574,7 +574,7 @@ mod tests { tx::ctx() .change_validator_commission_rate( &validator, - &Decimal::new(6, 2), + &Dec::new(6, 2).unwrap(), ) .unwrap(); }); diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 6918cb3513..f1b38f02a6 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2657,6 +2657,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "ibc", "ibc-proto", @@ -2765,7 +2766,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.10.6", "thiserror", ] From 7fbd9c68d3ab556d520b5d15cd6d57dfb4646a7a Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Apr 2023 17:07:39 -0400 Subject: [PATCH 33/69] WIP fixing pos unit tests --- apps/src/lib/config/genesis.rs | 4 ++-- .../lib/node/ledger/shell/finalize_block.rs | 24 ++++++++----------- core/src/types/dec.rs | 15 ++++++++++-- core/src/types/token.rs | 5 ++++ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 38762bbd76..4136f5c665 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -957,8 +957,8 @@ pub fn genesis(num_validators: u64) -> Genesis { implicit_vp_sha256: Default::default(), epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ - pos_gain_p: Dec::new(1, 2).expect("This can't fail"), - pos_gain_d: Dec::new(1, 2).expect("This can't fail"), + pos_gain_p: Dec::new(1, 1).expect("This can't fail"), + pos_gain_d: Dec::new(1, 1).expect("This can't fail"), staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), wrapper_tx_fees: Some(token::Amount::native_whole(0)), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 242ac3d619..6e4adac915 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -624,9 +624,9 @@ where let pos_last_staked_ratio: Dec = self .read_storage_key(¶ms_storage::get_staked_ratio_key()) .expect("PoS staked ratio should exist in storage"); - let pos_last_inflation_amount: u64 = self + let pos_last_inflation_amount: token::Amount = self .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) - .expect("PoS inflation rate should exist in storage"); + .expect("PoS inflation amount should exist in storage"); // Read from PoS storage let total_tokens = self .read_storage_key(&total_supply_key(&staking_token_address( @@ -654,11 +654,7 @@ where locked_ratio_target: pos_locked_ratio_target, locked_ratio_last: pos_last_staked_ratio, max_reward_rate: pos_max_inflation_rate, - last_inflation_amount: token::Amount::from_uint( - pos_last_inflation_amount, - 0, - ) - .expect("Amount out of bounds"), + last_inflation_amount: pos_last_inflation_amount, p_gain_nom: pos_p_gain_nom, d_gain_nom: pos_d_gain_nom, epochs_per_year, @@ -709,7 +705,7 @@ where let (address, value) = acc?; // Get reward token amount for this validator - let fractional_claim = value / Dec::from(num_blocks_in_last_epoch); + let fractional_claim = value / num_blocks_in_last_epoch; let reward = fractional_claim * inflation; // Get validator data at the last epoch @@ -1508,7 +1504,7 @@ mod test_finalize_block { // also return false if to_compare > target since this should // never happen for the use cases if to_compare < target { - let tolerance = Dec::new(1, POS_DECIMAL_PRECISION) + let tolerance = Dec::new(1, POS_DECIMAL_PRECISION / 2) .expect("Dec creation failed"); let res = Dec::one() - to_compare / target; res < tolerance @@ -1624,7 +1620,7 @@ mod test_finalize_block { assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(Dec(Uint::from(3)), acc_sum)); + assert!(is_decimal_equal_enough(Dec::new(3, 0).unwrap(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert!( acc.get(&val1.address).cloned().unwrap() @@ -1647,10 +1643,10 @@ mod test_finalize_block { assert_eq!(current_height, shell.wl_storage.storage.block.height.0); for _ in current_height..height_of_next_epoch.0 + 2 { - dbg!( - get_rewards_acc(&shell.wl_storage), - get_rewards_sum(&shell.wl_storage), - ); + // dbg!( + // get_rewards_acc(&shell.wl_storage), + // get_rewards_sum(&shell.wl_storage), + // ); next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); } assert!( diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 493d5c3321..34ff067a49 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -159,7 +159,7 @@ impl FromStr for Dec { impl From for Dec { fn from(amt: Amount) -> Self { - Self(amt.into()) + Self(amt.raw_amount()) } } @@ -246,11 +246,14 @@ impl Mul for Dec { } } +// TODO: is some checked arithmetic needed here to prevent overflows? +// Truncates down to the `POS_DECIMAL_PRECISION`th decimal place. impl Mul for Dec { type Output = Self; fn mul(self, rhs: Dec) -> Self::Output { - Self(self.0 * rhs.0) + let prod = self.0 * rhs.0; + Self(prod / Uint::exp10(POS_DECIMAL_PRECISION as usize)) } } @@ -269,6 +272,14 @@ impl Div for Dec { } } +impl Div for Dec { + type Output = Self; + + fn div(self, rhs: u64) -> Self::Output { + Self(self.0 / Uint::from(rhs)) + } +} + impl Display for Dec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut string = self.0.to_string(); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 734ca998c0..3ae675003c 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -78,6 +78,11 @@ impl Amount { } } + /// Get the raw [`Uint`] value, which represents namnam + pub fn raw_amount(&self) -> Uint { + self.raw + } + /// Create a new amount with the maximum value pub fn max() -> Self { Self { From 32b69e906908bf9e3a218af0b45d57d248db4f20 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 28 Apr 2023 16:13:12 -0400 Subject: [PATCH 34/69] Fix pos unit tests, change `Dec` precision to 12 places --- core/src/types/dec.rs | 63 ++++++++++++++++++++-------------- core/src/types/token.rs | 1 + shared/src/ledger/inflation.rs | 6 ++-- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 34ff067a49..e725f2f3a5 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -1,5 +1,8 @@ //! A non-negative fixed precision decimal type for computation primarily in the -//! PoS module. +//! PoS module. For rounding, any computation that exceeds the specified +//! precision is truncated down to the closest value with the specified +//! precision. + use std::fmt::{Debug, Display, Formatter}; use std::ops::{Add, AddAssign, Div, Mul, Sub}; use std::str::FromStr; @@ -8,11 +11,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; use serde::{Deserialize, Serialize}; +use super::token::NATIVE_MAX_DECIMAL_PLACES; use crate::types::token::{Amount, Change}; use crate::types::uint::Uint; /// The number of Dec places for PoS rational calculations -pub const POS_DECIMAL_PRECISION: u8 = 6; +pub const POS_DECIMAL_PRECISION: u8 = 12; #[derive(thiserror::Error, Debug)] #[error(transparent)] @@ -159,7 +163,13 @@ impl FromStr for Dec { impl From for Dec { fn from(amt: Amount) -> Self { - Self(amt.raw_amount()) + Self( + amt.raw_amount() + * Uint::exp10( + (POS_DECIMAL_PRECISION - NATIVE_MAX_DECIMAL_PLACES) + as usize, + ), + ) } } @@ -169,18 +179,12 @@ impl From for Dec { } } -// impl TryFrom for u64 { -// type Error = Error; - -// fn try_from(value: Dec) -> std::result::Result { -// let int = value.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize); -// if int > Uint::from(u64::MAX) { -// Err(eyre!("Dec value is too large to fit in a u64").into()) -// } else { -// Ok(int.into()) -// } -// } -// } +// Is error handling needed for this? +impl From for Dec { + fn from(num: Uint) -> Self { + Self(num * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} impl Add for Dec { type Output = Self; @@ -216,7 +220,7 @@ impl Mul for Dec { type Output = Uint; fn mul(self, rhs: Uint) -> Self::Output { - self.0 * rhs + self.0 * rhs / Uint::exp10(POS_DECIMAL_PRECISION as usize) } } @@ -247,7 +251,6 @@ impl Mul for Dec { } // TODO: is some checked arithmetic needed here to prevent overflows? -// Truncates down to the `POS_DECIMAL_PRECISION`th decimal place. impl Mul for Dec { type Output = Self; @@ -332,19 +335,29 @@ mod test_dec { // Fixed precision division is more thoroughly tested for the `Uint` // type. These are sanity checks that the precision is correct. assert_eq!( - Dec::new(1, 6).expect("Test failed") - / Dec::new(1, 6).expect("Test failed"), + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") + / Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed"), Dec::one(), ); assert_eq!( - Dec::new(1, 6).expect("Test failed") + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") / (Dec::new(1, 0).expect("Test failed") + Dec::one()), Dec::zero(), ); assert_eq!( - Dec::new(1, 6).expect("Test failed") / Dec::two(), + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") + / Dec::two(), Dec::zero(), ); + + // Test Dec * Dec multiplication + assert!(Dec::new(32353, POS_DECIMAL_PRECISION + 1u8).is_none()); + let dec1 = Dec::new(12345654321, 12).expect("Test failed"); + let dec2 = Dec::new(9876789, 12).expect("Test failed"); + let exp_prod = Dec::new(121935, 12).expect("Test failed"); + let exp_quot = Dec::new(1249966393025101, 12).expect("Test failed"); + assert_eq!(dec1 * dec2, exp_prod); + assert_eq!(dec1 / dec2, exp_quot); } /// Test the `Dec` and `Amount` interplay @@ -355,7 +368,7 @@ mod test_dec { debug_assert_eq!( Dec::from(amt), - Dec::new(1018, 0).expect("Test failed") + Dec::new(1018, 6).expect("Test failed") ); debug_assert_eq!(dec * amt, Amount::from(2809u64)); @@ -372,10 +385,10 @@ mod test_dec { Dec::new(314, 2).expect("Test failed"), ); - // more than six decimal places and zero integer part + // more than 12 decimal places and zero integer part assert_eq!( - Dec::from_str("0.1234567").expect("Test failed"), - Dec::new(123456, 6).expect("Test failed"), + Dec::from_str("0.1234567654321").expect("Test failed"), + Dec::new(123456765432, 12).expect("Test failed"), ); // No zero before the decimal diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 3ae675003c..f07e8eda72 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -488,6 +488,7 @@ impl From for Amount { } } +// Treats the u64 as a value of the raw amount (namnam) impl From for Amount { fn from(val: u64) -> Amount { Amount { diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index 11415ae96e..b5b1de321a 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -61,8 +61,10 @@ impl RewardsController { epochs_per_year, } = self; - let locked = Dec::from(locked_tokens); - let total = Dec::from(total_tokens); + // Token amounts must be expressed in terms of the raw amount (namnam) + // to properly run the PD controller + let locked = Dec::from(locked_tokens.raw_amount()); + let total = Dec::from(total_tokens.raw_amount()); let epochs_py: Dec = epochs_per_year.into(); let locked_ratio = locked / total; From 62cc6ec6b5e8c70929ed666d7f190e5ebe504042 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 1 May 2023 10:36:44 +0200 Subject: [PATCH 35/69] [fix]: Fixed proptest in PoS, removed rust_decimal from core and pos crates --- Cargo.lock | 5 -- .../lib/node/ledger/shell/finalize_block.rs | 1 - core/Cargo.toml | 4 +- core/src/types/token.rs | 73 ------------------- proof_of_stake/Cargo.toml | 2 - proof_of_stake/src/parameters.rs | 6 +- wasm/Cargo.lock | 5 -- wasm/checksums.json | 36 ++++----- wasm_for_tests/wasm_source/Cargo.lock | 5 -- 9 files changed, 21 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 448eb364e8..bc12a7c447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3954,8 +3954,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde 1.0.145", "serde_json", "sha2 0.9.9", @@ -4005,8 +4003,6 @@ dependencies = [ "namada_core", "once_cell", "proptest", - "rust_decimal", - "rust_decimal_macros", "test-log", "thiserror", "tracing 0.1.37", @@ -5557,7 +5553,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", - "borsh", "num-traits 0.2.15", "serde 1.0.145", ] diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6e4adac915..e9870d8ff1 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -906,7 +906,6 @@ mod test_finalize_block { InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; - use namada::types::uint::Uint; use namada_test_utils::TestWasms; use test_log::test; diff --git a/core/Cargo.toml b/core/Cargo.toml index 5e97f59a41..0a0b08ec54 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -90,8 +90,8 @@ prost-types = "0.9.0" rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} -rust_decimal = { version = "1.26.1", features = ["borsh"] } -rust_decimal_macros = "1.26.1" +# rust_decimal = { version = "1.26.1", features = ["borsh"] } +# rust_decimal_macros = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/core/src/types/token.rs b/core/src/types/token.rs index f07e8eda72..7cc997684e 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -7,7 +7,6 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use masp_primitives::transaction::Transaction; -use rust_decimal::prelude::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -143,27 +142,6 @@ impl Amount { Self { raw: change.abs() } } - /// Attempt to convert a `Decimal` to an `DenominatedAmount` with the - /// specified precision. - pub fn from_decimal( - decimal: Decimal, - denom: impl Into, - ) -> Result { - let denom = denom.into(); - if (denom as u32) < decimal.scale() { - Err(AmountParseError::ScaleTooLarge(decimal.scale(), denom)) - } else { - let value = Uint::from(decimal.mantissa().unsigned_abs()); - match Uint::from(10) - .checked_pow(Uint::from((denom as u32) - decimal.scale())) - .and_then(|scaling| scaling.checked_mul(value)) - { - Some(amount) => Ok(Self { raw: amount }), - None => Err(AmountParseError::ConvertToDecimal), - } - } - } - /// Given a string and a denomination, parse an amount from string. pub fn from_str( string: impl AsRef, @@ -174,18 +152,6 @@ impl Amount { .map(Into::into) } - /// Attempt to convert a float to an `Amount` with the specified - /// precision. - pub fn from_float( - float: impl Into, - denom: impl Into, - ) -> Result { - match Decimal::try_from(float.into()) { - Err(e) => Err(AmountParseError::InvalidDecimal(e)), - Ok(decimal) => Self::from_decimal(decimal, denom), - } - } - /// Attempt to convert an unsigned integer to an `Amount` with the /// specified precision. pub fn from_uint( @@ -463,19 +429,6 @@ impl<'de> serde::Deserialize<'de> for DenominatedAmount { } } -impl TryFrom for Decimal { - type Error = AmountParseError; - - fn try_from(amount: Amount) -> Result { - if amount.raw > Uint([u64::MAX, u64::MAX, 0, 0]) { - Err(AmountParseError::ConvertToDecimal) - } else { - Ok(Into::::into(amount.raw.as_u128()) - / Into::::into(NATIVE_SCALE)) - } - } -} - impl<'a> From<&'a DenominatedAmount> for &'a Amount { fn from(denom: &'a DenominatedAmount) -> Self { &denom.amount @@ -649,8 +602,6 @@ impl KeySeg for Amount { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum AmountParseError { - #[error("Error decoding token amount: {0}")] - InvalidDecimal(rust_decimal::Error), #[error( "Error decoding token amount, too many decimal places: {0}. Maximum \ {1}" @@ -982,24 +933,8 @@ impl TryFrom for Transfer { #[cfg(test)] mod tests { - use proptest::prelude::*; - use rust_decimal_macros::dec; - use super::*; - proptest! { - /// The upper limit is set to `2^51`, because then the float is - /// starting to lose precision. - #[test] - fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { - let amount = Amount::from_uint(raw_amount, NATIVE_MAX_DECIMAL_PLACES).expect("Test failed"); - // A round-trip conversion to and from Decimal should be an identity - let decimal = Decimal::from(raw_amount); - let identity = Amount::from_decimal(decimal, NATIVE_MAX_DECIMAL_PLACES).expect("Test failed"); - assert_eq!(amount, identity); - } - } - #[test] fn test_token_display() { let max = Amount::from_uint(u64::MAX, 0).expect("Test failed"); @@ -1095,14 +1030,6 @@ mod tests { assert_eq!(max_signed.checked_signed_add(max_signed), None); } - #[test] - fn test_amount_from_decimal() { - assert!(Amount::from_decimal(dec!(1.12), 1).is_err()); - assert!(Amount::from_decimal(dec!(1.12), 80).is_err()); - let amount = Amount::from_decimal(dec!(1.12), 3).expect("Test failed"); - assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); - } - #[test] fn test_amount_from_string() { assert!(Amount::from_str("1.12", 1).is_err()); diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index faab68dfd6..cab2920820 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -24,8 +24,6 @@ hex = "0.4.3" once_cell = "1.8.0" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} -rust_decimal = { version = "1.26.1", features = ["borsh"] } -rust_decimal_macros = "1.26.1" thiserror = "1.0.30" tracing = "0.1.30" data-encoding = "2.3.2" diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index a5b75ebd80..a67878b8d1 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -113,11 +113,7 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - // - // TODO: decide if this is still a check we want to do (in its current - // state with our latest voting power conventions, it will fail - // always) - let max_total_voting_power = *self.tm_votes_per_token + let max_total_voting_power = self.tm_votes_per_token * Uint::from(TOKEN_MAX_AMOUNT) * Uint::from(self.max_validator_slots); match i64::try_from(max_total_voting_power) { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 73cf3f057b..2c887ffc54 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2674,8 +2674,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -2709,8 +2707,6 @@ dependencies = [ "namada_core", "once_cell", "proptest", - "rust_decimal", - "rust_decimal_macros", "thiserror", "tracing", ] @@ -3729,7 +3725,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", - "borsh", "num-traits", "serde", ] diff --git a/wasm/checksums.json b/wasm/checksums.json index 470a405e0a..32215c5ba0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.204dd016d48999ce2bd65a2cc8ba7ba6eec6a2e9878b544e4d508ce8a6c4619e.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.820da4e9d5cd00200e1a88808c5d0cce79bbf9c6a42c4bcf69cc0094d44053eb.wasm", - "tx_ibc.wasm": "tx_ibc.9d95be7b97770cee6665bfb9a53cfb10a4bb162be3bd813fb0720c5b70bf75d7.wasm", - "tx_init_account.wasm": "tx_init_account.92adf7dd170cde441f34001738e4854f6febeba416c73489ae31a75a09be9603.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e76dcfdea228ac37745de999feefa56ae881b6be101565b86d832d27d1598323.wasm", - "tx_init_validator.wasm": "tx_init_validator.087a519a8e9e0a88e7ff71556b9cab8296ba879e0da3dfe7dfb66ccf407db92e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.fcd7aca657cd5e6aac3af9752c24d31078a3b8bbccba845e843980fec677dea0.wasm", - "tx_transfer.wasm": "tx_transfer.4ac65052b5a699df823b773dc943dd582701f3e4b1a6b8e52da4e39c9fa31195.wasm", - "tx_unbond.wasm": "tx_unbond.558c7d3e0d46766be957187b9c6d3d44490c29c9a354e7297be831fe48383452.wasm", - "tx_update_vp.wasm": "tx_update_vp.b7791369274dea718756434ba323396a471ca27c94a52d6c182da94953d9b22b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ca9bff7f78c3accf84725cd4bcdaf3872022fd5f6390fc22a5e20d24f53d88b7.wasm", - "tx_withdraw.wasm": "tx_withdraw.650cfc67a10136eb73bc3ceaaad9e2b2b06e2b423989a86604222187be4a5865.wasm", - "vp_implicit.wasm": "vp_implicit.14afbabd825733511d404013f4bc1dd9078ff0b801d3dd1ebd5bf3b4e5b41dfd.wasm", - "vp_masp.wasm": "vp_masp.b9d93452eb037e411a9e103aef6de33a27ddf9578b0125b7044aae00ebcfcd93.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3e2f78bfa703c54e28d41ee12ca7869e60f0fe37b936ccc246a7aef39c95807a.wasm", - "vp_token.wasm": "vp_token.189080cb6137da24bc2ce9173ecac54e46b1dcb341d79aa66c9b6891fed92eb7.wasm", - "vp_user.wasm": "vp_user.909808779f6442ff5d08a4b195bb39b75402c5fe3d710c38ea17469c56cc9824.wasm", - "vp_validator.wasm": "vp_validator.ce440d41274ebc487f88a64843c27bb693093a3c2828a197b7975e82d27b295c.wasm" + "tx_bond.wasm": "tx_bond.91275d24c59a883c629c5b26ff5f7b57cf0f18009cbed190050d752a2e96a0a8.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.73aa36169a1366c89fdd7f24b6d25f2931b96ed2bf8f2cdb2f690a4132c99788.wasm", + "tx_ibc.wasm": "tx_ibc.bc180e0716d5e28f0395abe8c21ab05c98d85011c377633f91e1d0291e969ef8.wasm", + "tx_init_account.wasm": "tx_init_account.bad8964f803f6e369ccc911027deb78e48a7896131296172147c92666d27227d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c70f106d3c26f0f2c5c2ac6a9b5ff851b67ecfb19f76fc57d0563aa4a9e012f5.wasm", + "tx_init_validator.wasm": "tx_init_validator.0538743e4433d9657670307828c5df727bc936bad9f751653f9a914fbd29c983.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.e4806260ff21c1a441ddbdba4f8640a18db03a5687f0ab5dc2e08a72423b808b.wasm", + "tx_transfer.wasm": "tx_transfer.c564498c1d419e566e03a1b5f39927ceda4b3efc1e78128c574d823d1f7cfaeb.wasm", + "tx_unbond.wasm": "tx_unbond.e800fac2d595b7ac3892b1e1efced00ee1f4b5fec22c08a90ee9838725104b8f.wasm", + "tx_update_vp.wasm": "tx_update_vp.6ff4bd7fbf11449abef4f3632243d6e8cf93949e76be206be6eaf8d88e15ccd2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4defe4f78e6aee8b6c6faabaf4a8cf6c2939abd9edcc2a772a02f0f6bcc4b8cf.wasm", + "tx_withdraw.wasm": "tx_withdraw.4a31fad26cf1e34d0b7ac5b47d78ccdc79de4666840c81b441592a1b5fe9d4b5.wasm", + "vp_implicit.wasm": "vp_implicit.d014edfd0b81d679c918d033962c347d0f833cd86642b91205e1705e33caddb5.wasm", + "vp_masp.wasm": "vp_masp.8e725e075108905e316a1f86fc696ee9f1f2ba4b0e7646505b3046443bb22d0d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.99f470dbb03b51f2082b8ba31c6cd1580ff5504752b04b8243ffbfc1b60b5c99.wasm", + "vp_token.wasm": "vp_token.d642c37273de047a4d3bd126ad0bbd4f7b16b2efb1a766fcec0104d7ca231a64.wasm", + "vp_user.wasm": "vp_user.113f37df167b59d278b24f9c1a4c64f2e1b03641cb3d55942e02fe8ba8e461f2.wasm", + "vp_validator.wasm": "vp_validator.f02cf20e2cfda256bc3deb5458de8aa6cb614c5cf2524c4be33495b30a8ef91d.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f1b38f02a6..1c677a8592 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2674,8 +2674,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -2709,8 +2707,6 @@ dependencies = [ "namada_core", "once_cell", "proptest", - "rust_decimal", - "rust_decimal_macros", "thiserror", "tracing", ] @@ -3711,7 +3707,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", - "borsh", "num-traits", "serde", ] From c2c66cc5b8c4193d1ce10f9a0d3d58dd0a0786db Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 1 May 2023 10:46:42 +0200 Subject: [PATCH 36/69] [chore]: Removed rust_decimal and rust_decimal_macro deps from project --- Cargo.lock | 27 --------------------------- apps/Cargo.toml | 2 -- core/Cargo.toml | 2 -- shared/Cargo.toml | 2 -- tests/Cargo.toml | 2 -- wasm/Cargo.lock | 14 -------------- wasm_for_tests/wasm_source/Cargo.lock | 25 ------------------------- 7 files changed, 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc12a7c447..21e402a594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3800,8 +3800,6 @@ dependencies = [ "prost", "pwasm-utils", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3880,8 +3878,6 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "rust_decimal", - "rust_decimal_macros", "serde 1.0.145", "serde_bytes", "serde_json", @@ -4049,8 +4045,6 @@ dependencies = [ "prost", "rand 0.8.5", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -5546,27 +5540,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" -[[package]] -name = "rust_decimal" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" -dependencies = [ - "arrayvec 0.7.2", - "num-traits 0.2.15", - "serde 1.0.145", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index c05a17a7aa..ee1e99ea4b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -151,8 +151,6 @@ winapi = "0.3.9" masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} -rust_decimal = "1.26.1" -rust_decimal_macros = "1.26.1" [dev-dependencies] namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} diff --git a/core/Cargo.toml b/core/Cargo.toml index 0a0b08ec54..a074390bcc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -90,8 +90,6 @@ prost-types = "0.9.0" rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} -# rust_decimal = { version = "1.26.1", features = ["borsh"] } -# rust_decimal_macros = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d4e0bf019e..d9dc8ad02a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,8 +105,6 @@ proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", prost = "0.9.0" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rayon = {version = "=1.5.3", optional = true} -rust_decimal = "1.26.1" -rust_decimal_macros = "1.26.1" serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 9941de357b..f141f363f6 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -46,8 +46,6 @@ tokio = {version = "1.8.2", features = ["full"]} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} derivative = "2.2.0" -rust_decimal = "1.26.1" -rust_decimal_macros = "1.26.1" [dev-dependencies] namada_apps = {path = "../apps", default-features = false, features = ["testing"]} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 2c887ffc54..c7edff1984 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2624,8 +2624,6 @@ dependencies = [ "prost", "pwasm-utils", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -2737,8 +2735,6 @@ dependencies = [ "namada_vp_prelude", "prost", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3729,16 +3725,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1c677a8592..04f4c6989b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2624,8 +2624,6 @@ dependencies = [ "prost", "pwasm-utils", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -2737,8 +2735,6 @@ dependencies = [ "namada_vp_prelude", "prost", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3700,27 +3696,6 @@ dependencies = [ "rustc-hex", ] -[[package]] -name = "rust_decimal" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" -dependencies = [ - "arrayvec 0.7.2", - "num-traits", - "serde", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.21" From c97a9e98af097e78f94595c1808bc7a49dfffe54 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 10 May 2023 17:12:45 +0800 Subject: [PATCH 37/69] Fix Key from always parsing Previously the key would always parse, even if the string is empty, this causes an issue where if we wanted an Optional Key, then it would always be parsed as Some, causing issues for various parts of the system. --- core/src/types/storage.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 9ceddecf15..4f5aec48f3 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -474,11 +474,16 @@ impl Value for TreeBytes { impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { - let mut segments = Vec::new(); - for s in string.as_ref().split(KEY_SEGMENT_SEPARATOR) { - segments.push(DbKeySeg::parse(s.to_owned())?); + let string = string.as_ref(); + if string.is_empty() { + Err(Error::ParseKeySeg(string.to_string())) + } else { + let mut segments = Vec::new(); + for s in string.split(KEY_SEGMENT_SEPARATOR) { + segments.push(DbKeySeg::parse(s.to_owned())?); + } + Ok(Key { segments }) } - Ok(Key { segments }) } /// Returns a new key with segments of `Self` and the given segment From 26413eca80b5b9ba2386acdecd2cef3f887f68cf Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 1 May 2023 19:22:59 +0800 Subject: [PATCH 38/69] Fixing Masp Amounts to convert on the edges We move out the looping code from each part and move it to the edges of masp --- apps/src/lib/client/rpc.rs | 409 +++++++++++++---------------- apps/src/lib/client/tx.rs | 515 ++++++++++++++++++++++++++----------- core/src/types/token.rs | 60 ++++- core/src/types/uint.rs | 19 +- 4 files changed, 622 insertions(+), 381 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f574661cf1..7dcb7eb07b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -45,7 +45,7 @@ use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; use namada::types::token::{ - balance_key, DenominatedAmount, MaspDenom, Transfer, + balance_key, Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, }; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, @@ -58,7 +58,7 @@ use crate::cli::args::InputAmount; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; use crate::client::tx::{ - Conversions, MaspDenominatedAmount, PinnedBalanceError, TransactionDelta, + Conversions, MaspAmount, MaspChange, PinnedBalanceError, TransactionDelta, TransferDelta, }; use crate::facade::tendermint::merkle::proof::Proof; @@ -251,30 +251,16 @@ pub async fn query_tx_deltas( } // Describe how a Transfer simply subtracts from one // account and adds the same to another - let mut delta = TransferDelta::default(); - for denom in MaspDenom::iter() { - let denominated = - denom.denominate(&transfer.amount); - if denominated != 0 { - let tfer_delta = Amount::from_nonnegative( - ( - transfer.token.clone(), - transfer.sub_prefix.clone(), - denom, - ), - denominated, - ) - .expect("invalid value for amount"); - delta.insert( - transfer.source.clone(), - Amount::zero() - &tfer_delta, - ); - delta.insert( - transfer.target.clone(), - tfer_delta, - ); - } - } + let delta = TransferDelta::from([( + transfer.source.clone(), + MaspChange { + asset: TokenAddress { + address: transfer.token.clone(), + sub_prefix: transfer.sub_prefix.clone(), + }, + change: -transfer.amount.amount.change(), + }, + )]); // No shielded accounts are affected by this Transfer transfers.insert( (height, idx), @@ -345,90 +331,72 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token - relevant &= - match &query_token { - Some(token) => { - tfer_delta.values().zip(MaspDenom::iter()).any( - |(x, denom)| { - x[&(token.clone(), sub_prefix.clone(), denom)] != 0 - }, - ) || shielded_accounts.values().zip(MaspDenom::iter()).any( - |(x, denom)| { - x[&(token.clone(), sub_prefix.clone(), denom)] != 0 - }, - ) - } - None => true, - }; + relevant &= match &query_token { + Some(token) => { + let check = |(tok, amt): (&TokenAddress, &token::Amount)| { + tok.sub_prefix == sub_prefix + && &tok.address == token + && !amt.is_zero() + }; + tfer_delta.values().cloned().any( + |MaspChange { ref asset, change }| { + check((asset, &token::Amount::from(change))) + }, + ) || shielded_accounts + .values() + .cloned() + .any(|x| x.iter().any(check)) + } + None => true, + }; // Filter out those entries that do not satisfy user query if !relevant { continue; } println!("Height: {}, Index: {}, Transparent Transfer:", height, idx); // Display the transparent changes first - for (account, amt) in tfer_delta { + for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { print!(" {}:", account); - for ((addr, sub_prefix, denom), val) in amt.components() { - let token_alias = lookup_alias(&ctx, addr); - let sign = match val.cmp(&0) { - Ordering::Greater => "+", - Ordering::Less => "-", - Ordering::Equal => "", - }; - print!( - " {}{} {}{}", - sign, - format_denominated_amount( - &client, - addr, - sub_prefix, - token::Amount::from_masp_denominated( - val.unsigned_abs(), - *denom - ) - ) - .await, - token_alias, - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), - ); - } - println!(); + let token_alias = lookup_alias(&ctx, &asset.address); + let sign = match change.cmp(&Change::zero()) { + Ordering::Greater => "+", + Ordering::Less => "-", + Ordering::Equal => "", + }; + print!( + " {}{} {}", + sign, + format_denominated_amount( + &client, + asset, + token::Amount::from(change), + ) + .await, + asset.format_with_alias(&token_alias) + ); } + println!(); } // Then display the shielded changes afterwards // TODO: turn this to a display impl - for (account, amt) in shielded_accounts { + // (account, amt) + for (account, masp_change) in shielded_accounts { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); - for ((addr, sub_prefix, denom), val) in amt.components() { - let token_alias = lookup_alias(&ctx, addr); - let sign = match val.cmp(&0) { + for (token_addr, val) in masp_change { + let token_alias = lookup_alias(&ctx, &token_addr.address); + let sign = match val.cmp(&token::Amount::zero()) { Ordering::Greater => "+", Ordering::Less => "-", Ordering::Equal => "", }; print!( - " {}{} {}{}", + " {}{} {}", sign, - format_denominated_amount( - &client, - addr, - sub_prefix, - token::Amount::from_masp_denominated( - val.unsigned_abs(), - *denom - ) - ) - .await, - token_alias, - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() + format_denominated_amount(&client, &token_addr, val,) + .await, + token_addr.format_with_alias(&token_alias), ); } println!(); @@ -552,8 +520,10 @@ pub async fn query_transparent_balance( Some(balance) => { let balance = format_denominated_amount( &client, - &token, - &sub_prefix, + &TokenAddress { + address: token, + sub_prefix, + }, balance, ) .await; @@ -721,23 +691,25 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .unwrap_or_default() ); } else { + let token_addr = TokenAddress { + address: token, + sub_prefix: sub_prefix + .as_ref() + .map(|k| Key::parse(k).unwrap()), + }; let formatted = format_denominated_amount( &client, - &token, - &sub_prefix.map(|k| Key::parse(k).unwrap()), + &token_addr, total_balance, ) .await; println!( "Payment address {} was consumed during epoch {}. \ - Received {} {}{}", + Received {} {}", owner, epoch, formatted, - token_alias, - sub_prefix - .map(|k| format!("/{}", k)) - .unwrap_or_default() + token_addr.format_with_alias(&token_alias), ); } } @@ -748,11 +720,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .shielded .decode_amount(client.clone(), balance, epoch) .await; - for ((addr, sub_prefix, denom), value) in balance.components() { - let asset_value = token::Amount::from_masp_denominated( - *value as u64, - *denom, - ); + for (token_addr, value) in balance.iter() { if !found_any { println!( "Payment address {} was consumed during epoch {}. \ @@ -761,23 +729,16 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { ); found_any = true; } - let formatted = format_denominated_amount( - &client, - addr, - sub_prefix, - asset_value, - ) - .await; + let formatted = + format_denominated_amount(&client, token_addr, *value) + .await; + let token_alias = tokens + .get(&token_addr.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_addr.address.to_string()); println!( - " {}{}: {}", - tokens - .get(addr) - .cloned() - .unwrap_or_else(|| addr.clone()), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), + " {}: {}", + token_addr.format_with_alias(&token_alias), formatted, ); } @@ -814,8 +775,10 @@ async fn print_balances( sub_prefix.clone(), format_denominated_amount( client, - tok, - &Some(sub_prefix), + &TokenAddress { + address: tok.clone(), + sub_prefix: Some(sub_prefix) + }, balance ) .await, @@ -831,7 +794,12 @@ async fn print_balances( format!( ": {}, owned by {}", format_denominated_amount( - client, tok, &None, balance + client, + &TokenAddress { + address: tok.clone(), + sub_prefix: None + }, + balance ) .await, lookup_alias(ctx, owner) @@ -1002,7 +970,7 @@ pub fn value_by_address( epoch: Epoch, ) -> (AssetType, i64) { // Compute the unique asset identifier from the token address - let asset_type = make_asset_type(epoch, &token, &sub_prefix, denom); + let asset_type = make_asset_type(Some(epoch), &token, &sub_prefix, denom); (asset_type, amt[&asset_type]) } @@ -1065,8 +1033,12 @@ pub async fn query_shielded_balance( .expect("context should contain viewing key") }; // Compute the unique asset identifier from the token address - let asset_type = - make_asset_type(epoch, &token, &args.sub_prefix, denom); + let asset_type = make_asset_type( + Some(epoch), + &token, + &args.sub_prefix, + denom, + ); total_balance += token::Amount::from_masp_denominated( balance[&asset_type] as u64, denom, @@ -1084,17 +1056,16 @@ pub async fn query_shielded_balance( .unwrap_or_default(), ); } else { + let token_address = TokenAddress { + address: token, + sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), + }; println!( - "{}{}: {}", - token_alias, - args.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), + "{}: {}", + token_address.format_with_alias(&token_alias), format_denominated_amount( &client, - &token, - &args.sub_prefix.map(|k| Key::parse(k).unwrap()), + &token_address, total_balance ) .await @@ -1177,19 +1148,22 @@ pub async fn query_shielded_balance( .entry(addr.clone()) .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) .or_insert_with(|| vec![sub_prefix.clone()]); + let token_address = TokenAddress { + address: addr, + sub_prefix, + }; // Only assets with the current timestamp count + let alias = tokens + .get(&token_address.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_address.address.to_string()); println!( - "Shielded Token {}{}:", - tokens.get(&addr).cloned().unwrap_or_else(|| addr.clone()), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), + "Shielded Token {}:", + token_address.format_with_alias(&alias), ); let formatted = format_denominated_amount( &client, - &addr, - &sub_prefix, + &token_address, token_balance, ) .await; @@ -1233,19 +1207,26 @@ pub async fn query_shielded_balance( let token = ctx.get(&token); let mut found_any = false; let token_alias = lookup_alias(ctx, &token); - println!( - "Shielded Token {}{}:", - token_alias, - args.sub_prefix + let token_address = TokenAddress { + address: token.clone(), + sub_prefix: args + .sub_prefix .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() + .map(|k| Key::parse(k).unwrap()), + }; + println!( + "Shielded Token {}:", + token_address.format_with_alias(&token_alias), ); for fvk in viewing_keys { let mut balance = token::Amount::default(); for denom in MaspDenom::iter() { - let asset_type = - make_asset_type(epoch, &token, &args.sub_prefix, denom); + let asset_type = make_asset_type( + Some(epoch), + &token, + &args.sub_prefix, + denom, + ); // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let denom_balance = if no_conversions { @@ -1270,19 +1251,15 @@ pub async fn query_shielded_balance( found_any = true; } } - let formatted = format_denominated_amount( - &client, - &token, - &args.sub_prefix.as_ref().map(|k| Key::parse(k).unwrap()), - balance, - ) - .await; + let formatted = + format_denominated_amount(&client, &token_address, balance) + .await; println!(" {}, owned by {}", formatted, fvk); } if !found_any { println!( "No shielded {} balance found for any wallet key", - token_alias + token_address.format_with_alias(&token_alias), ); } } @@ -1298,10 +1275,8 @@ pub async fn query_shielded_balance( .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded - .decode_all_amounts(client.clone(), balance) - .await; + let decoded_balance = + ctx.shielded.decode_all_amounts(&client, balance).await; print_decoded_balance_with_epoch(ctx, &client, decoded_balance) .await; } else { @@ -1328,30 +1303,17 @@ pub async fn query_shielded_balance( pub async fn print_decoded_balance( ctx: &mut Context, client: &HttpClient, - decoded_balance: MaspDenominatedAmount, + decoded_balance: HashMap, ) { - let mut balances = HashMap::new(); - for ((addr, sub_prefix, denom), value) in decoded_balance.components() { - let asset_value = - token::Amount::from_masp_denominated(*value as u64, *denom); - balances - .entry((addr, sub_prefix)) - .and_modify(|val| *val += asset_value) - .or_insert(asset_value); - } - if balances.is_empty() { + if decoded_balance.is_empty() { println!("No shielded balance found for given key"); } else { - for ((addr, sub_prefix), amount) in balances { + for (token_addr, amount) in decoded_balance { println!( - "{}{} : {}", - lookup_alias(ctx, addr), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), - format_denominated_amount(client, addr, sub_prefix, amount,) - .await, + "{} : {}", + token_addr + .format_with_alias(&lookup_alias(ctx, &token_addr.address)), + format_denominated_amount(client, &token_addr, amount).await, ); } } @@ -1360,36 +1322,24 @@ pub async fn print_decoded_balance( pub async fn print_decoded_balance_with_epoch( ctx: &mut Context, client: &HttpClient, - decoded_balance: Amount<(Address, Option, MaspDenom, Epoch)>, + decoded_balance: MaspAmount, ) { let tokens = ctx.tokens(); - let mut balances = HashMap::new(); - for ((addr, sub_prefix, denom, epoch), value) in - decoded_balance.components() - { - let asset_value = - token::Amount::from_masp_denominated(*value as u64, *denom); - balances - .entry((addr, sub_prefix, epoch)) - .and_modify(|val| *val += asset_value) - .or_insert(asset_value); - } - if balances.is_empty() { + if decoded_balance.is_empty() { println!("No shielded balance found for given key"); - } else { - for ((addr, sub_prefix, epoch), amount) in balances { - println!( - "{}{} | {} : {}", - tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), - sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), - epoch, - format_denominated_amount(client, addr, sub_prefix, amount,) - .await, - ); - } + } + for ((epoch, token_addr), value) in decoded_balance.iter() { + let asset_value = token::Amount::from(*value); + let alias = tokens + .get(&token_addr.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_addr.to_string()); + println!( + "{} | {} : {}", + token_addr.format_with_alias(&alias), + epoch, + format_denominated_amount(client, token_addr, asset_value).await, + ); } } @@ -2899,14 +2849,13 @@ pub(super) fn unwrap_client_response( /// correctly as a string. pub(super) async fn format_denominated_amount( client: &HttpClient, - token: &Address, - sub_prefix: &Option, + token: &TokenAddress, amount: token::Amount, ) -> String { let denom = unwrap_client_response( RPC.vp() .token() - .denomination(client, token, sub_prefix) + .denomination(client, &token.address, &token.sub_prefix) .await, ) .unwrap_or_else(|| { @@ -2921,23 +2870,35 @@ pub(super) async fn format_denominated_amount( /// Make asset type corresponding to given address and epoch pub fn make_asset_type( - epoch: Epoch, + epoch: Option, token: &Address, sub_prefix: &Option, denom: MaspDenom, ) -> AssetType { // Typestamp the chosen token with the current epoch - let token_bytes = ( - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - epoch.0, - ) - .try_to_vec() - .expect("token should serialize"); + let token_bytes = match epoch { + None => ( + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + ) + .try_to_vec() + .expect("token should serialize"), + Some(epoch) => ( + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + epoch.0, + ) + .try_to_vec() + .expect("token should serialize"), + }; // Generate the unique asset identifier from the unique token address AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 743bb4c600..712c0382e3 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -53,8 +53,8 @@ use namada::types::storage::{ }; use namada::types::time::DateTimeUtc; use namada::types::token::{ - DenominatedAmount, MaspDenom, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, - TX_KEY_PREFIX, + DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, + PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, @@ -507,19 +507,105 @@ pub enum PinnedBalanceError { InvalidViewingKey, } +// #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +// pub struct MaspAmount { +// pub asset: TokenAddress, +// pub amount: token::Amount, +// } + +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct MaspChange { + pub asset: TokenAddress, + pub change: token::Change, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Default)] +pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); + +impl std::ops::Deref for MaspAmount { + type Target = HashMap<(Epoch, TokenAddress), token::Change>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for MaspAmount { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::ops::Add for MaspAmount { + type Output = MaspAmount; + + fn add(mut self, mut rhs: MaspAmount) -> Self::Output { + for (key, value) in rhs.drain() { + self.entry(key) + .and_modify(|val| *val += value) + .or_insert(value); + } + self + } +} + +impl std::ops::AddAssign for MaspAmount { + fn add_assign(&mut self, amount: MaspAmount) { + *self = self.clone() + amount + } +} + +// please stop copying and pasting make a function +impl std::ops::Sub for MaspAmount { + type Output = MaspAmount; + + fn sub(mut self, mut rhs: MaspAmount) -> Self::Output { + for (key, value) in rhs.drain() { + self.entry(key) + .and_modify(|val| *val -= value) + .or_insert(value); + } + self + } +} + +impl std::ops::SubAssign for MaspAmount { + fn sub_assign(&mut self, amount: MaspAmount) { + *self = self.clone() - amount + } +} + +impl<'a> From<&'a MaspAmount> for Amount { + fn from(masp_amount: &'a MaspAmount) -> Amount { + let mut res = Amount::zero(); + for ((epoch, key), val) in masp_amount.iter() { + for denom in MaspDenom::iter() { + let asset = make_asset_type( + Some(*epoch), + &key.address, + &key.sub_prefix, + denom, + ); + res += Amount::from_pair(asset, denom.denominate_i64(val)) + .unwrap(); + } + } + res + } +} + /// Represents the amount used of different conversions pub type Conversions = HashMap, i64)>; /// Represents an amount that is -pub type MaspDenominatedAmount = Amount<(Address, Option, MaspDenom)>; +pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; /// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = - HashMap, MaspDenom)>>; +pub type TransferDelta = HashMap; /// Represents the changes that were made to a list of shielded accounts -pub type TransactionDelta = HashMap; +pub type TransactionDelta = HashMap; /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. @@ -665,6 +751,7 @@ impl ShieldedContext { sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], ) { + let client = HttpClient::new(ledger_address.clone()).unwrap(); // First determine which of the keys requested to be fetched are new. // Necessary because old transactions will need to be scanned for new // keys. @@ -696,7 +783,7 @@ impl ShieldedContext { // Update this unknown shielded context until it is level with self while tx_ctx.last_txidx != self.last_txidx { if let Some(((height, idx), (epoch, tx))) = tx_iter.next() { - tx_ctx.scan_tx(*height, *idx, *epoch, tx); + tx_ctx.scan_tx(&client, *height, *idx, *epoch, tx).await; } else { break; } @@ -714,7 +801,7 @@ impl ShieldedContext { // Now that we possess the unspent notes corresponding to both old and // new keys up until tx_pos, proceed to scan the new transactions. for ((height, idx), (epoch, tx)) in &mut tx_iter { - self.scan_tx(*height, *idx, *epoch, tx); + self.scan_tx(&client, *height, *idx, *epoch, tx).await; } } @@ -792,8 +879,9 @@ impl ShieldedContext { /// we have spent are updated. The witness map is maintained to make it /// easier to construct note merkle paths in other code. See /// - pub fn scan_tx( + pub async fn scan_tx( &mut self, + client: &HttpClient, height: BlockHeight, index: TxIndex, epoch: Epoch, @@ -826,7 +914,9 @@ impl ShieldedContext { self.witness_map.insert(note_pos, witness); // Let's try to see if any of our viewing keys can decrypt latest // note - for (vk, notes) in self.pos_map.iter_mut() { + let mut pos_map = HashMap::new(); + std::mem::swap(&mut pos_map, &mut self.pos_map); + for (vk, notes) in pos_map.iter_mut() { let decres = try_sapling_note_decryption::( 0, &vk.ivk().0, @@ -850,16 +940,24 @@ impl ShieldedContext { // Note the account changes let balance = transaction_delta .entry(*vk) - .or_insert_with(Amount::zero); - *balance += - Amount::from_nonnegative(note.asset_type, note.value) + .or_insert_with(MaspAmount::default); + *balance += self + .decode_all_amounts( + client, + Amount::from_nonnegative( + note.asset_type, + note.value, + ) .expect( "found note with invalid value or asset type", - ); + ), + ) + .await; self.vk_map.insert(note_pos, *vk); break; } } + std::mem::swap(&mut pos_map, &mut self.pos_map); } // Cancel out those of our notes that have been spent for ss in &shielded.shielded_spends { @@ -870,30 +968,35 @@ impl ShieldedContext { // Note the account changes let balance = transaction_delta .entry(self.vk_map[note_pos]) - .or_insert_with(Amount::zero); + .or_insert_with(MaspAmount::default); let note = self.note_map[note_pos]; - *balance -= - Amount::from_nonnegative(note.asset_type, note.value) - .expect("found note with invalid value or asset type"); + *balance -= self + .decode_all_amounts( + client, + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ), + ) + .await; } } // Record the changes to the transparent accounts let mut transfer_delta = TransferDelta::new(); - for denom in MaspDenom::iter() { - let transparent_delta = Amount::from_nonnegative( - (tx.token.clone(), tx.sub_prefix.clone(), denom), - denom.denominate(&tx.amount.amount), - ) - .expect("invalid value for amount"); - if transparent_delta == Amount::zero() { - continue; - } - transfer_delta - .insert(tx.source.clone(), Amount::zero() - &transparent_delta); - transfer_delta.insert(tx.target.clone(), transparent_delta); - self.last_txidx += 1; - } + let token_addr = TokenAddress { + address: tx.token.clone(), + sub_prefix: tx.sub_prefix.clone(), + }; + transfer_delta.insert( + tx.source.clone(), + MaspChange { + asset: token_addr, + change: -tx.amount.amount.change(), + }, + ); + self.last_txidx += 1; + self.delta_map.insert( (height, index), (epoch, transfer_delta, transaction_delta), @@ -1007,6 +1110,7 @@ impl ShieldedContext { ) -> Option { // First get the unexchanged balance if let Some(balance) = self.compute_shielded_balance(vk) { + let balance = self.decode_all_amounts(&client, balance).await; // And then exchange balance into current asset types Some( self.compute_exchanged_amount( @@ -1028,12 +1132,15 @@ impl ShieldedContext { /// conversion used, the conversions are applied to the given input, and /// the trace amount that could not be converted is moved from input to /// output. - fn apply_conversion( + #[allow(clippy::too_many_arguments)] + async fn apply_conversion( + &mut self, + client: &HttpClient, conv: AllowedConversion, asset_type: AssetType, value: i64, usage: &mut i64, - input: &mut Amount, + input: &mut MaspAmount, output: &mut Amount, ) { // If conversion if possible, accumulate the exchanged amount @@ -1056,7 +1163,9 @@ impl ShieldedContext { // Record how much more of the given conversion has been used *usage += required; // Apply the conversions to input and move the trace amount to output - *input += conv * required - &trace; + *input += self + .decode_all_amounts(client, conv * required - &trace) + .await; *output += trace; } @@ -1067,76 +1176,104 @@ impl ShieldedContext { pub async fn compute_exchanged_amount( &mut self, client: HttpClient, - mut input: Amount, + mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, ) -> (Amount, Conversions) { // Where we will store our exchanged value let mut output = Amount::zero(); // Repeatedly exchange assets until it is no longer possible - while let Some((asset_type, value)) = - input.components().next().map(cloned_pair) + while let Some(((asset_epoch, token_addr), value)) = + input.iter().next().map(cloned_pair) { - let target_asset_type = self - .decode_asset_type(client.clone(), asset_type) - .await - .map(|(addr, sub, denom, _epoch)| { - make_asset_type(target_epoch, &addr, &sub, denom) - }) - .unwrap_or(asset_type); - let at_target_asset_type = asset_type == target_asset_type; - if let (Some((conv, _wit, usage)), false) = ( - self.query_allowed_conversion( - client.clone(), - asset_type, - &mut conversions, - ) - .await, - at_target_asset_type, - ) { - println!( - "converting current asset type to latest asset type..." + for denom in MaspDenom::iter() { + let target_asset_type = make_asset_type( + Some(target_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, ); - // Not at the target asset type, not at the latest asset type. - // Apply conversion to get from current asset type to the latest - // asset type. - Self::apply_conversion( - conv.clone(), - asset_type, - value, - usage, - &mut input, - &mut output, + let asset_type = make_asset_type( + Some(asset_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, ); - } else if let (Some((conv, _wit, usage)), false) = ( - self.query_allowed_conversion( - client.clone(), - target_asset_type, - &mut conversions, - ) - .await, - at_target_asset_type, - ) { - println!( - "converting latest asset type to target asset type..." - ); - // Not at the target asset type, yes at the latest asset type. - // Apply inverse conversion to get from latest asset type to - // the target asset type. - Self::apply_conversion( - conv.clone(), - asset_type, - value, - usage, - &mut input, - &mut output, - ); - } else { - // At the target asset type. Then move component over to output. - let comp = input.project(asset_type); - output += ∁ - // Strike from input to avoid repeating computation - input -= comp; + let at_target_asset_type = target_epoch == asset_epoch; + + let denom_value = denom.denominate_i64(&value); + _ = self + .query_allowed_conversion( + client.clone(), + target_asset_type, + &mut conversions, + ) + .await; + + if let (Some((conv, _wit, usage)), false) = ( + conversions.get_mut(&target_asset_type), + at_target_asset_type, + ) { + println!( + "converting current asset type to latest asset type..." + ); + // Not at the target asset type, not at the latest asset + // type. Apply conversion to get from + // current asset type to the latest + // asset type. + self.apply_conversion( + &client, + conv.clone(), + target_asset_type, + denom_value, + usage, + &mut input, + &mut output, + ) + .await; + break; + } + _ = self + .query_allowed_conversion( + client.clone(), + asset_type, + &mut conversions, + ) + .await; + if let (Some((conv, _wit, usage)), false) = + (conversions.get_mut(&asset_type), at_target_asset_type) + { + println!( + "converting latest asset type to target asset type..." + ); + // Not at the target asset type, yes at the latest asset + // type. Apply inverse conversion to get + // from latest asset type to the target + // asset type. + self.apply_conversion( + &client, + conv.clone(), + target_asset_type, + denom_value, + usage, + &mut input, + &mut output, + ) + .await; + } else { + // At the target asset type. Then move component over to + // output. + + let mut comp = MaspAmount::default(); + for ((e, key), val) in input.iter() { + if *key == token_addr { + comp.insert((*e, key.clone()), *val); + } + } + output += Amount::from(&comp); + // Strike from input to avoid repeating computation + input -= comp; + } } } (output, conversions) @@ -1180,10 +1317,11 @@ impl ShieldedContext { // The amount contributed by this note before conversion let pre_contr = Amount::from_pair(note.asset_type, note.value) .expect("received note has invalid value or asset type"); + let input = self.decode_all_amounts(&client, pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( client.clone(), - pre_contr, + input, target_epoch, conversions.clone(), ) @@ -1301,9 +1439,10 @@ impl ShieldedContext { .await?; // Establish connection with which to do exchange rate queries let client = HttpClient::new(ledger_address.clone()).unwrap(); + let amount = self.decode_all_amounts(&client, amt).await; // Finally, exchange the balance to the transaction's epoch Ok(( - self.compute_exchanged_amount(client, amt, ep, HashMap::new()) + self.compute_exchanged_amount(client, amount, ep, HashMap::new()) .await .0, ep, @@ -1318,16 +1457,26 @@ impl ShieldedContext { client: HttpClient, amt: Amount, target_epoch: Epoch, - ) -> MaspDenominatedAmount { - let mut res = Amount::zero(); + ) -> HashMap { + let mut res = HashMap::new(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client.clone(), *asset_type).await; // Only assets with the target timestamp count match decoded { - Some((addr, sub, denom, epoch)) if epoch == target_epoch => { - res += &Amount::from_pair((addr, sub, denom), *val).unwrap() + Some(asset_type @ (_, _, _, epoch)) + if epoch == target_epoch => + { + decode_component( + asset_type, + *val, + &mut res, + |address, sub_prefix, _| TokenAddress { + address, + sub_prefix, + }, + ); } _ => {} } @@ -1335,25 +1484,37 @@ impl ShieldedContext { res } + // TODO :: Panics if we ever switch to an i128 in the masp crate /// Convert an amount whose units are AssetTypes to one whose units are /// Addresses that they decode to. pub async fn decode_all_amounts( &mut self, - client: HttpClient, + client: &HttpClient, amt: Amount, - ) -> Amount<(Address, Option, MaspDenom, Epoch)> { - let mut res = Amount::zero(); + ) -> MaspAmount { + let mut res = HashMap::default(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = - self.decode_asset_type(client.clone(), *asset_type).await; - // Only assets with the target timestamp count - if let Some((addr, sub, denom, epoch)) = decoded { - res += - &Amount::from_pair((addr, sub, denom, epoch), *val).unwrap() + if let Some(decoded) = + self.decode_asset_type(client.clone(), *asset_type).await + { + decode_component( + decoded, + *val, + &mut res, + |address, sub_prefix, epoch| { + ( + epoch, + TokenAddress { + address, + sub_prefix, + }, + ) + }, + ) } } - res + MaspAmount(res.into_iter().map(|(k, v)| (k, v.change())).collect()) } } @@ -1367,7 +1528,8 @@ fn convert_amount( let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { - let asset_type = make_asset_type(epoch, token, sub_prefix, denom); + let asset_type = + make_asset_type(Some(epoch), token, sub_prefix, denom); // Combine the value and unit into one amount amount += Amount::from_nonnegative(asset_type, denom.denominate(val)) @@ -1630,29 +1792,22 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { args.amount = InputAmount::Validated(validated_amount); args.tx.fee_amount = InputAmount::Validated(validate_fee); + let token_addr = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }; match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { if balance < validated_amount.amount { - let balance_amount = format_denominated_amount( - &client, - &token, - &sub_prefix, - balance, - ) - .await; + let balance_amount = + format_denominated_amount(&client, &token_addr, balance) + .await; eprintln!( - "The balance of the source {} of token {}{} is lower than \ + "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, - token, - validated_amount, - args.sub_prefix - .as_ref() - .map(|s| format!("/{}", s)) - .unwrap_or_default(), - balance_amount + source, token_addr, validated_amount, balance_amount ); if !args.tx.force { safe_exit(1) @@ -1661,13 +1816,8 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { } None => { eprintln!( - "No balance found for the source {} of token {}{}", - source, - token, - args.sub_prefix - .as_ref() - .map(|s| format!("/{}", s)) - .unwrap_or_default(), + "No balance found for the source {} of token {}", + source, token_addr, ); if !args.tx.force { safe_exit(1) @@ -1854,32 +2004,24 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { { Some(balance) => { if balance < args.amount { + let token_addr = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }; let formatted_amount = format_denominated_amount( &client, - &token, - &sub_prefix, + &token_addr, args.amount, ) .await; - let formatted_balance = format_denominated_amount( - &client, - &token, - &sub_prefix, - balance, - ) - .await; + let formatted_balance = + format_denominated_amount(&client, &token_addr, balance) + .await; eprintln!( - "The balance of the source {} of token {}{} is lower than \ + "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, - token, - args.sub_prefix - .as_ref() - .map(|s| format!("/{}", s)) - .unwrap_or_default(), - formatted_amount, - formatted_balance + source, token_addr, formatted_amount, formatted_balance ); if !args.tx.force { safe_exit(1) @@ -2134,7 +2276,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { "yay" => { if let Some(pgf) = args.proposal_pgf { let splits = pgf.trim().split_ascii_whitespace(); - let address_iter = splits.clone().into_iter().step_by(2); + let address_iter = splits.clone().step_by(2); let cap_iter = splits.into_iter().skip(1).step_by(2); let mut set = HashSet::new(); for (address, cap) in @@ -3250,3 +3392,66 @@ pub async fn submit_tx( parsed } + +fn decode_component( + (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), + val: i64, + res: &mut HashMap, + mk_key: F, +) where + F: FnOnce(Address, Option, Epoch) -> K, + K: Eq + std::hash::Hash, +{ + let decoded_amount = token::Amount::from_uint( + u64::try_from(val).expect("negative cash does not exist"), + denom as u8, + ) + .unwrap(); + res.entry(mk_key(addr, sub, epoch)) + .and_modify(|val| *val += decoded_amount) + .or_insert(decoded_amount); +} + +#[cfg(test)] +mod test_tx { + + use namada::types::address::testing::gen_established_address; + use namada::types::storage::DbKeySeg; + + use super::*; + + #[test] + fn test_masp_add_amount() { + let address_1 = gen_established_address(); + let prefix_1: Key = DbKeySeg::StringSeg("eth_seg".into()).into(); + let prefix_2: Key = DbKeySeg::StringSeg("crypto_kitty".into()).into(); + let denom_1 = MaspDenom::One; + let denom_2 = MaspDenom::Three; + let epoch = Epoch::default(); + let _masp_amount = MaspAmount::default(); + + let asset_base = make_asset_type( + Some(epoch), + &address_1, + &Some(prefix_1.clone()), + denom_1, + ); + let _asset_denom = + make_asset_type(Some(epoch), &address_1, &Some(prefix_1), denom_2); + let _asset_prefix = + make_asset_type(Some(epoch), &address_1, &Some(prefix_2), denom_1); + + let _amount_base = + Amount::from_pair(asset_base, 16).expect("Test failed"); + let _amount_denom = + Amount::from_pair(asset_base, 2).expect("Test failed"); + let _amount_prefix = + Amount::from_pair(asset_base, 4).expect("Test failed"); + + // masp_amount += amount_base; + // assert_eq!(masp_amount.get((epoch,)), Uint::zero()); + // Amount::from_pair(atype, amount) + // MaspDenom::One + // assert_eq!(zero.abs(), Uint::zero()); + } +} diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 7cc997684e..df675c6c53 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,6 +1,6 @@ //! A basic fungible token -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use std::str::FromStr; @@ -689,6 +689,16 @@ impl MaspDenom { let amount = amount.into(); amount.raw.0[*self as usize] } + + /// Get the corresponding u64 word from the input uint256. + pub fn denominate_i64(&self, amount: &Change) -> i64 { + let val = amount.abs().0[*self as usize] as i64; + if Change::is_negative(amount) { + -val + } else { + val + } + } } /// Key segment for a balance key @@ -705,6 +715,54 @@ pub const CONVERSION_KEY_PREFIX: &str = "conv"; pub const PIN_KEY_PREFIX: &str = "pin-"; const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; +/// A fully qualified (multi-) token address. +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct TokenAddress { + /// The address of the (multi-) token + pub address: Address, + /// If it is a mutli-token, this indicates the sub-token. + pub sub_prefix: Option, +} + +impl TokenAddress { + /// A function for displaying a [`TokenAddress`]. Takes a + /// human readable name of the token as input. + pub fn format_with_alias(&self, alias: &str) -> String { + format!( + "{}{}", + alias, + self.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() + ) + } +} + +impl Display for TokenAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let formatted = format!( + "{}{}", + self.address, + self.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() + ); + f.write_str(&formatted) + } +} + /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { Key::from(token_addr.to_db_key()) diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 39d0839c71..6698a2e8cf 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -2,7 +2,7 @@ //! An unsigned 256 integer type. Used for, among other things, //! the backing type of token amounts. use std::cmp::Ordering; -use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub}; +use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; @@ -111,6 +111,11 @@ impl I256 { self.0 == Uint::zero() } + /// Gives the zero value of an I256 + pub fn zero() -> I256 { + Self(Uint::zero()) + } + /// Get a string representation of `self` as a /// native token amount. pub fn to_string_native(&self) -> String { @@ -243,6 +248,12 @@ impl Sub for I256 { } } +impl SubAssign for I256 { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + impl Mul for I256 { type Output = Self; @@ -289,6 +300,12 @@ impl From for I256 { } } +impl std::iter::Sum for I256 { + fn sum>(iter: I) -> Self { + iter.fold(I256::zero(), |acc, amt| acc + amt) + } +} + impl TryFrom for i128 { type Error = std::io::Error; From e2189991f13197b21e9d1b2fa54fa1f7d1c78f6d Mon Sep 17 00:00:00 2001 From: mariari Date: Thu, 4 May 2023 20:23:31 +0800 Subject: [PATCH 39/69] Remove all references to Masp::Amount out of RPC With this, the amounts code compiles with RPC not referencing the base namada amounts --- apps/src/lib/client/rpc.rs | 328 +++++++++++--------------- apps/src/lib/client/tx.rs | 50 ++-- core/src/ledger/storage/wl_storage.rs | 6 +- core/src/types/token.rs | 2 +- shared/src/ledger/vp_host_fns.rs | 10 +- tests/src/e2e/ledger_tests.rs | 1 - 6 files changed, 173 insertions(+), 224 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7dcb7eb07b..49bcbd5fec 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -18,7 +18,6 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::primitives::ViewingKey; use masp_primitives::sapling::Node; -use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; @@ -340,7 +339,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { }; tfer_delta.values().cloned().any( |MaspChange { ref asset, change }| { - check((asset, &token::Amount::from(change))) + check((asset, &change.into())) }, ) || shielded_accounts .values() @@ -367,12 +366,8 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - format_denominated_amount( - &client, - asset, - token::Amount::from(change), - ) - .await, + format_denominated_amount(&client, asset, change.into(),) + .await, asset.format_with_alias(&token_alias) ); } @@ -611,8 +606,9 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Establish connection with which to do exchange rate queries let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Print the token balances by payment address + let pinned_error = Err(PinnedBalanceError::InvalidViewingKey); for owner in owners { - let mut balance = Err(PinnedBalanceError::InvalidViewingKey); + let mut balance = pinned_error.clone(); // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { @@ -624,12 +620,12 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { vk, ) .await; - if balance != Err(PinnedBalanceError::InvalidViewingKey) { + if balance != pinned_error { break; } } // If a suitable viewing key was not found, then demand it from the user - if balance == Err(PinnedBalanceError::InvalidViewingKey) { + if balance == pinned_error { print!("Enter the viewing key for {}: ", owner); io::stdout().flush().unwrap(); let mut vk_str = String::new(); @@ -664,43 +660,28 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { (Ok((balance, epoch)), Some(token), sub_prefix) => { let token = ctx.get(token); let token_alias = lookup_alias(ctx, &token); - let mut total_balance = token::Amount::default(); - for denom in MaspDenom::iter() { - // Extract and print only the specified token from the total - let (_asset_type, value) = value_by_address( - &balance, - token.clone(), - sub_prefix, - denom, - epoch, - ); - total_balance += token::Amount::from_masp_denominated( - value as u64, - denom, - ); - } + + let token_address = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix + .map(|string| Key::parse(string).unwrap()), + }; + + let total_balance = balance[&(epoch, token_address.clone())]; + if total_balance.is_zero() { println!( "Payment address {} was consumed during epoch {}. \ - Received no shielded {}{}", + Received no shielded {}", owner, epoch, - token_alias, - sub_prefix - .map(|k| format!("/{}", k)) - .unwrap_or_default() + token_address.format_with_alias(&token_alias) ); } else { - let token_addr = TokenAddress { - address: token, - sub_prefix: sub_prefix - .as_ref() - .map(|k| Key::parse(k).unwrap()), - }; let formatted = format_denominated_amount( &client, - &token_addr, - total_balance, + &token_address, + total_balance.into(), ) .await; println!( @@ -709,18 +690,17 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { owner, epoch, formatted, - token_addr.format_with_alias(&token_alias), + token_address.format_with_alias(&token_alias), ); } } (Ok((balance, epoch)), None, _) => { let mut found_any = false; - // Print balances by human-readable token names - let balance = ctx - .shielded - .decode_amount(client.clone(), balance, epoch) - .await; - for (token_addr, value) in balance.iter() { + + for ((_, token_addr), value) in balance + .iter() + .filter(|((token_epoch, _), _)| *token_epoch == epoch) + { if !found_any { println!( "Payment address {} was consumed during epoch {}. \ @@ -729,9 +709,12 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { ); found_any = true; } - let formatted = - format_denominated_amount(&client, token_addr, *value) - .await; + let formatted = format_denominated_amount( + &client, + token_addr, + (*value).into(), + ) + .await; let token_alias = tokens .get(&token_addr.address) .map(|a| a.to_string()) @@ -961,19 +944,6 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { } } -/// Get the component of the given amount corresponding to the given token -pub fn value_by_address( - amt: &masp_primitives::transaction::components::Amount, - token: Address, - sub_prefix: Option<&String>, - denom: MaspDenom, - epoch: Epoch, -) -> (AssetType, i64) { - // Compute the unique asset identifier from the token address - let asset_type = make_asset_type(Some(epoch), &token, &sub_prefix, denom); - (asset_type, amt[&asset_type]) -} - /// Query token shielded balance(s) pub async fn query_shielded_balance( ctx: &mut Context, @@ -1012,61 +982,48 @@ pub async fn query_shielded_balance( match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { - let mut total_balance = token::Amount::zero(); let token = ctx.get(&token); - for denom in MaspDenom::iter() { - // Query the multi-asset balance at the given spending key - let viewing_key = - ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance: Amount = if no_conversions { - ctx.shielded - .compute_shielded_balance(&viewing_key) - .expect("context should contain viewing key") - } else { - ctx.shielded - .compute_exchanged_balance( - client.clone(), - &viewing_key, - epoch, - ) - .await - .expect("context should contain viewing key") - }; - // Compute the unique asset identifier from the token address - let asset_type = make_asset_type( - Some(epoch), - &token, - &args.sub_prefix, - denom, - ); - total_balance += token::Amount::from_masp_denominated( - balance[&asset_type] as u64, - denom, - ); - } + + // Query the multi-asset balance at the given spending key + let viewing_key = + ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; + let balance: MaspAmount = if no_conversions { + ctx.shielded + .compute_shielded_balance(&client, &viewing_key) + .await + .expect("context should contain viewing key") + } else { + ctx.shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key") + }; let token_alias = lookup_alias(ctx, &token); + + let token_address = TokenAddress { + address: token, + sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), + }; + + let total_balance = balance[&(epoch, token_address.clone())]; if total_balance.is_zero() { println!( - "No shielded {}{} balance found for given key", - token_alias, - args.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default(), + "No shielded {} balance found for given key", + token_address.format_with_alias(&token_alias) ); } else { - let token_address = TokenAddress { - address: token, - sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), - }; println!( "{}: {}", token_address.format_with_alias(&token_alias), format_denominated_amount( &client, &token_address, - total_balance + token::Amount::from(total_balance) ) .await ); @@ -1081,7 +1038,8 @@ pub async fn query_shielded_balance( let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { ctx.shielded - .compute_shielded_balance(&viewing_key) + .compute_shielded_balance(&client, &viewing_key) + .await .expect("context should contain viewing key") } else { ctx.shielded @@ -1093,11 +1051,11 @@ pub async fn query_shielded_balance( .await .expect("context should contain viewing key") }; - for (asset_type, value) in balance.components() { - if !balances.contains_key(asset_type) { - balances.insert(*asset_type, Vec::new()); + for (key, value) in balance.iter() { + if !balances.contains_key(key) { + balances.insert(key.clone(), Vec::new()); } - balances.get_mut(asset_type).unwrap().push((fvk, *value)); + balances.get_mut(key).unwrap().push((fvk, *value)); } } @@ -1108,42 +1066,32 @@ pub async fn query_shielded_balance( // TODO Implement a function for this let mut balance_map = HashMap::new(); - for (asset_type, balances) in balances { - let decoded = ctx - .shielded - .decode_asset_type(client.clone(), asset_type) - .await; - - match decoded { - Some((addr, sub_prefix, denom, asset_epoch)) - if asset_epoch == epoch => - { - // remove this from here, should not be making the - // hashtable creation any uglier - if balances.is_empty() { - println!( - "No shielded {} balance found for any wallet \ - key", - asset_type - ); - } - let asset_type = (addr, sub_prefix); - for (fvk, value) in balances { - let token_value = - token::Amount::from_masp_denominated( - value as u64, - denom, - ); - balance_map - .entry((fvk, asset_type.clone())) - .and_modify(|value_2| *value_2 += token_value) - .or_insert(token_value); - } + for ((asset_epoch, token_addr), balances) in balances { + if asset_epoch == epoch { + // remove this from here, should not be making the + // hashtable creation any uglier + if balances.is_empty() { + println!( + "No shielded {} balance found for any wallet key", + &token_addr + ); } - _ => {} - }; + for (fvk, value) in balances { + balance_map.insert((fvk, token_addr.clone()), value); + } + } } - for ((fvk, (addr, sub_prefix)), token_balance) in balance_map { + for ( + ( + fvk, + TokenAddress { + address: addr, + sub_prefix, + }, + ), + token_balance, + ) in balance_map + { read_tokens .entry(addr.clone()) .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) @@ -1164,7 +1112,7 @@ pub async fn query_shielded_balance( let formatted = format_denominated_amount( &client, &token_address, - token_balance, + token_balance.into(), ) .await; println!(" {}, owned by {}", formatted, fvk); @@ -1219,42 +1167,36 @@ pub async fn query_shielded_balance( token_address.format_with_alias(&token_alias), ); for fvk in viewing_keys { - let mut balance = token::Amount::default(); - for denom in MaspDenom::iter() { - let asset_type = make_asset_type( - Some(epoch), - &token, - &args.sub_prefix, - denom, - ); - // Query the multi-asset balance at the given spending key - let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; - let denom_balance = if no_conversions { - ctx.shielded - .compute_shielded_balance(&viewing_key) - .expect("context should contain viewing key") - } else { - ctx.shielded - .compute_exchanged_balance( - client.clone(), - &viewing_key, - epoch, - ) - .await - .expect("context should contain viewing key") - }; - if denom_balance[&asset_type] != 0 { - balance += token::Amount::from_masp_denominated( - denom_balance[&asset_type] as u64, - denom, - ); + // Query the multi-asset balance at the given spending key + let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; + let balance = if no_conversions { + ctx.shielded + .compute_shielded_balance(&client, &viewing_key) + .await + .expect("context should contain viewing key") + } else { + ctx.shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key") + }; + + for ((_, address), val) in balance.iter() { + if !val.is_zero() { found_any = true; } + let formatted = format_denominated_amount( + &client, + address, + (*val).into(), + ) + .await; + println!(" {}, owned by {}", formatted, fvk); } - let formatted = - format_denominated_amount(&client, &token_address, balance) - .await; - println!(" {}, owned by {}", formatted, fvk); } if !found_any { println!( @@ -1268,19 +1210,16 @@ pub async fn query_shielded_balance( // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance; if no_conversions { - balance = ctx + let balance = ctx .shielded - .compute_shielded_balance(&viewing_key) + .compute_shielded_balance(&client, &viewing_key) + .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = - ctx.shielded.decode_all_amounts(&client, balance).await; - print_decoded_balance_with_epoch(ctx, &client, decoded_balance) - .await; + print_decoded_balance_with_epoch(ctx, &client, balance).await; } else { - balance = ctx + let balance = ctx .shielded .compute_exchanged_balance( client.clone(), @@ -1290,11 +1229,7 @@ pub async fn query_shielded_balance( .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded - .decode_amount(client.clone(), balance, epoch) - .await; - print_decoded_balance(ctx, &client, decoded_balance).await; + print_decoded_balance(ctx, &client, balance, epoch).await; } } } @@ -1303,17 +1238,22 @@ pub async fn query_shielded_balance( pub async fn print_decoded_balance( ctx: &mut Context, client: &HttpClient, - decoded_balance: HashMap, + decoded_balance: MaspAmount, + epoch: Epoch, ) { if decoded_balance.is_empty() { println!("No shielded balance found for given key"); } else { - for (token_addr, amount) in decoded_balance { + for ((_, token_addr), amount) in decoded_balance + .iter() + .filter(|((token_epoch, _), _)| *token_epoch == epoch) + { println!( "{} : {}", token_addr .format_with_alias(&lookup_alias(ctx, &token_addr.address)), - format_denominated_amount(client, &token_addr, amount).await, + format_denominated_amount(client, token_addr, (*amount).into()) + .await, ); } } @@ -1329,7 +1269,7 @@ pub async fn print_decoded_balance_with_epoch( println!("No shielded balance found for given key"); } for ((epoch, token_addr), value) in decoded_balance.iter() { - let asset_value = token::Amount::from(*value); + let asset_value = (*value).into(); let alias = tokens .get(&token_addr.address) .map(|a| a.to_string()) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 712c0382e3..9c2b6d3b7c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -499,7 +499,7 @@ fn cloned_pair((a, b): (&T, &U)) -> (T, U) { } /// Errors that can occur when trying to retrieve pinned transaction -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub enum PinnedBalanceError { /// No transaction has yet been pinned to the given payment address NoTransactionPinned, @@ -519,7 +519,9 @@ pub struct MaspChange { pub change: token::Change, } -#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Default)] +#[derive( + BorshSerialize, BorshDeserialize, Debug, Clone, Default, PartialEq, Eq, +)] pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); impl std::ops::Deref for MaspAmount { @@ -1017,7 +1019,11 @@ impl ShieldedContext { /// Compute the total unspent notes associated with the viewing key in the /// context. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub fn compute_shielded_balance(&self, vk: &ViewingKey) -> Option { + pub async fn compute_shielded_balance( + &mut self, + client: &HttpClient, + vk: &ViewingKey, + ) -> Option { // Cannot query the balance of a key that's not in the map if !self.pos_map.contains_key(vk) { return None; @@ -1038,7 +1044,7 @@ impl ShieldedContext { .expect("found note with invalid value or asset type"); } } - Some(val_acc) + Some(self.decode_all_amounts(client, val_acc).await) } /// Query the ledger for the decoding of the given asset type and cache it @@ -1107,21 +1113,21 @@ impl ShieldedContext { client: HttpClient, vk: &ViewingKey, target_epoch: Epoch, - ) -> Option { + ) -> Option { // First get the unexchanged balance - if let Some(balance) = self.compute_shielded_balance(vk) { - let balance = self.decode_all_amounts(&client, balance).await; - // And then exchange balance into current asset types - Some( - self.compute_exchanged_amount( - client, + if let Some(balance) = self.compute_shielded_balance(&client, vk).await + { + let exchanged_amount = self + .compute_exchanged_amount( + client.clone(), balance, target_epoch, HashMap::new(), ) .await - .0, - ) + .0; + // And then exchange balance into current asset types + Some(self.decode_all_amounts(&client, exchanged_amount).await) } else { None } @@ -1432,7 +1438,7 @@ impl ShieldedContext { ledger_address: &TendermintAddress, owner: PaymentAddress, viewing_key: &ViewingKey, - ) -> Result<(Amount, Epoch), PinnedBalanceError> { + ) -> Result<(MaspAmount, Epoch), PinnedBalanceError> { // Obtain the balance that will be exchanged let (amt, ep) = Self::compute_pinned_balance(ledger_address, owner, viewing_key) @@ -1441,12 +1447,16 @@ impl ShieldedContext { let client = HttpClient::new(ledger_address.clone()).unwrap(); let amount = self.decode_all_amounts(&client, amt).await; // Finally, exchange the balance to the transaction's epoch - Ok(( - self.compute_exchanged_amount(client, amount, ep, HashMap::new()) - .await - .0, - ep, - )) + let computed_amount = self + .compute_exchanged_amount( + client.clone(), + amount, + ep, + HashMap::new(), + ) + .await + .0; + Ok((self.decode_all_amounts(&client, computed_amount).await, ep)) } /// Convert an amount whose units are AssetTypes to one whose units are diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 7e6eaf7584..88d44bf871 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -360,14 +360,14 @@ where // try to read from the write log first let (log_val, _gas) = self.write_log().read(key); match log_val { - Some(&write_log::StorageModification::Write { ref value }) => { + Some(write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) } Some(&write_log::StorageModification::Delete) => Ok(None), - Some(&write_log::StorageModification::InitAccount { + Some(write_log::StorageModification::InitAccount { ref vp_code_hash, }) => Ok(Some(vp_code_hash.to_vec())), - Some(&write_log::StorageModification::Temp { ref value }) => { + Some(write_log::StorageModification::Temp { ref value }) => { Ok(Some(value.clone())) } None => { diff --git a/core/src/types/token.rs b/core/src/types/token.rs index df675c6c53..8b1d3e3101 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -681,7 +681,7 @@ impl From for MaspDenom { impl MaspDenom { /// Iterator over the possible denominations pub fn iter() -> impl Iterator { - (0u8..3).into_iter().map(Self::from) + (0u8..3).map(Self::from) } /// Get the corresponding u64 word from the input uint256. diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index b0e4ce7118..37f0118bcb 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -67,14 +67,14 @@ where let (log_val, gas) = write_log.read_pre(key); add_gas(gas_meter, gas)?; match log_val { - Some(&write_log::StorageModification::Write { ref value }) => { + Some(write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) } Some(&write_log::StorageModification::Delete) => { // Given key has been deleted Ok(None) } - Some(&write_log::StorageModification::InitAccount { + Some(write_log::StorageModification::InitAccount { ref vp_code_hash, }) => { // Read the VP of a new account @@ -109,14 +109,14 @@ where let (log_val, gas) = write_log.read(key); add_gas(gas_meter, gas)?; match log_val { - Some(&write_log::StorageModification::Write { ref value }) => { + Some(write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) } Some(&write_log::StorageModification::Delete) => { // Given key has been deleted Ok(None) } - Some(&write_log::StorageModification::InitAccount { + Some(write_log::StorageModification::InitAccount { ref vp_code_hash, }) => { // Read the VP code hash of a new account @@ -146,7 +146,7 @@ pub fn read_temp( let (log_val, gas) = write_log.read(key); add_gas(gas_meter, gas)?; match log_val { - Some(&write_log::StorageModification::Temp { ref value }) => { + Some(write_log::StorageModification::Temp { ref value }) => { Ok(Some(value.clone())) } None => Ok(None), diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2418deb884..848bb38a88 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2599,7 +2599,6 @@ fn ledger_many_txs_in_a_block() -> Result<()> { // We collect to run the threads in parallel. #[allow(clippy::needless_collect)] let tasks: Vec> = (0..4) - .into_iter() .map(|_| { let test = Arc::clone(&test); let validator_one_rpc = Arc::clone(&validator_one_rpc); From f6d0f6285eda2af543d63d4cbba3117fca31be96 Mon Sep 17 00:00:00 2001 From: mariari Date: Fri, 5 May 2023 17:03:16 +0800 Subject: [PATCH 40/69] Gensis Parsing config issue solved We made sure that all parameters in the config file have a DENOM, there is NO DEFAULT!!!! Furhter we renamed Dot to DOT, please write configs correctly We also derived serialization for dec, meaning that all decimals that were unquoted now have to be quoted, making configuration a bit more annoying --- core/Cargo.toml | 2 ++ core/src/types/dec.rs | 49 ++++++++++++++++++++++++++++-- genesis/dev.toml | 25 +++++++++------ genesis/e2e-tests-single-node.toml | 35 ++++++++++++--------- 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index a074390bcc..806ac757f4 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -113,6 +113,8 @@ rand = {version = "0.8"} rand_core = {version = "0.6"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} +toml = {version = "=0.5.9"} + [build-dependencies] tonic-build = "0.6.0" diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index e725f2f3a5..bbdf431009 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -36,16 +36,18 @@ pub type Result = std::result::Result; Default, BorshSerialize, BorshDeserialize, - Serialize, - Deserialize, BorshSchema, PartialEq, + Serialize, + Deserialize, Eq, PartialOrd, Ord, Debug, Hash, )] +#[serde(try_from = "String")] +#[serde(into = "String")] pub struct Dec(pub Uint); impl std::ops::Deref for Dec { @@ -161,6 +163,14 @@ impl FromStr for Dec { } } +impl TryFrom for Dec { + type Error = Error; + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + impl From for Dec { fn from(amt: Amount) -> Self { Self( @@ -186,6 +196,12 @@ impl From for Dec { } } +impl From for String { + fn from(value: Dec) -> String { + value.to_string() + } +} + impl Add for Dec { type Output = Self; @@ -306,6 +322,19 @@ mod test_dec { use super::*; use crate::types::token::{Amount, Change}; + #[derive(Debug, Serialize, Deserialize)] + struct SerializerTest { + dec: Dec, + } + + #[test] + fn dump_toml() { + let serializer = SerializerTest { + dec: Dec::new(3, 0).unwrap(), + }; + println!("{:?}", toml::to_string(&serializer)); + } + /// Fill in tests later #[test] fn test_dec_basics() { @@ -426,4 +455,20 @@ mod test_dec { } assert!(Dec::from_str(&yuge).is_err()); } + + /// Test that parsing from string is correct. + #[test] + fn test_dec_from_serde() { + assert_eq!( + serde_json::from_str::(r#""0.667""#).expect("all good"), + Dec::from_str("0.667").expect("should work") + ); + + let dec = Dec::from_str("0.667").unwrap(); + assert_eq!( + dec, + serde_json::from_str::(&serde_json::to_string(&dec).unwrap()) + .unwrap() + ); + } } diff --git a/genesis/dev.toml b/genesis/dev.toml index 0aff92206f..620298db3d 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -17,9 +17,9 @@ non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_validator" # Commission rate for rewards -commission_rate = 0.05 +commission_rate = "0.05" # Maximum change per epoch in the commission rate -max_commission_rate_change = 0.01 +max_commission_rate_change = "0.01" # Public IP:port address net_address = "127.0.0.1:26656" @@ -27,6 +27,7 @@ net_address = "127.0.0.1:26656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" +denom = 8 vp = "vp_token" [token.NAM.balances] # In token balances, we can use: @@ -43,6 +44,7 @@ bertha = 1000000 [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" +denom = 8 vp = "vp_token" [token.BTC.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -52,6 +54,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" +denom = 18 vp = "vp_token" [token.ETH.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -61,6 +64,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" +denom = 10 vp = "vp_token" [token.DOT.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -70,6 +74,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" +denom = 6 vp = "vp_token" [token.schnitzel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -79,6 +84,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" +denom = 6 vp = "vp_token" [token.apfel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -88,6 +94,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" +denom = 6 public_key = "" vp = "vp_token" [token.kartoffel.balances] @@ -160,21 +167,21 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 21 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 1 +tm_votes_per_token = "1" # Reward for proposing a block. -block_proposer_reward = 0.125 +block_proposer_reward = "0.125" # Reward for voting on a block. -block_vote_reward = 0.1 +block_vote_reward = "0.1" # Maximum inflation rate per annum (10%) -max_inflation_rate = 0.1 +max_inflation_rate = "0.1" # Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = 0.6667 +target_staked_ratio = "0.6667" # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_min_slash_rate = 0.001 +duplicate_vote_min_slash_rate = "0.001" # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_min_slash_rate = 0.001 +light_client_attack_min_slash_rate = "0.001" # Governance parameters. [gov_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 6a1cc634f3..5fc2d4f231 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -5,7 +5,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" faucet_pow_difficulty = 1 -faucet_withdrawal_limit = "1_000" +faucet_withdrawal_limit = "1000" [validator.validator-0] # Validator's staked NAM at genesis. @@ -15,11 +15,11 @@ non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_validator" # Commission rate for rewards -commission_rate = 0.05 +commission_rate = "0.05" # Maximum change per epoch in the commission rate -max_commission_rate_change = 0.01 +max_commission_rate_change = "0.01" # Public IP:port address. -# We set the port to be the default+1000, so that if a local node was running at +# We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. net_address = "127.0.0.1:27656" @@ -27,6 +27,7 @@ net_address = "127.0.0.1:27656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" +denom = 6 vp = "vp_token" [token.NAM.balances] Albert = 1000000 @@ -42,6 +43,7 @@ faucet = 9223372036 [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" +denom = 8 vp = "vp_token" [token.BTC.balances] Albert = 1000000 @@ -52,6 +54,7 @@ faucet = 9223372036854 [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" +denom = 18 vp = "vp_token" [token.ETH.balances] Albert = 1000000 @@ -62,8 +65,9 @@ faucet = 9223372036854 [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" +denom = 10 vp = "vp_token" -[token.Dot.balances] +[token.DOT.balances] Albert = 1000000 Bertha = 1000000 Christel = 1000000 @@ -72,6 +76,7 @@ faucet = 9223372036854 [token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" +denom = 6 vp = "vp_token" [token.Schnitzel.balances] Albert = 1000000 @@ -82,6 +87,7 @@ faucet = 9223372036854 [token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" +denom = 6 vp = "vp_token" [token.Apfel.balances] Albert = 1000000 @@ -93,6 +99,7 @@ faucet = 9223372036854 [token.Kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" public_key = "" +denom = 6 vp = "vp_token" [token.Kartoffel.balances] Albert = 1000000 @@ -164,9 +171,9 @@ implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) epochs_per_year = 31_536_000 # The P gain factor in the Proof of Stake rewards controller -pos_gain_p = 0.1 +pos_gain_p = "0.1" # The D gain factor in the Proof of Stake rewards controller -pos_gain_d = 0.1 +pos_gain_d = "0.1" # Proof of stake parameters. [pos_params] @@ -179,21 +186,21 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 0.1 +tm_votes_per_token = "0.1" # Reward for proposing a block. -block_proposer_reward = 0.125 +block_proposer_reward = "0.125" # Reward for voting on a block. -block_vote_reward = 0.1 +block_vote_reward = "0.1" # Maximum inflation rate per annum (10%) -max_inflation_rate = 0.1 +max_inflation_rate = "0.1" # Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = 0.6667 +target_staked_ratio = "0.6667" # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_min_slash_rate = 0.001 +duplicate_vote_min_slash_rate = "0.001" # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_min_slash_rate = 0.001 +light_client_attack_min_slash_rate = "0.001" # Governance parameters. [gov_params] From 997ff52d993dd7a6167e7d6b699cd2c36d291e1d Mon Sep 17 00:00:00 2001 From: mariari Date: Fri, 5 May 2023 19:37:20 +0800 Subject: [PATCH 41/69] Improve dec display method Instead of showing all trailing zeros, we truncate the trailing 0's from being displayed --- core/src/types/dec.rs | 7 ++++++- core/src/types/token.rs | 11 ++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index bbdf431009..3c99c8610c 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -313,7 +313,12 @@ impl Display for Dec { str_pre.push_str(string.as_str()); string = str_pre; }; - f.write_str(&string) + let stripped_string = string.trim_end_matches(['.', '0']); + if stripped_string.is_empty() { + f.write_str("0") + } else { + f.write_str(stripped_string) + } } } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 8b1d3e3101..59413f84f3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -324,12 +324,9 @@ impl DenominatedAmount { impl Display for DenominatedAmount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = self.to_string_precise(); - let string = string.trim_end_matches(&['0', '.']); - if string.is_empty() { - f.write_str("0") - } else { - f.write_str(string) - } + let string = string.trim_end_matches(&['0']); + let string = string.trim_end_matches(&['.']); + f.write_str(string) } } @@ -704,7 +701,7 @@ impl MaspDenom { /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; /// Key segment for a denomination key -pub const DENOM_STORAGE_KEY: &str = "balance"; +pub const DENOM_STORAGE_KEY: &str = "denom"; /// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key From 8f7270a8793f6343a680d0864215410648fa14bd Mon Sep 17 00:00:00 2001 From: mariari Date: Fri, 5 May 2023 21:04:17 +0800 Subject: [PATCH 42/69] Fix u64 going to Dec from overflowing We simply convert to Dec before doing any shifting math --- core/src/types/dec.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 3c99c8610c..ae29e36702 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -185,7 +185,7 @@ impl From for Dec { impl From for Dec { fn from(num: u64) -> Self { - Self(Uint::from(num * 10u64.pow(POS_DECIMAL_PRECISION as u32))) + Self(Uint::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) } } @@ -410,6 +410,15 @@ mod test_dec { debug_assert_eq!(dec * chg, Change::from(-2809i64)); } + #[test] + fn test_into() { + assert_eq!( + Dec::from(u64::MAX), + Dec::from_str("18446744073709551615.000000000000") + .expect("only 104 bits") + ) + } + /// Test that parsing from string is correct. #[test] fn test_dec_from_string() { From 85a82b4e109c866cf2404fd3704ac4119f3899b2 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 9 May 2023 13:01:31 +0800 Subject: [PATCH 43/69] Temporary remove check for chain id verification Further temporarly change the denomination to be ignored. --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/init_chain.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21e402a594..8168c7f218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3960,6 +3960,7 @@ dependencies = [ "tendermint-proto 0.23.6", "test-log", "thiserror", + "toml", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 5bc3c4cfb4..7f44315706 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -52,11 +52,11 @@ where let errors = self.wl_storage.storage.chain_id.validate(genesis_bytes); use itertools::Itertools; - assert!( - errors.is_empty(), - "Chain ID validation failed: {}", - errors.into_iter().format(". ") - ); + // assert!( + // errors.is_empty(), + // "Chain ID validation failed: {}", + // errors.into_iter().format(". ") + // ); } #[cfg(feature = "dev")] let genesis = genesis::genesis(num_validators); From bfb81af754399f05b035b429c8a169837317f517 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 9 May 2023 23:07:53 +0800 Subject: [PATCH 44/69] Remove signed conversion errors Instead of using Amounts, we use Change instead. This prevents vp_token from overflowing --- core/src/types/uint.rs | 5 +++++ wasm/wasm_source/src/vp_token.rs | 34 ++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 6698a2e8cf..8e4bc2ec0d 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -152,6 +152,11 @@ impl I256 { fn canonical(self) -> Self { Self(self.0.canonical()) } + + /// the maximum I256 value + pub fn maximum() -> Self { + Self(MAX_SIGNED_VALUE) + } } impl From for I256 { diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 5b958e1222..0fd227fb3a 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -77,25 +77,33 @@ fn token_checks( } Some(owner) => { // accumulate the change - let pre: token::Amount = match owner { + let pre: token::Change = match owner { Address::Internal(InternalAddress::IbcMint) => { - token::Amount::max() + token::Change::maximum() } Address::Internal(InternalAddress::IbcBurn) => { - token::Amount::default() + token::Change::default() } - _ => ctx.read_pre(key)?.unwrap_or_default(), + _ => ctx + .read_pre::(key)? + .unwrap_or_default() + .change(), }; - let post: token::Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(token::Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.read_temp(key)?.unwrap_or_default() - } - _ => ctx.read_post(key)?.unwrap_or_default(), + let post: token::Change = match owner { + Address::Internal(InternalAddress::IbcMint) => ctx + .read_temp::(key)? + .map(|x| x.change()) + .unwrap_or_else(token::Change::maximum), + Address::Internal(InternalAddress::IbcBurn) => ctx + .read_temp::(key)? + .unwrap_or_default() + .change(), + _ => ctx + .read_post::(key)? + .unwrap_or_default() + .change(), }; - let this_change = post.change() - pre.change(); + let this_change = post - pre; change += this_change; // make sure that the spender approved the transaction if !(this_change.non_negative() From 79133e4f2a164b9279d0d155a89127cdf3987def Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 10 May 2023 12:09:38 +0800 Subject: [PATCH 45/69] Fix underflow issue in commisions --- core/src/types/dec.rs | 10 ++++++++++ wasm/wasm_source/src/tx_change_validator_commission.rs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index ae29e36702..da9e0d2d8a 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -114,6 +114,16 @@ impl Dec { pub fn to_uint(&self) -> Uint { self.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize) } + + /// Do subtraction of two [`Dec`]s If and only if the value is + /// greater + pub fn checked_sub(&self, other: &Self) -> Option { + if self > other { + Some(*self - *other) + } else { + None + } + } } impl FromStr for Dec { diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index e2a41bc69e..208a836cb0 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -175,7 +175,7 @@ mod tests { rate_pre: Dec, max_change: Dec, ) -> impl Strategy { - let min = cmp::max(rate_pre - max_change, Dec::zero()); + let min = rate_pre.checked_sub(&max_change).unwrap_or_default(); let max = cmp::min(rate_pre + max_change, Dec::one()); (arb_established_address(), arb_new_rate(min, max, rate_pre)).prop_map( |(validator, new_rate)| transaction::pos::CommissionChange { From ecb135c7d3302a5a3cbb3bfb2e11003ff8611c46 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 10 May 2023 18:47:39 +0800 Subject: [PATCH 46/69] Add a new destination for ibc rpc endpoint --- shared/src/ledger/queries/vp/token.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index 71ef3a52b4..cbad27005f 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -9,6 +9,7 @@ use crate::ledger::queries::RequestCtx; router! {TOKEN, ( "denomination" / [addr: Address] / [sub_prefix: opt Key] ) -> Option = denomination, + ( "denomination" / [addr: Address] / "ibc" / [_ibc_junk: String] ) -> Option = denomination_ibc, } /// Get the number of decimal places (in base 10) for a @@ -24,3 +25,19 @@ where { read_denom(ctx.wl_storage, &addr, sub_prefix.as_ref()) } + +// TODO Please fix this + +/// Get the number of decimal places (in base 10) for a +/// token specified by `addr`. +fn denomination_ibc( + ctx: RequestCtx<'_, D, H>, + addr: Address, + _ibc_junk: String, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_denom(ctx.wl_storage, &addr, None) +} From c09dd7e1e882e2633424b25f5acc5ca8038eed71 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 10 May 2023 18:50:54 +0800 Subject: [PATCH 47/69] Removed denominated from being called from user contracts --- wasm/wasm_source/src/vp_implicit.rs | 7 ++----- wasm/wasm_source/src/vp_user.rs | 7 ++----- wasm/wasm_source/src/vp_validator.rs | 7 ++----- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 3f1ea331f2..fd76f1bd97 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -143,15 +143,12 @@ fn validate_tx( // debit has to signed, credit doesn't let valid = change.non_negative() || *valid_sig; let sign = if change.non_negative() { "" } else { "-" }; - let denom_amount = token::Amount::from_change(change) - .denominated(token, sub_prefix.as_ref(), &ctx.pre()) - .unwrap(); debug_log!( - "token key: {}, change: {}{}, valid_sig: {}, valid \ + "token key: {}, change: {}{:?}, valid_sig: {}, valid \ modification: {}", key, sign, - denom_amount, + change, *valid_sig, valid ); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 1cbb2b4d94..63ae898099 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -116,14 +116,11 @@ fn validate_tx( // debit has to signed, credit doesn't let valid = change.non_negative() || addr == masp() || *valid_sig; - let denom_amount = token::Amount::from(change) - .denominated(token, sub_prefix.as_ref(), &ctx.pre()) - .unwrap(); debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {:?}, valid_sig: {}, valid \ modification: {}", key, - denom_amount, + change, *valid_sig, valid ); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 24169fb17d..0328fdd066 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -115,14 +115,11 @@ fn validate_tx( let change = post.change() - pre.change(); // debit has to signed, credit doesn't let valid = change.non_negative() || *valid_sig; - let amount = token::Amount::from(change) - .denominated(token, sub_prefix.as_ref(), &ctx.pre()) - .unwrap(); debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {:?}, valid_sig: {}, valid \ modification: {}", key, - amount, + change, *valid_sig, valid ); From 34e5be79944208b1b484bc7b0187247b3f46523a Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 11 May 2023 00:37:15 -0400 Subject: [PATCH 48/69] WIP fix `pos_bonds` --- tests/src/e2e/ledger_tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 848bb38a88..eba045f775 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1965,7 +1965,8 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Amount 5100 withdrawable starting from epoch ")?; + client + .exp_string("Amount 5100.000000 withdrawable starting from epoch ")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1987,7 +1988,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - let expected = "Amount 3200 withdrawable starting from epoch "; + let expected = "Amount 3200.000000 withdrawable starting from epoch "; let (_unread, matched) = client.exp_regex(&format!("{expected}.*\n"))?; let epoch_raw = matched.trim().split_once(expected).unwrap().1; let delegation_withdrawable_epoch = Epoch::from_str(epoch_raw).unwrap(); From fcaa11ac4682815c0f69376aae057e26b9571a1c Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 May 2023 01:30:02 -0400 Subject: [PATCH 49/69] refactor `Dec` to allow negative values --- core/src/types/dec.rs | 182 ++++++++++++++---- core/src/types/token.rs | 11 +- core/src/types/uint.rs | 26 ++- proof_of_stake/src/parameters.rs | 13 +- proof_of_stake/src/rewards.rs | 5 +- proof_of_stake/src/types.rs | 3 +- shared/src/ledger/inflation.rs | 6 +- .../src/tx_change_validator_commission.rs | 8 +- 8 files changed, 195 insertions(+), 59 deletions(-) diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index da9e0d2d8a..4681c9fbd5 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use super::token::NATIVE_MAX_DECIMAL_PLACES; use crate::types::token::{Amount, Change}; -use crate::types::uint::Uint; +use crate::types::uint::{Uint, I256}; /// The number of Dec places for PoS rational calculations pub const POS_DECIMAL_PRECISION: u8 = 12; @@ -48,10 +48,10 @@ pub type Result = std::result::Result; )] #[serde(try_from = "String")] #[serde(into = "String")] -pub struct Dec(pub Uint); +pub struct Dec(pub I256); impl std::ops::Deref for Dec { - type Target = Uint; + type Target = I256; fn deref(&self) -> &Self::Target { &self.0 @@ -67,37 +67,59 @@ impl std::ops::DerefMut for Dec { impl Dec { /// Division with truncation (TODO: better description) pub fn trunc_div(&self, rhs: &Self) -> Option { - self.0 - .fixed_precision_div(rhs, POS_DECIMAL_PRECISION) - .map(Self) + let is_neg = self.0.is_negative() ^ rhs.0.is_negative(); + let inner_uint = self.0.abs(); + let inner_rhs_uint = rhs.0.abs(); + match inner_uint + .fixed_precision_div(&inner_rhs_uint, POS_DECIMAL_PRECISION) + { + Some(res) => { + let res = I256::try_from(res).ok()?; + if is_neg { + Some(Self(-res)) + } else { + Some(Self(res)) + } + } + None => None, + } } /// The representation of 0 pub fn zero() -> Self { - Self(Uint::zero()) + Self(I256::zero()) } /// The representation of 1 pub fn one() -> Self { - Self(Uint::one() * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + Self(I256( + Uint::one() * Uint::exp10(POS_DECIMAL_PRECISION as usize), + )) } /// The representation of 2 pub fn two() -> Self { - Self( - (Uint::one() + Uint::one()) - * Uint::exp10(POS_DECIMAL_PRECISION as usize), - ) + Self::one() + Self::one() } /// Create a new [`Dec`] using a mantissa and a scale. - pub fn new(mantissa: u64, scale: u8) -> Option { + pub fn new(mantissa: i128, scale: u8) -> Option { if scale > POS_DECIMAL_PRECISION { None } else { - Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) - .checked_mul(Uint::from(mantissa)) - .map(Self) + let abs = u64::try_from(mantissa.abs()).ok()?; + match Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) + .checked_mul(Uint::from(abs)) + { + Some(res) => { + if mantissa.is_negative() { + Some(Self(-I256(res))) + } else { + Some(Self(I256(res))) + } + } + None => None, + } } } @@ -110,11 +132,20 @@ impl Dec { } } - /// Convert the Dec type into a Uint with truncation - pub fn to_uint(&self) -> Uint { + /// Convert the Dec type into a I256 with truncation + pub fn to_i256(&self) -> I256 { self.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize) } + /// Convert the Dec type into a Uint with truncation + pub fn to_uint(&self) -> Option { + if self.is_negative() { + None + } else { + Some(self.0.abs() / Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } + } + /// Do subtraction of two [`Dec`]s If and only if the value is /// greater pub fn checked_sub(&self, other: &Self) -> Option { @@ -124,17 +155,24 @@ impl Dec { None } } + + /// Return if the [`Dec`] is negative + pub fn is_negative(&self) -> bool { + self.0.is_negative() + } } impl FromStr for Dec { type Err = Error; fn from_str(s: &str) -> Result { - if s.starts_with('-') { - return Err(eyre!("Dec cannot be negative").into()); - } + let ((large, small), is_neg) = if let Some(strip) = s.strip_prefix('-') + { + (strip.split_once('.').unwrap_or((s, "0")), true) + } else { + (s.split_once('.').unwrap_or((s, "0")), false) + }; - let (large, small) = s.split_once('.').unwrap_or((s, "0")); let num_large = Uint::from_str_radix(large, 10).map_err(|e| { eyre!("Could not parse {} as an integer: {}", large, e) })?; @@ -169,7 +207,13 @@ impl FromStr for Dec { num_large ) })?; - Ok(Dec(int_part + decimal_part)) + let inner = I256::try_from(int_part + decimal_part) + .map_err(|e| eyre!("Could not convert Uint to I256: {}", e))?; + if is_neg { + Ok(Dec(-inner)) + } else { + Ok(Dec(inner)) + } } } @@ -183,25 +227,43 @@ impl TryFrom for Dec { impl From for Dec { fn from(amt: Amount) -> Self { - Self( - amt.raw_amount() - * Uint::exp10( + match I256::try_from(amt.raw_amount()).ok() { + Some(raw) => Self( + raw * Uint::exp10( (POS_DECIMAL_PRECISION - NATIVE_MAX_DECIMAL_PLACES) as usize, ), - ) + ), + None => Self::zero(), + } + } +} + +impl TryFrom for Dec { + type Error = Error; + + fn try_from(value: Uint) -> std::result::Result { + let i256 = I256::try_from(value) + .map_err(|e| eyre!("Could not convert Uint to I256: {}", e))?; + Ok(Self(i256 * Uint::exp10(POS_DECIMAL_PRECISION as usize))) } } impl From for Dec { fn from(num: u64) -> Self { - Self(Uint::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + Self(I256::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} + +impl From for Dec { + fn from(num: i128) -> Self { + Self(I256::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) } } // Is error handling needed for this? -impl From for Dec { - fn from(num: Uint) -> Self { +impl From for Dec { + fn from(num: I256) -> Self { Self(num * Uint::exp10(POS_DECIMAL_PRECISION as usize)) } } @@ -224,7 +286,7 @@ impl Add for Dec { type Output = Self; fn add(self, rhs: u64) -> Self::Output { - Self(self.0 + Uint::from(rhs)) + Self(self.0 + I256::from(rhs)) } } @@ -242,11 +304,19 @@ impl Sub for Dec { } } -impl Mul for Dec { - type Output = Uint; +// impl Mul for Dec { +// type Output = Uint; + +// fn mul(self, rhs: Uint) -> Self::Output { +// self.0 * rhs / Uint::exp10(POS_DECIMAL_PRECISION as usize) +// } +// } - fn mul(self, rhs: Uint) -> Self::Output { - self.0 * rhs / Uint::exp10(POS_DECIMAL_PRECISION as usize) +impl Mul for Dec { + type Output = Dec; + + fn mul(self, rhs: u64) -> Self::Output { + Self(self.0 * Uint::from(rhs)) } } @@ -262,7 +332,11 @@ impl Mul for Dec { type Output = Amount; fn mul(self, rhs: Amount) -> Self::Output { - (rhs * self.0) / 10u64.pow(POS_DECIMAL_PRECISION as u32) + if !self.is_negative() { + (rhs * self.0.abs()) / 10u64.pow(POS_DECIMAL_PRECISION as u32) + } else { + panic!("aaa"); + } } } @@ -311,7 +385,8 @@ impl Div for Dec { impl Display for Dec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut string = self.0.to_string(); + let is_neg = self.is_negative(); + let mut string = self.0.abs().to_string(); if string.len() > POS_DECIMAL_PRECISION as usize { let idx = string.len() - POS_DECIMAL_PRECISION as usize; string.insert(idx, '.'); @@ -326,6 +401,9 @@ impl Display for Dec { let stripped_string = string.trim_end_matches(['.', '0']); if stripped_string.is_empty() { f.write_str("0") + } else if is_neg { + let stripped_string = format!("-{}", stripped_string); + f.write_str(stripped_string.as_str()) } else { f.write_str(stripped_string) } @@ -360,21 +438,43 @@ mod test_dec { assert_eq!(Dec::new(1, 0).expect("Test failed"), Dec::one()); assert_eq!(Dec::new(2, 0).expect("Test failed"), Dec::two()); assert_eq!( - Dec(Uint::from(1653)), + Dec(I256(Uint::from(1653))), Dec::new(1653, POS_DECIMAL_PRECISION).expect("Test failed") ); assert_eq!( - Dec::new(123456789, 4).expect("Test failed").to_uint(), + Dec(I256::from(-48756)), + Dec::new(-48756, POS_DECIMAL_PRECISION).expect("Test failed") + ); + assert_eq!( + Dec::new(123456789, 4) + .expect("Test failed") + .to_uint() + .unwrap(), Uint::from(12345) ); assert_eq!( - Dec::new(123, 4).expect("Test failed").to_uint(), + Dec::new(-123456789, 4).expect("Test failed").to_i256(), + I256::from(-12345) + ); + assert_eq!( + Dec::new(123, 4).expect("Test failed").to_uint().unwrap(), Uint::zero() ); assert_eq!( - Dec::from_str("4876.3855").expect("Test failed").to_uint(), + Dec::new(123, 4).expect("Test failed").to_i256(), + I256::zero() + ); + assert_eq!( + Dec::from_str("4876.3855") + .expect("Test failed") + .to_uint() + .unwrap(), Uint::from(4876) ); + assert_eq!( + Dec::from_str("4876.3855").expect("Test failed").to_i256(), + I256::from(4876) + ); // Fixed precision division is more thoroughly tested for the `Uint` // type. These are sanity checks that the precision is correct. diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 59413f84f3..bd01e1917e 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -449,8 +449,15 @@ impl From for Amount { impl From for Amount { fn from(dec: Dec) -> Amount { - Amount { - raw: dec.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize), + if !dec.is_negative() { + Amount { + raw: dec.0.abs() / Uint::exp10(POS_DECIMAL_PRECISION as usize), + } + } else { + panic!( + "The Dec value is negative and cannot be multiplied by an \ + Amount" + ) } } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 8e4bc2ec0d..1749af7b51 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -81,9 +81,18 @@ const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); /// A signed 256 big integer. #[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, + Copy, + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] -pub struct I256(Uint); +pub struct I256(pub Uint); impl I256 { /// Check if the amount is not negative (greater @@ -259,6 +268,7 @@ impl SubAssign for I256 { } } +// NOTE: watch the overflow impl Mul for I256 { type Output = Self; @@ -269,6 +279,18 @@ impl Mul for I256 { } } +impl Mul for I256 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + if rhs.is_negative() { + -self * rhs.abs() + } else { + self * rhs.abs() + } + } +} + impl Div for I256 { type Output = Self; diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index a67878b8d1..c3bcd146a3 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -113,9 +113,11 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - let max_total_voting_power = self.tm_votes_per_token - * Uint::from(TOKEN_MAX_AMOUNT) - * Uint::from(self.max_validator_slots); + let max_total_voting_power = (self.tm_votes_per_token + * TOKEN_MAX_AMOUNT + * self.max_validator_slots) + .to_uint() + .expect("Cannot fail"); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { @@ -178,7 +180,7 @@ pub mod testing { // `unbonding_len` > `pipeline_len` unbonding_len in pipeline_len + 1..pipeline_len + 8, pipeline_len in Just(pipeline_len), - tm_votes_per_token in 1..10_001_u64) + tm_votes_per_token in 1..10_001_i128) -> PosParams { PosParams { max_validator_slots, @@ -195,6 +197,7 @@ pub mod testing { /// Get an arbitrary rate - a Dec value between 0 and 1 inclusive, with /// some fixed precision pub fn arb_rate() -> impl Strategy { - (0..=100_000_u64).prop_map(|num| Dec::new(num, 5).expect("Test failed")) + (0..=100_000_i128) + .prop_map(|num| Dec::new(num, 5).expect("Test failed")) } } diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index acfca50673..26d1b91442 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -2,11 +2,12 @@ use namada_core::types::dec::Dec; use namada_core::types::token::Amount; -use namada_core::types::uint::Uint; +use namada_core::types::uint::{Uint, I256}; use thiserror::Error; /// This is equal to 0.01. -const MIN_PROPOSER_REWARD: Dec = Dec(Uint([10000u64, 0u64, 0u64, 0u64])); +const MIN_PROPOSER_REWARD: Dec = + Dec(I256(Uint([10000000000u64, 0u64, 0u64, 0u64]))); /// Errors during rewards calculation #[derive(Debug, Error)] diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index eb87cb22f1..da7c3f2914 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -509,7 +509,8 @@ impl Display for SlashType { pub fn into_tm_voting_power(votes_per_token: Dec, tokens: Amount) -> i64 { let pow = votes_per_token * u128::try_from(tokens).expect("Voting power out of bounds"); - i64::try_from(pow.to_uint()).expect("Invalid voting power") + i64::try_from(pow.to_uint().expect("Cant fail")) + .expect("Invalid voting power") } #[cfg(test)] diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index b5b1de321a..1d869dcff9 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -63,8 +63,10 @@ impl RewardsController { // Token amounts must be expressed in terms of the raw amount (namnam) // to properly run the PD controller - let locked = Dec::from(locked_tokens.raw_amount()); - let total = Dec::from(total_tokens.raw_amount()); + let locked = + Dec::try_from(locked_tokens.raw_amount()).expect("Should not fail"); + let total = + Dec::try_from(total_tokens.raw_amount()).expect("Should not fail"); let epochs_py: Dec = epochs_per_year.into(); let locked_ratio = locked / total; diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 208a836cb0..3869dbafe2 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -153,10 +153,10 @@ mod tests { fn arb_rate(min: Dec, max: Dec) -> impl Strategy { let scale = Dec::new(100_000, 0).expect("Test failed"); - let int_min = (min * scale).to_uint(); - let int_min = u64::try_from(int_min).unwrap(); - let int_max = (max * scale).to_uint(); - let int_max = u64::try_from(int_max).unwrap(); + let int_min = (min * scale).to_i256(); + let int_min = i128::try_from(int_min).unwrap(); + let int_max = (max * scale).to_i256(); + let int_max = i128::try_from(int_max).unwrap(); (int_min..=int_max).prop_map(move |num| Dec::from(num) / scale) } From aebb5f5076fafc6dac5623f0344e1bed74c4074c Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 24 May 2023 13:09:33 -0400 Subject: [PATCH 50/69] we don't take separators anymore whoops --- tests/src/e2e/ledger_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index eba045f775..63dbdc2539 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2548,7 +2548,7 @@ fn pos_init_validator() -> Result<()> { find_bonded_stake(&test, new_validator, &validator_one_rpc)?; assert_eq!( bonded_stake, - token::Amount::from_str("11_000.5", NATIVE_MAX_DECIMAL_PLACES).unwrap() + token::Amount::from_str("11000.5", NATIVE_MAX_DECIMAL_PLACES).unwrap() ); Ok(()) From f09312e49b0af6e02aed0cd794d22de3572e6033 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 24 May 2023 15:49:47 -0400 Subject: [PATCH 51/69] disambiguate token denom and ibc denom --- core/src/ledger/ibc/storage.rs | 4 ++-- core/src/types/token.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 478a9e10f3..785e309fa3 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -25,7 +25,7 @@ const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; const CAPABILITIES_INDEX: &str = "capabilities/index"; const CAPABILITIES: &str = "capabilities"; -const DENOM: &str = "denom"; +const DENOM: &str = "ibc_denom"; /// Key segment for a multitoken related to IBC pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; @@ -83,7 +83,7 @@ pub fn ibc_prefix(key: &Key) -> Option { "receipts" => IbcPrefix::Receipt, "acks" => IbcPrefix::Ack, "event" => IbcPrefix::Event, - "denom" => IbcPrefix::Denom, + "ibc_denom" => IbcPrefix::Denom, _ => IbcPrefix::Unknown, }) } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index bd01e1917e..ed5e58f5d2 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -708,7 +708,7 @@ impl MaspDenom { /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; /// Key segment for a denomination key -pub const DENOM_STORAGE_KEY: &str = "denom"; +pub const DENOM_STORAGE_KEY: &str = "denomination"; /// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key From 55591d57e78b4c4631a8309c70403297e3922370 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 24 May 2023 15:51:20 -0400 Subject: [PATCH 52/69] force 0 ibc denoms --- core/src/ledger/storage_api/token.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 24494dbf1d..b2abbb8a97 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -3,12 +3,14 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; +use crate::types::storage::DbKeySeg::StringSeg; use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{ balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, Change, }; +use crate::types::token::Denomination; /// Read the balance of a given token and owner. pub fn read_balance( @@ -48,6 +50,11 @@ pub fn read_denom( where S: StorageRead, { + if let Some(sub_prefix) = sub_prefix { + if sub_prefix.segments.contains(&StringSeg("ibc".to_string())) { + return Ok(Some(Denomination(0))) + } + } let key = token::denom_key(token, sub_prefix); storage.read(&key) } From 9be7ef86f13b582560f33b9daded57e7d03be85a Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 24 May 2023 15:51:52 -0400 Subject: [PATCH 53/69] stupid test fix: add six zeroes --- tests/src/e2e/ibc_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 7ded2c703a..e1b5e4d15e 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -784,7 +784,7 @@ fn transfer_received_token( "--sub-prefix", &sub_prefix, "--amount", - "50000", + "50000000000", "--gas-amount", "0", "--gas-limit", From 43ce2af0640604f55f518c184b12b6a7c7f86be5 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 25 May 2023 09:23:19 -0400 Subject: [PATCH 54/69] fix ledger e2e tests --- apps/src/lib/client/rpc.rs | 25 +++++++++++++++++-------- apps/src/lib/client/tx.rs | 11 +++++++++-- core/src/ledger/storage_api/token.rs | 4 ++-- core/src/types/uint.rs | 14 +++++++++++++- genesis/e2e-tests-single-node.toml | 2 +- tests/src/e2e/ledger_tests.rs | 2 +- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 49bcbd5fec..0db6ea5508 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -44,7 +44,8 @@ use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; use namada::types::token::{ - balance_key, Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, + balance_key, Change, DenominatedAmount, Denomination, MaspDenom, + TokenAddress, Transfer, }; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, @@ -2849,6 +2850,7 @@ pub async fn validate_amount( amount: InputAmount, token: &Address, sub_prefix: &Option, + force: bool, ) -> token::DenominatedAmount { let input_amount = match amount { InputAmount::Unvalidated(amt) => amt.canonical(), @@ -2861,14 +2863,21 @@ pub async fn validate_amount( .await, ) .unwrap_or_else(|| { - println!( - "No denomination found for token: {token}, the input arguments \ - could - not be parsed." - ); - cli::safe_exit(1); + if force { + println!( + "No denomination found for token: {token}, but --force was \ + passed. Defaulting to the provided denomination." + ); + input_amount.denom + } else { + println!( + "No denomination found for token: {token}, the input \ + arguments could not be parsed." + ); + cli::safe_exit(1); + } }); - if denom < input_amount.denom { + if denom < input_amount.denom && !force { println!( "The input amount contained a higher precision than allowed by \ {token}." diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9c2b6d3b7c..8807259bbf 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1788,14 +1788,21 @@ pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { None => (None, token::balance_key(&token, &source)), }; // validate the amount given - let validated_amount = - validate_amount(&client, args.amount, &token, &sub_prefix).await; + let validated_amount = validate_amount( + &client, + args.amount, + &token, + &sub_prefix, + args.tx.force, + ) + .await; let validate_fee = validate_amount( &client, args.tx.fee_amount, &fee_token, // TODO: Currently multi-tokens cannot be used to pay fees &None, + args.tx.force, ) .await; diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index b2abbb8a97..1964a69a98 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -6,11 +6,11 @@ use crate::types::address::Address; use crate::types::storage::DbKeySeg::StringSeg; use crate::types::storage::Key; use crate::types::token; +use crate::types::token::Denomination; pub use crate::types::token::{ balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, Change, }; -use crate::types::token::Denomination; /// Read the balance of a given token and owner. pub fn read_balance( @@ -52,7 +52,7 @@ where { if let Some(sub_prefix) = sub_prefix { if sub_prefix.segments.contains(&StringSeg("ibc".to_string())) { - return Ok(Some(Denomination(0))) + return Ok(Some(Denomination(0))); } } let key = token::denom_key(token, sub_prefix); diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 1749af7b51..8cc7537733 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -2,6 +2,7 @@ //! An unsigned 256 integer type. Used for, among other things, //! the backing type of token amounts. use std::cmp::Ordering; +use std::fmt::{Debug, Display, Formatter}; use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -83,7 +84,6 @@ const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); #[derive( Copy, Clone, - Debug, Default, PartialEq, Eq, @@ -333,6 +333,18 @@ impl std::iter::Sum for I256 { } } +impl Display for I256 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string_native().as_str()) + } +} + +impl Debug for I256 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + impl TryFrom for i128 { type Error = std::io::Error; diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 5fc2d4f231..eb771e2341 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -5,7 +5,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" faucet_pow_difficulty = 1 -faucet_withdrawal_limit = "1000" +faucet_withdrawal_limit = "1000000000" [validator.validator-0] # Validator's staked NAM at genesis. diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 63dbdc2539..ffd062a793 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1832,7 +1832,7 @@ fn invalid_transactions() -> Result<()> { "--token", BERTHA, "--amount", - "1_000_000.1", + "1000000.1", "--gas-amount", "0", "--gas-limit", From 41b626700f5e9d6c27c7f53a24539bda60be9aad Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 29 May 2023 14:30:26 -0400 Subject: [PATCH 55/69] unsorted wips on e2e --- apps/src/lib/client/rpc.rs | 23 +++--- apps/src/lib/client/tx.rs | 30 ++++---- apps/src/lib/config/genesis.rs | 6 +- core/src/ledger/storage/masp_conversions.rs | 8 +-- core/src/types/token.rs | 3 +- core/src/types/uint.rs | 25 ++++++- genesis/dev.toml | 2 +- genesis/e2e-tests-single-node.toml | 80 ++++++++++----------- tests/src/e2e/ledger_tests.rs | 52 +++++++------- wasm/wasm_source/src/vp_masp.rs | 31 +++++++- 10 files changed, 160 insertions(+), 100 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0db6ea5508..1b663174d0 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -333,15 +333,13 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - let check = |(tok, amt): (&TokenAddress, &token::Amount)| { + let check = |(tok, chg): (&TokenAddress, &Change)| { tok.sub_prefix == sub_prefix && &tok.address == token - && !amt.is_zero() + && !chg.is_zero() }; tfer_delta.values().cloned().any( - |MaspChange { ref asset, change }| { - check((asset, &change.into())) - }, + |MaspChange { ref asset, change }| check((asset, &change)), ) || shielded_accounts .values() .cloned() @@ -382,7 +380,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!(" {}:", fvk_map[&account]); for (token_addr, val) in masp_change { let token_alias = lookup_alias(&ctx, &token_addr.address); - let sign = match val.cmp(&token::Amount::zero()) { + let sign = match val.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", Ordering::Equal => "", @@ -390,8 +388,12 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { print!( " {}{} {}", sign, - format_denominated_amount(&client, &token_addr, val,) - .await, + format_denominated_amount( + &client, + &token_addr, + val.into(), + ) + .await, token_addr.format_with_alias(&token_alias), ); } @@ -1011,7 +1013,10 @@ pub async fn query_shielded_balance( sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), }; - let total_balance = balance[&(epoch, token_address.clone())]; + let total_balance = balance + .get(&(epoch, token_address.clone())) + .cloned() + .unwrap_or_default(); if total_balance.is_zero() { println!( "No shielded {} balance found for given key", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8807259bbf..5ac3f90637 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -524,6 +524,14 @@ pub struct MaspChange { )] pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); +impl MaspAmount { + pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)>{ + let key = self.keys().next()?.clone(); + let value = self.remove(&key).unwrap(); + Some((key, value)) + } +} + impl std::ops::Deref for MaspAmount { type Target = HashMap<(Epoch, TokenAddress), token::Change>; @@ -1189,9 +1197,8 @@ impl ShieldedContext { // Where we will store our exchanged value let mut output = Amount::zero(); // Repeatedly exchange assets until it is no longer possible - while let Some(((asset_epoch, token_addr), value)) = - input.iter().next().map(cloned_pair) - { + loop { + let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( Some(target_epoch), @@ -1467,7 +1474,7 @@ impl ShieldedContext { client: HttpClient, amt: Amount, target_epoch: Epoch, - ) -> HashMap { + ) -> HashMap { let mut res = HashMap::new(); for (asset_type, val) in amt.components() { // Decode the asset type @@ -1524,7 +1531,7 @@ impl ShieldedContext { ) } } - MaspAmount(res.into_iter().map(|(k, v)| (k, v.change())).collect()) + MaspAmount(res) } } @@ -3413,20 +3420,17 @@ pub async fn submit_tx( fn decode_component( (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), val: i64, - res: &mut HashMap, + res: &mut HashMap, mk_key: F, ) where F: FnOnce(Address, Option, Epoch) -> K, K: Eq + std::hash::Hash, { - let decoded_amount = token::Amount::from_uint( - u64::try_from(val).expect("negative cash does not exist"), - denom as u8, - ) - .unwrap(); + let decoded_change = token::Change::from_masp_denominated(val, denom) + .expect("expected this to fit"); res.entry(mk_key(addr, sub, epoch)) - .and_modify(|val| *val += decoded_amount) - .or_insert(decoded_amount); + .and_modify(|val| *val += decoded_change) + .or_insert(decoded_change); } #[cfg(test)] diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 4136f5c665..3bb6bb0db7 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -213,7 +213,7 @@ pub mod genesis_config { pub vp: Option, // Initial balances held by accounts defined elsewhere. // XXX: u64 doesn't work with toml-rs! - pub balances: Option>, + pub balances: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -460,7 +460,9 @@ pub mod genesis_config { } } }, - token::Amount::native_whole(*amount), + token::Amount::from_uint(*amount, config.denom).expect( + "expected a balance that fits into 256 bits", + ), ) }) .collect(), diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 37ce49892f..4dec02d4f5 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -65,12 +65,8 @@ where // with the zeroth epoch to minimize the number of convert notes clients // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. - let reward_asset_bytes = - (address::nam(), None::, MaspDenom::Zero, 0u64) - .try_to_vec() - .expect("unable to serialize address and epoch"); - let reward_asset = AssetType::new(reward_asset_bytes.as_ref()) - .expect("unable to derive asset identifier"); + let reward_asset = + encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); // Conversions from the previous to current asset for each address let mut current_convs = BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index ed5e58f5d2..dccef5683d 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -685,7 +685,8 @@ impl From for MaspDenom { impl MaspDenom { /// Iterator over the possible denominations pub fn iter() -> impl Iterator { - (0u8..3).map(Self::from) + // 0, 1, 2, 3 + (0u8..4).map(Self::from) } /// Get the corresponding u64 word from the input uint256. diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 8cc7537733..ff20e28fd5 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use uint::construct_uint; use crate::types::token; -use crate::types::token::Amount; +use crate::types::token::{Amount, AmountParseError, MaspDenom}; construct_uint! { /// Namada native type to replace for unsigned 256 bit @@ -166,6 +166,29 @@ impl I256 { pub fn maximum() -> Self { Self(MAX_SIGNED_VALUE) } + + /// Attempt to convert a MASP-denominated integer to an I256 + /// using the given denomination. + pub fn from_masp_denominated( + value: impl Into, + denom: MaspDenom, + ) -> Result { + let value = value.into(); + let is_negative = value < 0; + let value = value.unsigned_abs(); + let mut result = [0u64; 4]; + result[denom as usize] = value; + let result = Uint(result); + if result <= MAX_SIGNED_VALUE { + if is_negative { + Ok(Self(result.negate()).canonical()) + } else { + Ok(Self(result).canonical()) + } + } else { + Err(AmountParseError::InvalidRange) + } + } } impl From for I256 { diff --git a/genesis/dev.toml b/genesis/dev.toml index 620298db3d..e2d5c1e39f 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -36,7 +36,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 # 2. An alias of any account -bertha = 1000000 +Bertha = "1000000" # 3. A public key of a validator or an established account from which the # address of the implicit account is derived) "bertha.public_key" = 100 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index eb771e2341..33114e3640 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -30,71 +30,71 @@ address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvf denom = 6 vp = "vp_token" [token.NAM.balances] -Albert = 1000000 -"Albert.public_key" = 100 -Bertha = 1000000 -"Bertha.public_key" = 2000 -Christel = 1000000 -"Christel.public_key" = 100 -Daewon = 1000000 -faucet = 9223372036 -"faucet.public_key" = 100 -"validator-0.public_key" = 100 +Albert = "1000000" +"Albert.public_key" = "100" +Bertha = "1000000" +"Bertha.public_key" = "2000" +Christel = "1000000" +"Christel.public_key" = "100" +Daewon = "1000000" +faucet = "9223372036854" +"faucet.public_key" = "100" +"validator-0.public_key" = "100" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 vp = "vp_token" [token.BTC.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 vp = "vp_token" [token.ETH.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 vp = "vp_token" [token.DOT.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" [token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 vp = "vp_token" [token.Schnitzel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" [token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 vp = "vp_token" [token.Apfel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" [token.Kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" @@ -102,11 +102,11 @@ public_key = "" denom = 6 vp = "vp_token" [token.Kartoffel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +faucet = "9223372036854" # Some established accounts present at genesis. [established.faucet] diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ffd062a793..3a78294e41 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1069,6 +1069,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1085,7 +1087,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); @@ -1103,7 +1105,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1126,7 +1128,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); @@ -1149,7 +1151,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1171,7 +1173,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1196,7 +1198,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); @@ -1214,7 +1216,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1236,7 +1238,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1283,7 +1285,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("eth: 30")?; client.assert_success(); @@ -1301,7 +1303,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1322,7 +1324,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("eth: 30")?; client.assert_success(); @@ -1340,7 +1342,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1363,7 +1365,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1413,7 +1415,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded eth balance found")?; client.assert_success(); @@ -1433,7 +1435,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1457,7 +1459,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1507,7 +1509,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded btc balance found")?; client.assert_success(); @@ -1525,7 +1527,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1548,7 +1550,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1574,7 +1576,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1596,7 +1598,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1619,7 +1621,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string(&format!( "nam: {}", @@ -1699,7 +1701,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1717,7 +1719,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1735,7 +1737,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("nam: 0")?; client.assert_success(); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 5dd02a95b5..7fee57705b 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -14,10 +14,11 @@ use ripemd::{Digest, Ripemd160}; fn asset_type_from_epoched_address( epoch: Epoch, token: &Address, + sub_prefix: String, denom: token::MaspDenom, ) -> AssetType { // Timestamp the chosen token with the current epoch - let token_bytes = (token, denom, epoch.0) + let token_bytes = (token, sub_prefix, denom, epoch.0) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address @@ -63,10 +64,19 @@ fn valid_transfer_amount( fn convert_amount( epoch: Epoch, token: &Address, + sub_prefix: &Option, val: token::Amount, denom: token::MaspDenom, ) -> (AssetType, Amount) { - let asset_type = asset_type_from_epoched_address(epoch, token, denom); + let asset_type = asset_type_from_epoched_address( + epoch, + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + ); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, denom.denominate(&val)) .expect("invalid value or asset type for amount"); @@ -102,6 +112,7 @@ fn validate_tx( transparent_tx_pool += shielded_tx.value_balance.clone(); if transfer.source != masp() { + log_string("transparent input"); // Handle transparent input // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp @@ -110,10 +121,12 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, + &transfer.sub_prefix, transfer.amount.into(), denom, ); + log_string(format!("transparent amount: {:?}", transp_amt)); // Non-masp sources add to transparent tx pool transparent_tx_pool += transp_amt; } @@ -135,6 +148,7 @@ fn validate_tx( } if transfer.target != masp() { + log_string("transparent output"); // Handle transparent output // The following boundary conditions must be satisfied // 1. One to 4 transparent outputs @@ -166,6 +180,11 @@ fn validate_tx( asset_type_from_epoched_address( ctx.get_block_epoch().unwrap(), &transfer.token, + transfer + .sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), denom, ); @@ -185,6 +204,7 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, + &transfer.sub_prefix, transfer.amount.amount, denom, ); @@ -227,6 +247,7 @@ fn validate_tx( // Satisfies 1. if !shielded_tx.vout.is_empty() { + log_string(format!("transparent vout {:?}", shielded_tx.vout)); debug_log!( "Transparent output to a transaction from the masp must \ be 0 but is {}", @@ -245,6 +266,11 @@ fn validate_tx( ); // Section 3.4: The remaining value in the transparent // transaction value pool MUST be nonnegative. + log_string(format!( + "would give the masp a negative balance; transparent tx \ + {:?}", + transparent_tx_pool + )); return reject(); } _ => {} @@ -252,5 +278,6 @@ fn validate_tx( } // Do the expensive proof verification in the VM at the end. + log_string("reached proof verification"); ctx.verify_masp(data) } From 6e5782bdb8a95bc11a60530bfdd65cfa412d5b9c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 30 May 2023 17:00:17 +0200 Subject: [PATCH 56/69] WIP fixing masp amounts --- Cargo.lock | 4 +- Makefile | 2 +- apps/Cargo.toml | 4 +- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/client/tx.rs | 37 ++++++----- core/Cargo.toml | 2 +- core/src/types/uint.rs | 4 +- shared/Cargo.toml | 4 +- shared/src/ledger/masp.rs | 2 +- tests/src/e2e/ledger_tests.rs | 96 +++++++++++++++------------- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 4 +- wasm/Cargo.lock | 4 +- wasm/checksums.json | 36 +++++------ wasm/wasm_source/Cargo.toml | 4 +- wasm/wasm_source/src/vp_implicit.rs | 3 +- wasm/wasm_source/src/vp_user.rs | 3 +- wasm/wasm_source/src/vp_validator.rs | 3 +- 18 files changed, 108 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8168c7f218..2beee040c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3484,7 +3484,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" dependencies = [ "aes", "bip0039", @@ -3517,7 +3517,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/Makefile b/Makefile index b65e8b698d..756fc222b5 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ endif audit-ignores += RUSTSEC-2021-0076 build: - $(cargo) $(jobs) build + $(cargo) build $(jobs) build-test: $(cargo) +$(nightly) build --tests $(jobs) -Z unstable-options diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ee1e99ea4b..5e8b337f0e 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -148,8 +148,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter", "json"]} websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} [dev-dependencies] diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1b663174d0..d90283aa9f 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -44,7 +44,7 @@ use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; use namada::types::token::{ - balance_key, Change, DenominatedAmount, Denomination, MaspDenom, + balance_key, Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, }; use namada::types::transaction::{ diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5ac3f90637..973580a653 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -493,11 +493,6 @@ pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { false } -/// An extension of Option's cloned method for pair types -fn cloned_pair((a, b): (&T, &U)) -> (T, U) { - (a.clone(), b.clone()) -} - /// Errors that can occur when trying to retrieve pinned transaction #[derive(PartialEq, Eq, Clone, Copy)] pub enum PinnedBalanceError { @@ -555,6 +550,7 @@ impl std::ops::Add for MaspAmount { .and_modify(|val| *val += value) .or_insert(value); } + self.retain(|_, v| !v.is_zero()); self } } @@ -606,7 +602,7 @@ impl<'a> From<&'a MaspAmount> for Amount { /// Represents the amount used of different conversions pub type Conversions = - HashMap, i64)>; + HashMap, i128)>; /// Represents an amount that is pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; @@ -1087,7 +1083,7 @@ impl ShieldedContext { client: HttpClient, asset_type: AssetType, conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { + ) -> Option<&'a mut (AllowedConversion, MerklePath, i128)> { match conversions.entry(asset_type) { Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), Entry::Vacant(conv_entry) => { @@ -1152,8 +1148,8 @@ impl ShieldedContext { client: &HttpClient, conv: AllowedConversion, asset_type: AssetType, - value: i64, - usage: &mut i64, + value: i128, + usage: &mut i128, input: &mut MaspAmount, output: &mut Amount, ) { @@ -1170,6 +1166,9 @@ impl ShieldedContext { } // We should use an amount of the AllowedConversion that almost // cancels the original amount + if threshold > value { + return + } let required = value / threshold; // Forget about the trace amount left over because we cannot // realize its value @@ -1178,7 +1177,7 @@ impl ShieldedContext { *usage += required; // Apply the conversions to input and move the trace amount to output *input += self - .decode_all_amounts(client, conv * required - &trace) + .decode_all_amounts(client, conv.clone() * required - &trace) .await; *output += trace; } @@ -1214,7 +1213,7 @@ impl ShieldedContext { ); let at_target_asset_type = target_epoch == asset_epoch; - let denom_value = denom.denominate_i64(&value); + let denom_value = denom.denominate(&token::Amount::from(value)) as i128; _ = self .query_allowed_conversion( client.clone(), @@ -1237,7 +1236,7 @@ impl ShieldedContext { self.apply_conversion( &client, conv.clone(), - target_asset_type, + asset_type, denom_value, usage, &mut input, @@ -1266,26 +1265,24 @@ impl ShieldedContext { self.apply_conversion( &client, conv.clone(), - target_asset_type, + asset_type, denom_value, usage, &mut input, &mut output, ) - .await; + .await; } else { // At the target asset type. Then move component over to // output. - let mut comp = MaspAmount::default(); + comp.insert((asset_epoch, token_addr.clone()), denom_value.into()); for ((e, key), val) in input.iter() { if *key == token_addr { comp.insert((*e, key.clone()), *val); } } output += Amount::from(&comp); - // Strike from input to avoid repeating computation - input -= comp; } } } @@ -1501,7 +1498,6 @@ impl ShieldedContext { res } - // TODO :: Panics if we ever switch to an i128 in the masp crate /// Convert an amount whose units are AssetTypes to one whose units are /// Addresses that they decode to. pub async fn decode_all_amounts( @@ -1542,11 +1538,14 @@ fn convert_amount( sub_prefix: &Option<&String>, val: &token::Amount, ) -> ([AssetType; 4], Amount) { + println!("{}", DenominatedAmount { amount: val.clone(), denom: 18.into()}); let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { let asset_type = make_asset_type(Some(epoch), token, sub_prefix, denom); + let inner = denom.denominate(val); + println!("{}", inner); // Combine the value and unit into one amount amount += Amount::from_nonnegative(asset_type, denom.denominate(val)) @@ -3419,7 +3418,7 @@ pub async fn submit_tx( fn decode_component( (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), - val: i64, + val: i128, res: &mut HashMap, mk_key: F, ) where diff --git a/core/Cargo.toml b/core/Cargo.toml index 806ac757f4..3a68ca3abc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -83,7 +83,7 @@ impl-num-traits = "0.1.2" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index ff20e28fd5..640f79ee6c 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -170,14 +170,14 @@ impl I256 { /// Attempt to convert a MASP-denominated integer to an I256 /// using the given denomination. pub fn from_masp_denominated( - value: impl Into, + value: impl Into, denom: MaspDenom, ) -> Result { let value = value.into(); let is_negative = value < 0; let value = value.unsigned_abs(); let mut result = [0u64; 4]; - result[denom as usize] = value; + result[denom as usize] = value as u64; let result = Uint(result); if result <= MAX_SIGNED_VALUE { if is_negative { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d9dc8ad02a..0f059bc663 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -98,6 +98,8 @@ ibc = {version = "0.14.0", default-features = false, optional = true} ibc-proto = {version = "0.17.1", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing @@ -125,8 +127,6 @@ wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } zeroize = "1.5.5" [dev-dependencies] diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 10c63db4cb..8c51803622 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -176,7 +176,7 @@ pub fn verify_shielded_tx(transaction: &Transaction) -> bool { tracing::info!("passed spend/output verification"); - let assets_and_values: Vec<(AssetType, i64)> = + let assets_and_values: Vec<(AssetType, i128)> = tx_data.value_balance.clone().into_components().collect(); tracing::info!("accumulated {} assets/values", assets_and_values.len()); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 3a78294e41..26582aa20e 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,7 +27,7 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_core::types::token::{DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES}; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -1014,6 +1014,10 @@ fn masp_pinned_txs() -> Result<()> { #[test] fn masp_incentives() -> Result<()> { + // The number of decimal places used by BTC amounts. + const BTC_DENOMINATION: u8 = 8; + // The number of decimal places used by ETH amounts. + const ETH_DENOMINATION: u8 = 18; // Download the shielded pool parameters before starting node let _ = ShieldedContext::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and @@ -1134,9 +1138,9 @@ fn masp_incentives() -> Result<()> { client.assert_success(); let amt20 = - token::Amount::from_uint(20, NATIVE_MAX_DECIMAL_PLACES).unwrap(); + token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); let amt30 = - token::Amount::from_uint(30, NATIVE_MAX_DECIMAL_PLACES).unwrap(); + token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1153,10 +1157,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1175,10 +1179,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1218,10 +1222,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1240,10 +1244,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0)) - .to_string_native(), + "nam: {}", denominated, ))?; client.assert_success(); @@ -1344,10 +1348,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)) - .to_string_native(), + "nam: {}", denominated, ))?; client.assert_success(); @@ -1367,11 +1371,11 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0))) - .to_string_native() + "nam: {}", denominated ))?; client.assert_success(); @@ -1437,10 +1441,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); + let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1461,11 +1465,11 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); + let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0))) - .to_string_native() + "nam: {}", denominated ))?; client.assert_success(); @@ -1529,10 +1533,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1552,11 +1556,11 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; client.exp_string(&format!( - "nam: {}", - (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0))) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1578,10 +1582,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - .to_string_native() + "nam: {}", denominated ))?; client.assert_success(); @@ -1600,10 +1604,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); @@ -1623,11 +1627,11 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; client.exp_string(&format!( - "nam: {}", - (((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0))) - .to_string_native() + "nam: {}", denominated, ))?; client.assert_success(); diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index e669fdefee..6de40aed20 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -15,7 +15,7 @@ abciplus = [ ] [dependencies] -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } namada_core = {path = "../core", default-features = false} namada_macros = {path = "../macros"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index b851f716b9..4e9bd48a47 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -16,6 +16,6 @@ abciplus = [ namada_core = {path = "../core", default-features = false} borsh = "0.9.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } hex = "0.4.3" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c7edff1984..b4d4e87839 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2425,7 +2425,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" dependencies = [ "aes", "bip0039", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/wasm/checksums.json b/wasm/checksums.json index 32215c5ba0..ebd6ccc2b1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.91275d24c59a883c629c5b26ff5f7b57cf0f18009cbed190050d752a2e96a0a8.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.73aa36169a1366c89fdd7f24b6d25f2931b96ed2bf8f2cdb2f690a4132c99788.wasm", - "tx_ibc.wasm": "tx_ibc.bc180e0716d5e28f0395abe8c21ab05c98d85011c377633f91e1d0291e969ef8.wasm", - "tx_init_account.wasm": "tx_init_account.bad8964f803f6e369ccc911027deb78e48a7896131296172147c92666d27227d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c70f106d3c26f0f2c5c2ac6a9b5ff851b67ecfb19f76fc57d0563aa4a9e012f5.wasm", - "tx_init_validator.wasm": "tx_init_validator.0538743e4433d9657670307828c5df727bc936bad9f751653f9a914fbd29c983.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.e4806260ff21c1a441ddbdba4f8640a18db03a5687f0ab5dc2e08a72423b808b.wasm", - "tx_transfer.wasm": "tx_transfer.c564498c1d419e566e03a1b5f39927ceda4b3efc1e78128c574d823d1f7cfaeb.wasm", - "tx_unbond.wasm": "tx_unbond.e800fac2d595b7ac3892b1e1efced00ee1f4b5fec22c08a90ee9838725104b8f.wasm", - "tx_update_vp.wasm": "tx_update_vp.6ff4bd7fbf11449abef4f3632243d6e8cf93949e76be206be6eaf8d88e15ccd2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.4defe4f78e6aee8b6c6faabaf4a8cf6c2939abd9edcc2a772a02f0f6bcc4b8cf.wasm", - "tx_withdraw.wasm": "tx_withdraw.4a31fad26cf1e34d0b7ac5b47d78ccdc79de4666840c81b441592a1b5fe9d4b5.wasm", - "vp_implicit.wasm": "vp_implicit.d014edfd0b81d679c918d033962c347d0f833cd86642b91205e1705e33caddb5.wasm", - "vp_masp.wasm": "vp_masp.8e725e075108905e316a1f86fc696ee9f1f2ba4b0e7646505b3046443bb22d0d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.99f470dbb03b51f2082b8ba31c6cd1580ff5504752b04b8243ffbfc1b60b5c99.wasm", - "vp_token.wasm": "vp_token.d642c37273de047a4d3bd126ad0bbd4f7b16b2efb1a766fcec0104d7ca231a64.wasm", - "vp_user.wasm": "vp_user.113f37df167b59d278b24f9c1a4c64f2e1b03641cb3d55942e02fe8ba8e461f2.wasm", - "vp_validator.wasm": "vp_validator.f02cf20e2cfda256bc3deb5458de8aa6cb614c5cf2524c4be33495b30a8ef91d.wasm" + "tx_bond.wasm": "tx_bond.a63917a5f9971fd9e0c38039327c477f6a5ac70850a6714d6606c7d8c625ec5d.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.494320a5fd32eb09f8d5ec4e0950f57ab4da68aae5940e0e5d27030cca4334bf.wasm", + "tx_ibc.wasm": "tx_ibc.bed0f324d800aa2da8492767d7addd607d85918159407735120c8b8d0cdff882.wasm", + "tx_init_account.wasm": "tx_init_account.025fef966350ee18b643b1d2b83d99d264b82e4f247be3d3d9709cd68db1e99d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.02388a86b695c410830ce7eb03a948dcc1cb74b37cf2ff04488485a84a0fe4c6.wasm", + "tx_init_validator.wasm": "tx_init_validator.a2f7e28fc73d67b17d2dec7cda589d54c988ce34267d4a08d3b31c831973ac53.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.da894ee43081312e9f0dc7a1ebc010ea087348d0e17588a4f9ed876b5a64466c.wasm", + "tx_transfer.wasm": "tx_transfer.569847cc57c5872b6be5485d8e51ae7fdbb2c147b442f3dced2d7a47dad997d3.wasm", + "tx_unbond.wasm": "tx_unbond.02932b2c5b5ddc5c5fa23955014eb7e40551c62e065d135d3666003c7f53c206.wasm", + "tx_update_vp.wasm": "tx_update_vp.1910af6c9d86bb241b7e8d2aede17aa131e77ea888f37ab125bc3f8163bc45ec.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.99f64828e6e8571b7f4ae0050c1ed3f36184f984ed595f3b588a9c639943aede.wasm", + "tx_withdraw.wasm": "tx_withdraw.6ccc14bcbbaaa4fe02cbc10b16301dee9c306cef6fad0b16091295cc105d6e79.wasm", + "vp_implicit.wasm": "vp_implicit.6434963d9844da7dc382fcc09808583bd57740f0ab0a6f13ad940e35ebe4dec3.wasm", + "vp_masp.wasm": "vp_masp.7e29cd91a45bf0e2f8976e9db739dcdb7ae6cbb38f8f5f6d82eedbe24857474b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.b581d376d9b4454800bb82c32a4377cfe4dc67ac2a08cf8d7ad9a7a80bf6a16b.wasm", + "vp_token.wasm": "vp_token.246329086c51c170ff890a3d361db42f2f34af9781a8b7b8f12c81764fcad223.wasm", + "vp_user.wasm": "vp_user.74814ed9d67905454fdda739d2f4959f08bca96095c0b6a30abcdb60b7474af2.wasm", + "vp_validator.wasm": "vp_validator.fe9672f182cdbece544d9bf2c5bd86ed984b07add82ecabc47380ae6685f90c7.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 2e334d2caa..61d6e9f4b3 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } ripemd = "0.1.3" [dev-dependencies] diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index fd76f1bd97..f8906a5f15 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -130,9 +130,8 @@ fn validate_tx( true } KeyType::Token { - token, owner, - sub_prefix, + .. } => { if owner == &addr { let pre: token::Amount = diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 63ae898099..c3f6424336 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -103,9 +103,8 @@ fn validate_tx( let key_type: KeyType = key.into(); let is_valid = match key_type { KeyType::Token { - token, owner, - sub_prefix, + .. } => { if owner == &addr { let pre: token::Amount = diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 0328fdd066..3309b75e26 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -103,9 +103,8 @@ fn validate_tx( let key_type: KeyType = key.into(); let is_valid = match key_type { KeyType::Token { - token, owner, - sub_prefix, + .. } => { if owner == &addr { let pre: token::Amount = From 5053330ad06b11cb64a2f089a3fa9e398ace5da7 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Jun 2023 16:58:36 +0200 Subject: [PATCH 57/69] WIP fixing masp incentives --- Cargo.lock | 4 +- apps/Cargo.toml | 4 +- apps/src/lib/client/rpc.rs | 3 +- apps/src/lib/client/tx.rs | 328 +++++++++++++++++---------- core/Cargo.toml | 2 +- core/src/types/token.rs | 19 +- core/src/types/uint.rs | 30 ++- shared/Cargo.toml | 4 +- tests/src/e2e/ledger_tests.rs | 127 ++++++----- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 4 +- wasm/Cargo.lock | 4 +- wasm/checksums.json | 36 +-- wasm/wasm_source/Cargo.toml | 4 +- wasm/wasm_source/src/vp_implicit.rs | 5 +- wasm/wasm_source/src/vp_user.rs | 5 +- wasm/wasm_source/src/vp_validator.rs | 5 +- 17 files changed, 358 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2beee040c5..ab6910b899 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3484,7 +3484,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "aes", "bip0039", @@ -3517,7 +3517,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 5e8b337f0e..cc319c8287 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -148,8 +148,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter", "json"]} websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["bundled-prover", "download-params"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} [dev-dependencies] diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d90283aa9f..317882f9a7 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -44,8 +44,7 @@ use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; use namada::types::token::{ - balance_key, Change, DenominatedAmount, MaspDenom, - TokenAddress, Transfer, + balance_key, Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, }; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 973580a653..8f95c34f84 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -53,7 +53,7 @@ use namada::types::storage::{ }; use namada::types::time::DateTimeUtc; use namada::types::token::{ - DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, + Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada::types::transaction::governance::{ @@ -520,8 +520,8 @@ pub struct MaspChange { pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); impl MaspAmount { - pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)>{ - let key = self.keys().next()?.clone(); + pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)> { + let key = self.keys().find(|(e, _)| e.0 != 0)?.clone(); let value = self.remove(&key).unwrap(); Some((key, value)) } @@ -581,6 +581,17 @@ impl std::ops::SubAssign for MaspAmount { } } +impl std::ops::Mul for MaspAmount { + type Output = Self; + + fn mul(mut self, rhs: Change) -> Self::Output { + for (_, value) in self.iter_mut() { + *value = *value * rhs + } + self + } +} + impl<'a> From<&'a MaspAmount> for Amount { fn from(masp_amount: &'a MaspAmount) -> Amount { let mut res = Amount::zero(); @@ -592,7 +603,7 @@ impl<'a> From<&'a MaspAmount> for Amount { &key.sub_prefix, denom, ); - res += Amount::from_pair(asset, denom.denominate_i64(val)) + res += Amount::from_pair(asset, denom.denominate_i128(val)) .unwrap(); } } @@ -600,9 +611,15 @@ impl<'a> From<&'a MaspAmount> for Amount { } } +impl From for Amount { + fn from(amt: MaspAmount) -> Self { + Self::from(&amt) + } +} + /// Represents the amount used of different conversions pub type Conversions = - HashMap, i128)>; + HashMap, Change)>; /// Represents an amount that is pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; @@ -1083,26 +1100,17 @@ impl ShieldedContext { client: HttpClient, asset_type: AssetType, conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i128)> { - match conversions.entry(asset_type) { - Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), - Entry::Vacant(conv_entry) => { - // Query for the ID of the last accepted transaction - let (addr, sub_prefix, denom, ep, conv, path): ( - Address, - _, - _, - _, - _, - _, - ) = query_conversion(client, asset_type).await?; + ) { + if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { + // Query for the ID of the last accepted transaction + if let Some((addr, sub_prefix, denom, ep, conv, path)) = + query_conversion(client, asset_type).await + { self.asset_types .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding - if conv == Amount::zero() { - None - } else { - Some(conv_entry.insert((Amount::into(conv), path, 0))) + if conv != Amount::zero() { + conv_entry.insert((conv.into(), path, Change::zero())); } } } @@ -1146,40 +1154,46 @@ impl ShieldedContext { async fn apply_conversion( &mut self, client: &HttpClient, - conv: AllowedConversion, - asset_type: AssetType, - value: i128, - usage: &mut i128, + conv: &MaspAmount, + asset_type: (Epoch, TokenAddress), + value: Change, input: &mut MaspAmount, - output: &mut Amount, - ) { + output: &mut MaspAmount, + ) -> Option { + if !value.non_negative() { + return None; + } // If conversion if possible, accumulate the exchanged amount - let conv: Amount = conv.into(); + let conv = self.decode_all_amounts(client, conv.into()).await; + // println!("conv {:?}", conv); // The amount required of current asset to qualify for conversion - let threshold = -conv[&asset_type]; - if threshold == 0 { + let threshold = token::Change::from(-conv[&asset_type]); + // println!("theshold: {}, value: {}", threshold, value); + if threshold.is_zero() { eprintln!( - "Asset threshold of selected conversion for asset type {} is \ - 0, this is a bug, please report it.", + "Asset threshold of selected conversion for asset type {:?} \ + is 0, this is a bug, please report it.", asset_type ); } // We should use an amount of the AllowedConversion that almost // cancels the original amount if threshold > value { - return + return None; } let required = value / threshold; + // Forget about the trace amount left over because we cannot // realize its value - let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); - // Record how much more of the given conversion has been used - *usage += required; + let trace = + MaspAmount(HashMap::from([(asset_type, value % threshold)])); + // println!("Trace: {:?}", trace); + // println!("required {:?}", required); // Apply the conversions to input and move the trace amount to output - *input += self - .decode_all_amounts(client, conv.clone() * required - &trace) - .await; + *input += conv.clone() * required - trace.clone(); + // println!("Input {:?}", input); *output += trace; + Some(required) } /// Convert the given amount into the latest asset types whilst making a @@ -1194,99 +1208,106 @@ impl ShieldedContext { mut conversions: Conversions, ) -> (Amount, Conversions) { // Where we will store our exchanged value - let mut output = Amount::zero(); + let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible loop { + println!("Input {:?}", input); + let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; - for denom in MaspDenom::iter() { - let target_asset_type = make_asset_type( - Some(target_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, - ); - let asset_type = make_asset_type( - Some(asset_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, + let at_target_asset_type = target_epoch == asset_epoch; + let conv = self + .aggregate_conversions( + &client, + asset_epoch, + target_epoch, + token_addr.clone(), + &mut conversions, + ) + .await; + println!("conversions {:?}", conv); + if let (Some(conv), false) = ( + conv.get(&(target_epoch, token_addr.clone())), + at_target_asset_type, + ) { + println!( + "converting current asset type to latest asset type..." ); - let at_target_asset_type = target_epoch == asset_epoch; - - let denom_value = denom.denominate(&token::Amount::from(value)) as i128; - _ = self - .query_allowed_conversion( - client.clone(), - target_asset_type, - &mut conversions, - ) - .await; - - if let (Some((conv, _wit, usage)), false) = ( - conversions.get_mut(&target_asset_type), - at_target_asset_type, - ) { - println!( - "converting current asset type to latest asset type..." - ); - // Not at the target asset type, not at the latest asset - // type. Apply conversion to get from - // current asset type to the latest - // asset type. - self.apply_conversion( + if let Some(used) = self + .apply_conversion( &client, - conv.clone(), - asset_type, - denom_value, - usage, + conv, + (asset_epoch, token_addr.clone()), + value, &mut input, &mut output, ) - .await; - break; - } - _ = self - .query_allowed_conversion( - client.clone(), - asset_type, - &mut conversions, - ) - .await; - if let (Some((conv, _wit, usage)), false) = - (conversions.get_mut(&asset_type), at_target_asset_type) + .await { - println!( - "converting latest asset type to target asset type..." - ); - // Not at the target asset type, yes at the latest asset - // type. Apply inverse conversion to get - // from latest asset type to the target - // asset type. - self.apply_conversion( + for denom in MaspDenom::iter() { + let target_asset_type = make_asset_type( + Some(target_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, + ); + if let Some((_, _, usage)) = + conversions.get_mut(&target_asset_type) + { + *usage += used; + } + } + }; + break; + } + if let (Some(conv), false) = + (conv.get(&(asset_epoch, token_addr.clone())), at_target_asset_type) + { + if let Some(used) = self + .apply_conversion( &client, - conv.clone(), - asset_type, - denom_value, - usage, + conv, + (asset_epoch, token_addr.clone()), + value, &mut input, &mut output, ) - .await; - } else { - // At the target asset type. Then move component over to - // output. - let mut comp = MaspAmount::default(); - comp.insert((asset_epoch, token_addr.clone()), denom_value.into()); - for ((e, key), val) in input.iter() { - if *key == token_addr { - comp.insert((*e, key.clone()), *val); + .await + { + for denom in MaspDenom::iter() { + let asset_type = make_asset_type( + Some(asset_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, + ); + if let Some((_, _, usage)) = + conversions.get_mut(&asset_type) + { + *usage += used; } } - output += Amount::from(&comp); } + } else { + // At the target asset type. Then move component over to + // output. + let mut comp = MaspAmount::default(); + comp.insert((asset_epoch, token_addr.clone()), value); + for ((e, key), val) in input.iter() { + if *key == token_addr { + comp.insert((*e, key.clone()), *val); + } + } + output += comp; } } - (output, conversions) + // finally convert the rewards in epoch 0. + let mut comp = MaspAmount::default(); + for ((_, key), val) in input.drain() { + comp.insert((target_epoch, key), val); + } + output += comp; + println!("{:?}", output); + (output.into(), conversions) } /// Collect enough unspent notes in this context to exceed the given amount @@ -1356,6 +1377,9 @@ impl ShieldedContext { } } } + println!("val_acc: {:?}", val_acc); + println!("notes: {:?}", notes); + println!("conversions: {:?}", conversions); (val_acc, notes, conversions) } @@ -1529,6 +1553,58 @@ impl ShieldedContext { } MaspAmount(res) } + + /// Change conversions to use 256 bit numbers + async fn aggregate_conversions( + &mut self, + client: &HttpClient, + asset_epoch: Epoch, + target_epoch: Epoch, + token_addr: TokenAddress, + conversions: &mut Conversions, + ) -> HashMap<(Epoch, TokenAddress), MaspAmount> { + let mut conv = HashMap::new(); + for denom in MaspDenom::iter() { + let target_asset_type = make_asset_type( + Some(target_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, + ); + let asset_type = make_asset_type( + Some(asset_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, + ); + + self.query_allowed_conversion( + client.clone(), + target_asset_type, + conversions, + ) + .await; + self.query_allowed_conversion( + client.clone(), + asset_type, + conversions, + ) + .await; + if let Some((a, _, _)) = conversions.get(&target_asset_type) { + conv.insert( + (target_epoch, token_addr.clone()), + self.decode_all_amounts(&client, a.clone().into()).await, + ); + } + if let Some((a, _, _)) = conversions.get(&asset_type) { + conv.insert( + (asset_epoch, token_addr.clone()), + self.decode_all_amounts(&client, a.clone().into()).await, + ); + } + } + conv + } } /// Convert Namada amount and token type to MASP equivalents @@ -1538,7 +1614,13 @@ fn convert_amount( sub_prefix: &Option<&String>, val: &token::Amount, ) -> ([AssetType; 4], Amount) { - println!("{}", DenominatedAmount { amount: val.clone(), denom: 18.into()}); + println!( + "{}", + DenominatedAmount { + amount: val.clone(), + denom: 18.into() + } + ); let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { @@ -1662,12 +1744,14 @@ async fn gen_shielded_transfer( } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; + if value.is_positive() { + for denom in MaspDenom::iter() { + builder.add_convert( + conv.clone(), + denom.denominate(&token::Amount::from(*value)), + wit.clone(), + )?; + } } } } else { diff --git a/core/Cargo.toml b/core/Cargo.toml index 3a68ca3abc..ba3beeb5e6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -83,7 +83,7 @@ impl-num-traits = "0.1.2" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" diff --git a/core/src/types/token.rs b/core/src/types/token.rs index dccef5683d..910e51b180 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -696,8 +696,8 @@ impl MaspDenom { } /// Get the corresponding u64 word from the input uint256. - pub fn denominate_i64(&self, amount: &Change) -> i64 { - let val = amount.abs().0[*self as usize] as i64; + pub fn denominate_i128(&self, amount: &Change) -> i128 { + let val = amount.abs().0[*self as usize] as i128; if Change::is_negative(amount) { -val } else { @@ -1150,6 +1150,21 @@ mod tests { let non_zero = Amount::from_uint(1, 0).expect("Test failed"); assert!(!non_zero.is_zero()); } + + #[test] + fn testy_poo() { + let change = Change::from(30000000000000000000i128); + let output = Change::from(6893488147419103231i128); + + let amt = DenominatedAmount { + amount: Amount::from(change), + denom: 18.into(), + }; + println!("{}", amt); + println!("{:?}", change.0.0); + println!("{:?}", output.0.0); + assert!(false); + } } /// Helpers for testing with addresses. diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 640f79ee6c..0f5f617d8c 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -3,7 +3,7 @@ //! the backing type of token amounts. use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; -use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Rem, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; @@ -106,6 +106,11 @@ impl I256 { !self.non_negative() } + /// Check if the amount is positive (greater than zero) + pub fn is_positive(&self) -> bool { + self.non_negative() && !self.is_zero() + } + /// Get the absolute value pub fn abs(&self) -> Uint { if self.non_negative() { @@ -327,6 +332,29 @@ impl Div for I256 { } } +impl Div for I256 { + type Output = Self; + + fn div(self, rhs: I256) -> Self::Output { + if rhs.is_negative() { + -(self / rhs.abs()) + } else { + self / rhs.abs() + } + } +} + +impl Rem for I256 { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + if self.is_negative() { + -(Self(self.abs() % rhs.abs())) + } else { + Self(self.abs() % rhs.abs()) + } + } +} impl From for I256 { fn from(val: i128) -> Self { if val < 0 { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0f059bc663..4bc5900da0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -98,8 +98,8 @@ ibc = {version = "0.14.0", default-features = false, optional = true} ibc-proto = {version = "0.17.1", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 26582aa20e..453a2dd1c7 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,7 +27,9 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_core::types::token::{DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES}; +use namada_core::types::token::{ + DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES, +}; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -1137,10 +1139,8 @@ fn masp_incentives() -> Result<()> { client.exp_string("btc: 20")?; client.assert_success(); - let amt20 = - token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); - let amt30 = - token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); + let amt20 = token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); + let amt30 = token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1158,10 +1158,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_1-epoch_0) @@ -1180,10 +1181,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1223,10 +1225,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_2-epoch_0) @@ -1245,10 +1248,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1349,10 +1353,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1373,10 +1378,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary @@ -1442,10 +1448,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); - let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); ep = get_epoch(&test, &validator_one_rpc)?; @@ -1467,10 +1474,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); - let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary @@ -1534,10 +1542,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1558,10 +1567,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1583,10 +1593,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); - let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) @@ -1605,10 +1616,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); - let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1629,10 +1641,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary to prevent conversion expiry during transaction diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 6de40aed20..6d5f9c1433 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -15,7 +15,7 @@ abciplus = [ ] [dependencies] -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } namada_core = {path = "../core", default-features = false} namada_macros = {path = "../macros"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 4e9bd48a47..f359161765 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -16,6 +16,6 @@ abciplus = [ namada_core = {path = "../core", default-features = false} borsh = "0.9.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } hex = "0.4.3" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b4d4e87839..604abb3686 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2425,7 +2425,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "aes", "bip0039", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/wasm/checksums.json b/wasm/checksums.json index ebd6ccc2b1..d40a0ba7b4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.a63917a5f9971fd9e0c38039327c477f6a5ac70850a6714d6606c7d8c625ec5d.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.494320a5fd32eb09f8d5ec4e0950f57ab4da68aae5940e0e5d27030cca4334bf.wasm", - "tx_ibc.wasm": "tx_ibc.bed0f324d800aa2da8492767d7addd607d85918159407735120c8b8d0cdff882.wasm", - "tx_init_account.wasm": "tx_init_account.025fef966350ee18b643b1d2b83d99d264b82e4f247be3d3d9709cd68db1e99d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.02388a86b695c410830ce7eb03a948dcc1cb74b37cf2ff04488485a84a0fe4c6.wasm", - "tx_init_validator.wasm": "tx_init_validator.a2f7e28fc73d67b17d2dec7cda589d54c988ce34267d4a08d3b31c831973ac53.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.da894ee43081312e9f0dc7a1ebc010ea087348d0e17588a4f9ed876b5a64466c.wasm", - "tx_transfer.wasm": "tx_transfer.569847cc57c5872b6be5485d8e51ae7fdbb2c147b442f3dced2d7a47dad997d3.wasm", - "tx_unbond.wasm": "tx_unbond.02932b2c5b5ddc5c5fa23955014eb7e40551c62e065d135d3666003c7f53c206.wasm", - "tx_update_vp.wasm": "tx_update_vp.1910af6c9d86bb241b7e8d2aede17aa131e77ea888f37ab125bc3f8163bc45ec.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.99f64828e6e8571b7f4ae0050c1ed3f36184f984ed595f3b588a9c639943aede.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ccc14bcbbaaa4fe02cbc10b16301dee9c306cef6fad0b16091295cc105d6e79.wasm", - "vp_implicit.wasm": "vp_implicit.6434963d9844da7dc382fcc09808583bd57740f0ab0a6f13ad940e35ebe4dec3.wasm", - "vp_masp.wasm": "vp_masp.7e29cd91a45bf0e2f8976e9db739dcdb7ae6cbb38f8f5f6d82eedbe24857474b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.b581d376d9b4454800bb82c32a4377cfe4dc67ac2a08cf8d7ad9a7a80bf6a16b.wasm", - "vp_token.wasm": "vp_token.246329086c51c170ff890a3d361db42f2f34af9781a8b7b8f12c81764fcad223.wasm", - "vp_user.wasm": "vp_user.74814ed9d67905454fdda739d2f4959f08bca96095c0b6a30abcdb60b7474af2.wasm", - "vp_validator.wasm": "vp_validator.fe9672f182cdbece544d9bf2c5bd86ed984b07add82ecabc47380ae6685f90c7.wasm" + "tx_bond.wasm": "tx_bond.9259e3386b68f7ee0e35a3dfe7e96b1543e4ee8a6215010eae76de0104c9c9c2.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.fcc26c4678517d7097f41257c84c294b8c48eb4a0250b0ec74ee634d7935cf1e.wasm", + "tx_ibc.wasm": "tx_ibc.7ca7ee70182913d4167aeec2703b21712af82785856bab59a25f1fe732095dcc.wasm", + "tx_init_account.wasm": "tx_init_account.709c1a6b0de57f8cfb4bf84676f263370bf47256fcdbdf0cb91a8ef5fccb7cea.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e2568a89aed19218367d64f63a14a74973d7768577f2dcfd8c3b0e0558df6890.wasm", + "tx_init_validator.wasm": "tx_init_validator.63709c3297d1752efb9b7a943c82edb27aa61c82a95857739273080bee9d42cf.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.c78cb00453c5f2b0eaec49b36e895ac8d363261eb2af5e719dd641424847383f.wasm", + "tx_transfer.wasm": "tx_transfer.6a656bcb2a13ff3c4402ff7e275c080f4b1cf1de8f6112bbfbc03bf07167b297.wasm", + "tx_unbond.wasm": "tx_unbond.96ca5b536a02093d3f35a0e631670f10960c5bad220082e992003eb02c42f5f8.wasm", + "tx_update_vp.wasm": "tx_update_vp.8fc0dde790bf9304ce80fdbb85c85f6671dac6eedeb19cde17e825e6b477c23b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c5a856595b0a76b7191aec1bf1912d69e9d29e94e412ce7ebd7c96467900bfd8.wasm", + "tx_withdraw.wasm": "tx_withdraw.43cf91dc3fec04ffe740da865e55b00b6e6d71a6da9dcfde378506aedd715f24.wasm", + "vp_implicit.wasm": "vp_implicit.377e14b90713516ed12fd10975a13c9d74bd3a4de8f40be17e9aa4315953c282.wasm", + "vp_masp.wasm": "vp_masp.eb53123cad15bf82a09501dfe9da8dbfa0070468f6c8376dab4a62fd8ac58548.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9059ec6cd09ce0a7bde581b487d1e53921a9c26f29c76071d3a8d68fdba9bdab.wasm", + "vp_token.wasm": "vp_token.cd4d62be7dbb08a1f983857a9a59689b8ac12856ce15480173e26d94647adf86.wasm", + "vp_user.wasm": "vp_user.79e5cd7e4cb39fbb2b54c8a59b2a142f53050b030ec79acab37a36fd24402ece.wasm", + "vp_validator.wasm": "vp_validator.257b596e176e2db8057b2a6489f978b77c30dbd77282dd5e57809d75dd6d7f0d.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 61d6e9f4b3..9d56f416e4 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", optional = true } ripemd = "0.1.3" [dev-dependencies] diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index f8906a5f15..272d613bd4 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -129,10 +129,7 @@ fn validate_tx( } true } - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index c3f6424336..429008f660 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -102,10 +102,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 3309b75e26..f3cad12e93 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -102,10 +102,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); From cdde306a10f31b367f7e92d72e4528bb05c53aa6 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Jun 2023 17:35:55 +0200 Subject: [PATCH 58/69] WIP fixed bug in collecting unspent notes --- apps/src/lib/client/tx.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8f95c34f84..30369842ad 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -485,7 +485,7 @@ pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { if delta > Amount::zero() { let gap = dest - src; for (asset_type, value) in gap.components() { - if *value > 0 && delta[asset_type] > 0 { + if *value >= 0 && delta[asset_type] >= 0 { return true; } } @@ -1211,7 +1211,7 @@ impl ShieldedContext { let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible loop { - println!("Input {:?}", input); + println!("\n\nInput {:?}\n\n", input); let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; let at_target_asset_type = target_epoch == asset_epoch; @@ -1224,7 +1224,7 @@ impl ShieldedContext { &mut conversions, ) .await; - println!("conversions {:?}", conv); + println!("\n\nconversions {:?}\n\n", conv); if let (Some(conv), false) = ( conv.get(&(target_epoch, token_addr.clone())), at_target_asset_type, @@ -1306,7 +1306,7 @@ impl ShieldedContext { comp.insert((target_epoch, key), val); } output += comp; - println!("{:?}", output); + println!("\n\noutput {:?}\n\n", output); (output.into(), conversions) } From 16b17cce544676f6223638460bcdfa622fd85202 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 2 Jun 2023 13:39:24 +0200 Subject: [PATCH 59/69] WIP --- apps/src/bin/namada-client/cli.rs | 36 ++++++++++++++++++- apps/src/lib/client/tx.rs | 58 +++++++++++++++---------------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 1a60d3f01b..cb86945d6c 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -4,18 +4,52 @@ use std::time::Duration; use color_eyre::eyre::Result; use namada_apps::cli::cmds::*; -use namada_apps::cli::{self, safe_exit}; +use namada_apps::cli::{self, args, Context, safe_exit}; use namada_apps::client::{rpc, tx, utils}; use namada_apps::facade::tendermint::block::Height; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; use tokio::time::sleep; +use namada::types::token::{Amount, DenominatedAmount, Denomination}; +use namada_apps::cli::args::{InputAmount, Tx}; +use namada_apps::cli::context::FromContext; +use namada_apps::config::TendermintMode; pub async fn main() -> Result<()> { + let test_cmd = NamadaClientWithContext::TxTransfer(TxTransfer(args::TxTransfer{ + tx: Tx { + dry_run: false, + dump_tx: false, + force: false, + broadcast_only: false, + ledger_address: TendermintAddress::Tcp {peer_id: None, host: "127.0.0.1".to_string(), port: 27657}, + initialized_account_alias: None, + fee_amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::zero(), denom: Denomination(6) }), + fee_token: FromContext::new("NAM".into()), + gas_limit: Default::default(), + expiration: None, + signing_key: None, + signer: Some(FromContext::new("Bertha".into())), + }, + source: FromContext::new("xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0".into()), + target: FromContext::new("Christel".into()), + token: FromContext::new("ETH".into()), + sub_prefix: None, + amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::from_uint(30, 0).unwrap(), denom: Denomination(0) }), + })); match cli::namada_client_cli()? { cli::NamadaClient::WithContext(cmd_box) => { let (cmd, ctx) = *cmd_box; use NamadaClientWithContext as Sub; + /*let global_args = args::Global { + chain_id: None, + base_dir: " /tmp/.tmpmalzmo".into(), + wasm_dir: None, + mode: Some(TendermintMode::Full) + }; + + let ctx = Context::new(global_args).unwrap(); + println!("{:?}", test_cmd);*/ match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 30369842ad..085525b193 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -619,7 +619,7 @@ impl From for Amount { /// Represents the amount used of different conversions pub type Conversions = - HashMap, Change)>; + HashMap, i128)>; /// Represents an amount that is pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; @@ -1110,7 +1110,7 @@ impl ShieldedContext { .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv != Amount::zero() { - conv_entry.insert((conv.into(), path, Change::zero())); + conv_entry.insert((conv.into(), path, 0)); } } } @@ -1153,7 +1153,6 @@ impl ShieldedContext { #[allow(clippy::too_many_arguments)] async fn apply_conversion( &mut self, - client: &HttpClient, conv: &MaspAmount, asset_type: (Epoch, TokenAddress), value: Change, @@ -1164,10 +1163,9 @@ impl ShieldedContext { return None; } // If conversion if possible, accumulate the exchanged amount - let conv = self.decode_all_amounts(client, conv.into()).await; // println!("conv {:?}", conv); // The amount required of current asset to qualify for conversion - let threshold = token::Change::from(-conv[&asset_type]); + let threshold = -conv[&asset_type]; // println!("theshold: {}, value: {}", threshold, value); if threshold.is_zero() { eprintln!( @@ -1224,7 +1222,6 @@ impl ShieldedContext { &mut conversions, ) .await; - println!("\n\nconversions {:?}\n\n", conv); if let (Some(conv), false) = ( conv.get(&(target_epoch, token_addr.clone())), at_target_asset_type, @@ -1234,7 +1231,6 @@ impl ShieldedContext { ); if let Some(used) = self .apply_conversion( - &client, conv, (asset_epoch, token_addr.clone()), value, @@ -1253,7 +1249,9 @@ impl ShieldedContext { if let Some((_, _, usage)) = conversions.get_mut(&target_asset_type) { - *usage += used; + println!("used: {:?}", used); + *usage += denom.denominate_i128(&used); + println!("usage: {:?}", usage); } } }; @@ -1264,7 +1262,6 @@ impl ShieldedContext { { if let Some(used) = self .apply_conversion( - &client, conv, (asset_epoch, token_addr.clone()), value, @@ -1283,7 +1280,9 @@ impl ShieldedContext { if let Some((_, _, usage)) = conversions.get_mut(&asset_type) { - *usage += used; + println!("used: {:?}", used); + *usage += denom.denominate_i128(&used); + println!("usage: {:?}", usage); } } } @@ -1306,7 +1305,7 @@ impl ShieldedContext { comp.insert((target_epoch, key), val); } output += comp; - println!("\n\noutput {:?}\n\n", output); + println!("\n\nconversions {:?}\n\n", output); (output.into(), conversions) } @@ -1377,9 +1376,6 @@ impl ShieldedContext { } } } - println!("val_acc: {:?}", val_acc); - println!("notes: {:?}", notes); - println!("conversions: {:?}", conversions); (val_acc, notes, conversions) } @@ -1591,16 +1587,16 @@ impl ShieldedContext { ) .await; if let Some((a, _, _)) = conversions.get(&target_asset_type) { - conv.insert( - (target_epoch, token_addr.clone()), - self.decode_all_amounts(&client, a.clone().into()).await, - ); + let amt = self.decode_all_amounts(&client, a.clone().into()).await; + conv.entry((target_epoch, token_addr.clone())) + .and_modify(|e| *e += amt.clone()) + .or_insert(amt); } if let Some((a, _, _)) = conversions.get(&asset_type) { - conv.insert( - (asset_epoch, token_addr.clone()), - self.decode_all_amounts(&client, a.clone().into()).await, - ); + let amt = self.decode_all_amounts(&client, a.clone().into()).await; + conv.entry((asset_epoch, token_addr.clone())) + .and_modify(|e| *e += amt.clone()) + .or_insert(amt); } } conv @@ -1738,20 +1734,22 @@ async fn gen_shielded_transfer( epoch, ) .await; + println!("used convs: {:?}", used_convs); // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { + let decoded = ctx.shielded.decode_asset_type(client.clone(), note.asset_type.clone()).await.unwrap(); + println!("Adding note value: {:?}: {:?}", decoded, note.value); builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { if value.is_positive() { - for denom in MaspDenom::iter() { - builder.add_convert( - conv.clone(), - denom.denominate(&token::Amount::from(*value)), - wit.clone(), - )?; - } + println!("adding conversion {:?} -> {}", conv.assets, *value as u64); + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; } } } else { @@ -1809,6 +1807,8 @@ async fn gen_shielded_transfer( target_enc.as_ref(), )); for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { + let decoded = ctx.shielded.decode_asset_type(client.clone(), asset_type.clone()).await.unwrap(); + println!("Adding transparent outpt: {:?}: {}", decoded, denom.denominate(&amt)); builder.add_transparent_output( &TransparentAddress::PublicKey(hash.into()), *asset_type, From 087341d65a546983b830cf9f147aa008db2c0188 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Jun 2023 16:58:36 +0200 Subject: [PATCH 60/69] WIP fixing masp incentives --- Cargo.lock | 4 +- apps/Cargo.toml | 4 +- apps/src/lib/client/rpc.rs | 3 +- apps/src/lib/client/tx.rs | 77 ++++++++++------ core/Cargo.toml | 2 +- core/src/types/token.rs | 19 +++- core/src/types/uint.rs | 30 ++++++- shared/Cargo.toml | 4 +- tests/src/e2e/ledger_tests.rs | 127 +++++++++++++++------------ tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 4 +- wasm/Cargo.lock | 4 +- wasm/checksums.json | 36 ++++---- wasm/wasm_source/Cargo.toml | 4 +- wasm/wasm_source/src/vp_implicit.rs | 5 +- wasm/wasm_source/src/vp_user.rs | 5 +- wasm/wasm_source/src/vp_validator.rs | 5 +- 17 files changed, 200 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2beee040c5..ab6910b899 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3484,7 +3484,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "aes", "bip0039", @@ -3517,7 +3517,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 5e8b337f0e..cc319c8287 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -148,8 +148,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter", "json"]} websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", features = ["bundled-prover", "download-params"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} [dev-dependencies] diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d90283aa9f..317882f9a7 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -44,8 +44,7 @@ use namada::types::storage::{ BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, }; use namada::types::token::{ - balance_key, Change, DenominatedAmount, MaspDenom, - TokenAddress, Transfer, + balance_key, Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, }; use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 973580a653..01fa0391ad 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -53,7 +53,7 @@ use namada::types::storage::{ }; use namada::types::time::DateTimeUtc; use namada::types::token::{ - DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, + Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada::types::transaction::governance::{ @@ -520,8 +520,8 @@ pub struct MaspChange { pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); impl MaspAmount { - pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)>{ - let key = self.keys().next()?.clone(); + pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)> { + let key = self.keys().find(|(e, _)| e.0 != 0)?.clone(); let value = self.remove(&key).unwrap(); Some((key, value)) } @@ -581,6 +581,17 @@ impl std::ops::SubAssign for MaspAmount { } } +impl std::ops::Mul for MaspAmount { + type Output = Self; + + fn mul(mut self, rhs: Change) -> Self::Output { + for (_, value) in self.iter_mut() { + *value = *value * rhs + } + self + } +} + impl<'a> From<&'a MaspAmount> for Amount { fn from(masp_amount: &'a MaspAmount) -> Amount { let mut res = Amount::zero(); @@ -592,7 +603,7 @@ impl<'a> From<&'a MaspAmount> for Amount { &key.sub_prefix, denom, ); - res += Amount::from_pair(asset, denom.denominate_i64(val)) + res += Amount::from_pair(asset, denom.denominate_i128(val)) .unwrap(); } } @@ -600,9 +611,15 @@ impl<'a> From<&'a MaspAmount> for Amount { } } +impl From for Amount { + fn from(amt: MaspAmount) -> Self { + Self::from(&amt) + } +} + /// Represents the amount used of different conversions pub type Conversions = - HashMap, i128)>; + HashMap, Change)>; /// Represents an amount that is pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; @@ -1083,26 +1100,17 @@ impl ShieldedContext { client: HttpClient, asset_type: AssetType, conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i128)> { - match conversions.entry(asset_type) { - Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), - Entry::Vacant(conv_entry) => { - // Query for the ID of the last accepted transaction - let (addr, sub_prefix, denom, ep, conv, path): ( - Address, - _, - _, - _, - _, - _, - ) = query_conversion(client, asset_type).await?; + ) { + if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { + // Query for the ID of the last accepted transaction + if let Some((addr, sub_prefix, denom, ep, conv, path)) = + query_conversion(client, asset_type).await + { self.asset_types .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding - if conv == Amount::zero() { - None - } else { - Some(conv_entry.insert((Amount::into(conv), path, 0))) + if conv != Amount::zero() { + conv_entry.insert((conv.into(), path, Change::zero())); } } } @@ -1356,6 +1364,9 @@ impl ShieldedContext { } } } + println!("val_acc: {:?}", val_acc); + println!("notes: {:?}", notes); + println!("conversions: {:?}", conversions); (val_acc, notes, conversions) } @@ -1538,7 +1549,13 @@ fn convert_amount( sub_prefix: &Option<&String>, val: &token::Amount, ) -> ([AssetType; 4], Amount) { - println!("{}", DenominatedAmount { amount: val.clone(), denom: 18.into()}); + println!( + "{}", + DenominatedAmount { + amount: val.clone(), + denom: 18.into() + } + ); let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { @@ -1662,12 +1679,14 @@ async fn gen_shielded_transfer( } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; + if value.is_positive() { + for denom in MaspDenom::iter() { + builder.add_convert( + conv.clone(), + denom.denominate(&token::Amount::from(*value)), + wit.clone(), + )?; + } } } } else { diff --git a/core/Cargo.toml b/core/Cargo.toml index 3a68ca3abc..ba3beeb5e6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -83,7 +83,7 @@ impl-num-traits = "0.1.2" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" diff --git a/core/src/types/token.rs b/core/src/types/token.rs index dccef5683d..910e51b180 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -696,8 +696,8 @@ impl MaspDenom { } /// Get the corresponding u64 word from the input uint256. - pub fn denominate_i64(&self, amount: &Change) -> i64 { - let val = amount.abs().0[*self as usize] as i64; + pub fn denominate_i128(&self, amount: &Change) -> i128 { + let val = amount.abs().0[*self as usize] as i128; if Change::is_negative(amount) { -val } else { @@ -1150,6 +1150,21 @@ mod tests { let non_zero = Amount::from_uint(1, 0).expect("Test failed"); assert!(!non_zero.is_zero()); } + + #[test] + fn testy_poo() { + let change = Change::from(30000000000000000000i128); + let output = Change::from(6893488147419103231i128); + + let amt = DenominatedAmount { + amount: Amount::from(change), + denom: 18.into(), + }; + println!("{}", amt); + println!("{:?}", change.0.0); + println!("{:?}", output.0.0); + assert!(false); + } } /// Helpers for testing with addresses. diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 640f79ee6c..0f5f617d8c 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -3,7 +3,7 @@ //! the backing type of token amounts. use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; -use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Rem, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; @@ -106,6 +106,11 @@ impl I256 { !self.non_negative() } + /// Check if the amount is positive (greater than zero) + pub fn is_positive(&self) -> bool { + self.non_negative() && !self.is_zero() + } + /// Get the absolute value pub fn abs(&self) -> Uint { if self.non_negative() { @@ -327,6 +332,29 @@ impl Div for I256 { } } +impl Div for I256 { + type Output = Self; + + fn div(self, rhs: I256) -> Self::Output { + if rhs.is_negative() { + -(self / rhs.abs()) + } else { + self / rhs.abs() + } + } +} + +impl Rem for I256 { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + if self.is_negative() { + -(Self(self.abs() % rhs.abs())) + } else { + Self(self.abs() % rhs.abs()) + } + } +} impl From for I256 { fn from(val: i128) -> Self { if val < 0 { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0f059bc663..4bc5900da0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -98,8 +98,8 @@ ibc = {version = "0.14.0", default-features = false, optional = true} ibc-proto = {version = "0.17.1", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 26582aa20e..453a2dd1c7 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,7 +27,9 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_core::types::token::{DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES}; +use namada_core::types::token::{ + DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES, +}; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -1137,10 +1139,8 @@ fn masp_incentives() -> Result<()> { client.exp_string("btc: 20")?; client.assert_success(); - let amt20 = - token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); - let amt30 = - token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); + let amt20 = token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); + let amt30 = token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1158,10 +1158,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_1-epoch_0) @@ -1180,10 +1181,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1223,10 +1225,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_2-epoch_0) @@ -1245,10 +1248,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1349,10 +1353,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1373,10 +1378,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary @@ -1442,10 +1448,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); - let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); ep = get_epoch(&test, &validator_one_rpc)?; @@ -1467,10 +1474,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); - let denominated = DenominatedAmount{ amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary @@ -1534,10 +1542,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1558,10 +1567,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into() }; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1583,10 +1593,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); - let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) @@ -1605,10 +1616,11 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); - let denominated = DenominatedAmount {amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1629,10 +1641,11 @@ fn masp_incentives() -> Result<()> { )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); - let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into()}; - client.exp_string(&format!( - "nam: {}", denominated, - ))?; + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary to prevent conversion expiry during transaction diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 6de40aed20..6d5f9c1433 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -15,7 +15,7 @@ abciplus = [ ] [dependencies] -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } namada_core = {path = "../core", default-features = false} namada_macros = {path = "../macros"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 4e9bd48a47..f359161765 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -16,6 +16,6 @@ abciplus = [ namada_core = {path = "../core", default-features = false} borsh = "0.9.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37" } hex = "0.4.3" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b4d4e87839..604abb3686 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2425,7 +2425,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "aes", "bip0039", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=402b08accff6cf656b7f63823d074931cdcf7773#402b08accff6cf656b7f63823d074931cdcf7773" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/wasm/checksums.json b/wasm/checksums.json index ebd6ccc2b1..d40a0ba7b4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.a63917a5f9971fd9e0c38039327c477f6a5ac70850a6714d6606c7d8c625ec5d.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.494320a5fd32eb09f8d5ec4e0950f57ab4da68aae5940e0e5d27030cca4334bf.wasm", - "tx_ibc.wasm": "tx_ibc.bed0f324d800aa2da8492767d7addd607d85918159407735120c8b8d0cdff882.wasm", - "tx_init_account.wasm": "tx_init_account.025fef966350ee18b643b1d2b83d99d264b82e4f247be3d3d9709cd68db1e99d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.02388a86b695c410830ce7eb03a948dcc1cb74b37cf2ff04488485a84a0fe4c6.wasm", - "tx_init_validator.wasm": "tx_init_validator.a2f7e28fc73d67b17d2dec7cda589d54c988ce34267d4a08d3b31c831973ac53.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.da894ee43081312e9f0dc7a1ebc010ea087348d0e17588a4f9ed876b5a64466c.wasm", - "tx_transfer.wasm": "tx_transfer.569847cc57c5872b6be5485d8e51ae7fdbb2c147b442f3dced2d7a47dad997d3.wasm", - "tx_unbond.wasm": "tx_unbond.02932b2c5b5ddc5c5fa23955014eb7e40551c62e065d135d3666003c7f53c206.wasm", - "tx_update_vp.wasm": "tx_update_vp.1910af6c9d86bb241b7e8d2aede17aa131e77ea888f37ab125bc3f8163bc45ec.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.99f64828e6e8571b7f4ae0050c1ed3f36184f984ed595f3b588a9c639943aede.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ccc14bcbbaaa4fe02cbc10b16301dee9c306cef6fad0b16091295cc105d6e79.wasm", - "vp_implicit.wasm": "vp_implicit.6434963d9844da7dc382fcc09808583bd57740f0ab0a6f13ad940e35ebe4dec3.wasm", - "vp_masp.wasm": "vp_masp.7e29cd91a45bf0e2f8976e9db739dcdb7ae6cbb38f8f5f6d82eedbe24857474b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.b581d376d9b4454800bb82c32a4377cfe4dc67ac2a08cf8d7ad9a7a80bf6a16b.wasm", - "vp_token.wasm": "vp_token.246329086c51c170ff890a3d361db42f2f34af9781a8b7b8f12c81764fcad223.wasm", - "vp_user.wasm": "vp_user.74814ed9d67905454fdda739d2f4959f08bca96095c0b6a30abcdb60b7474af2.wasm", - "vp_validator.wasm": "vp_validator.fe9672f182cdbece544d9bf2c5bd86ed984b07add82ecabc47380ae6685f90c7.wasm" + "tx_bond.wasm": "tx_bond.9259e3386b68f7ee0e35a3dfe7e96b1543e4ee8a6215010eae76de0104c9c9c2.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.fcc26c4678517d7097f41257c84c294b8c48eb4a0250b0ec74ee634d7935cf1e.wasm", + "tx_ibc.wasm": "tx_ibc.7ca7ee70182913d4167aeec2703b21712af82785856bab59a25f1fe732095dcc.wasm", + "tx_init_account.wasm": "tx_init_account.709c1a6b0de57f8cfb4bf84676f263370bf47256fcdbdf0cb91a8ef5fccb7cea.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e2568a89aed19218367d64f63a14a74973d7768577f2dcfd8c3b0e0558df6890.wasm", + "tx_init_validator.wasm": "tx_init_validator.63709c3297d1752efb9b7a943c82edb27aa61c82a95857739273080bee9d42cf.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.c78cb00453c5f2b0eaec49b36e895ac8d363261eb2af5e719dd641424847383f.wasm", + "tx_transfer.wasm": "tx_transfer.6a656bcb2a13ff3c4402ff7e275c080f4b1cf1de8f6112bbfbc03bf07167b297.wasm", + "tx_unbond.wasm": "tx_unbond.96ca5b536a02093d3f35a0e631670f10960c5bad220082e992003eb02c42f5f8.wasm", + "tx_update_vp.wasm": "tx_update_vp.8fc0dde790bf9304ce80fdbb85c85f6671dac6eedeb19cde17e825e6b477c23b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c5a856595b0a76b7191aec1bf1912d69e9d29e94e412ce7ebd7c96467900bfd8.wasm", + "tx_withdraw.wasm": "tx_withdraw.43cf91dc3fec04ffe740da865e55b00b6e6d71a6da9dcfde378506aedd715f24.wasm", + "vp_implicit.wasm": "vp_implicit.377e14b90713516ed12fd10975a13c9d74bd3a4de8f40be17e9aa4315953c282.wasm", + "vp_masp.wasm": "vp_masp.eb53123cad15bf82a09501dfe9da8dbfa0070468f6c8376dab4a62fd8ac58548.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9059ec6cd09ce0a7bde581b487d1e53921a9c26f29c76071d3a8d68fdba9bdab.wasm", + "vp_token.wasm": "vp_token.cd4d62be7dbb08a1f983857a9a59689b8ac12856ce15480173e26d94647adf86.wasm", + "vp_user.wasm": "vp_user.79e5cd7e4cb39fbb2b54c8a59b2a142f53050b030ec79acab37a36fd24402ece.wasm", + "vp_validator.wasm": "vp_validator.257b596e176e2db8057b2a6489f978b77c30dbd77282dd5e57809d75dd6d7f0d.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 61d6e9f4b3..9d56f416e4 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "402b08accff6cf656b7f63823d074931cdcf7773", optional = true } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "ea4fcc68d9412b94042c1c2f0ea0212394e1be37", optional = true } ripemd = "0.1.3" [dev-dependencies] diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index f8906a5f15..272d613bd4 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -129,10 +129,7 @@ fn validate_tx( } true } - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index c3f6424336..429008f660 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -102,10 +102,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 3309b75e26..f3cad12e93 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -102,10 +102,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token { - owner, - .. - } => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); From ffd72ec7ce405079da5c1dab6b107c32096bc8f5 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Jun 2023 17:35:55 +0200 Subject: [PATCH 61/69] WIP fixed bug in collecting unspent notes --- apps/src/lib/client/tx.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 01fa0391ad..4fd55c4c1b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -485,7 +485,7 @@ pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { if delta > Amount::zero() { let gap = dest - src; for (asset_type, value) in gap.components() { - if *value > 0 && delta[asset_type] > 0 { + if *value >= 0 && delta[asset_type] >= 0 { return true; } } @@ -1205,6 +1205,8 @@ impl ShieldedContext { let mut output = Amount::zero(); // Repeatedly exchange assets until it is no longer possible loop { + println!("\n\nInput {:?}\n\n", input); + let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( @@ -1294,7 +1296,14 @@ impl ShieldedContext { } } } - (output, conversions) + // finally convert the rewards in epoch 0. + let mut comp = MaspAmount::default(); + for ((_, key), val) in input.drain() { + comp.insert((target_epoch, key), val); + } + output += comp; + println!("\n\noutput {:?}\n\n", output); + (output.into(), conversions) } /// Collect enough unspent notes in this context to exceed the given amount From b4f616b2ff54bd0a667b7aa44614da07b4d992e1 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 2 Jun 2023 13:39:24 +0200 Subject: [PATCH 62/69] WIP --- apps/src/bin/namada-client/cli.rs | 36 ++++++++++++++++++++++++++++++- apps/src/lib/client/tx.rs | 31 +++++++++++++------------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 1a60d3f01b..cb86945d6c 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -4,18 +4,52 @@ use std::time::Duration; use color_eyre::eyre::Result; use namada_apps::cli::cmds::*; -use namada_apps::cli::{self, safe_exit}; +use namada_apps::cli::{self, args, Context, safe_exit}; use namada_apps::client::{rpc, tx, utils}; use namada_apps::facade::tendermint::block::Height; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; use tokio::time::sleep; +use namada::types::token::{Amount, DenominatedAmount, Denomination}; +use namada_apps::cli::args::{InputAmount, Tx}; +use namada_apps::cli::context::FromContext; +use namada_apps::config::TendermintMode; pub async fn main() -> Result<()> { + let test_cmd = NamadaClientWithContext::TxTransfer(TxTransfer(args::TxTransfer{ + tx: Tx { + dry_run: false, + dump_tx: false, + force: false, + broadcast_only: false, + ledger_address: TendermintAddress::Tcp {peer_id: None, host: "127.0.0.1".to_string(), port: 27657}, + initialized_account_alias: None, + fee_amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::zero(), denom: Denomination(6) }), + fee_token: FromContext::new("NAM".into()), + gas_limit: Default::default(), + expiration: None, + signing_key: None, + signer: Some(FromContext::new("Bertha".into())), + }, + source: FromContext::new("xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0".into()), + target: FromContext::new("Christel".into()), + token: FromContext::new("ETH".into()), + sub_prefix: None, + amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::from_uint(30, 0).unwrap(), denom: Denomination(0) }), + })); match cli::namada_client_cli()? { cli::NamadaClient::WithContext(cmd_box) => { let (cmd, ctx) = *cmd_box; use NamadaClientWithContext as Sub; + /*let global_args = args::Global { + chain_id: None, + base_dir: " /tmp/.tmpmalzmo".into(), + wasm_dir: None, + mode: Some(TendermintMode::Full) + }; + + let ctx = Context::new(global_args).unwrap(); + println!("{:?}", test_cmd);*/ match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 4fd55c4c1b..f6f5607728 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -619,7 +619,7 @@ impl From for Amount { /// Represents the amount used of different conversions pub type Conversions = - HashMap, Change)>; + HashMap, i128)>; /// Represents an amount that is pub type MaspDenominatedAmount = Amount<(TokenAddress, MaspDenom)>; @@ -1110,7 +1110,7 @@ impl ShieldedContext { .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv != Amount::zero() { - conv_entry.insert((conv.into(), path, Change::zero())); + conv_entry.insert((conv.into(), path, 0)); } } } @@ -1301,9 +1301,9 @@ impl ShieldedContext { for ((_, key), val) in input.drain() { comp.insert((target_epoch, key), val); } - output += comp; - println!("\n\noutput {:?}\n\n", output); - (output.into(), conversions) + output += Amount::from(comp); + println!("\n\nconversions {:?}\n\n", output); + (output, conversions) } /// Collect enough unspent notes in this context to exceed the given amount @@ -1373,9 +1373,6 @@ impl ShieldedContext { } } } - println!("val_acc: {:?}", val_acc); - println!("notes: {:?}", notes); - println!("conversions: {:?}", conversions); (val_acc, notes, conversions) } @@ -1682,20 +1679,22 @@ async fn gen_shielded_transfer( epoch, ) .await; + println!("used convs: {:?}", used_convs); // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { + let decoded = ctx.shielded.decode_asset_type(client.clone(), note.asset_type.clone()).await.unwrap(); + println!("Adding note value: {:?}: {:?}", decoded, note.value); builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { if value.is_positive() { - for denom in MaspDenom::iter() { - builder.add_convert( - conv.clone(), - denom.denominate(&token::Amount::from(*value)), - wit.clone(), - )?; - } + println!("adding conversion {:?} -> {}", conv.assets, *value as u64); + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; } } } else { @@ -1753,6 +1752,8 @@ async fn gen_shielded_transfer( target_enc.as_ref(), )); for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { + let decoded = ctx.shielded.decode_asset_type(client.clone(), asset_type.clone()).await.unwrap(); + println!("Adding transparent outpt: {:?}: {}", decoded, denom.denominate(&amt)); builder.add_transparent_output( &TransparentAddress::PublicKey(hash.into()), *asset_type, From d2a07edd3a2dc6341bd3a42eaa19cfdf87206e22 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 2 Jun 2023 16:23:32 +0200 Subject: [PATCH 63/69] WIP small bug fixes and a debugger can be attached --- apps/src/bin/namada-client/cli.rs | 22 +++++++------- apps/src/lib/client/tx.rs | 50 +++++++++++++++++-------------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index cb86945d6c..3fff3c9bad 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -3,6 +3,7 @@ use std::time::Duration; use color_eyre::eyre::Result; +use eyre::Chain; use namada_apps::cli::cmds::*; use namada_apps::cli::{self, args, Context, safe_exit}; use namada_apps::client::{rpc, tx, utils}; @@ -10,6 +11,7 @@ use namada_apps::facade::tendermint::block::Height; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; use tokio::time::sleep; +use namada::types::chain::ChainId; use namada::types::token::{Amount, DenominatedAmount, Denomination}; use namada_apps::cli::args::{InputAmount, Tx}; use namada_apps::cli::context::FromContext; @@ -37,20 +39,20 @@ pub async fn main() -> Result<()> { sub_prefix: None, amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::from_uint(30, 0).unwrap(), denom: Denomination(0) }), })); - match cli::namada_client_cli()? { - cli::NamadaClient::WithContext(cmd_box) => { - let (cmd, ctx) = *cmd_box; + //match cli::namada_client_cli()? { + //cli::NamadaClient::WithContext(cmd_box) => { + //let (cmd, ctx) = *cmd_box; use NamadaClientWithContext as Sub; - /*let global_args = args::Global { - chain_id: None, - base_dir: " /tmp/.tmpmalzmo".into(), + let global_args = args::Global { + chain_id: Some(ChainId("e2e-test.1842a9ed7737c1c61d588".into())), + base_dir: "/tmp/.tmpmalzmo".into(), wasm_dir: None, mode: Some(TendermintMode::Full) }; let ctx = Context::new(global_args).unwrap(); - println!("{:?}", test_cmd);*/ - match cmd { + + match test_cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { wait_until_node_is_synched(&args.tx.ledger_address).await; @@ -176,7 +178,7 @@ pub async fn main() -> Result<()> { rpc::query_protocol_parameters(ctx, args).await; } } - } + /*} cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { // Utils cmds Utils::JoinNetwork(JoinNetwork(args)) => { @@ -192,7 +194,7 @@ pub async fn main() -> Result<()> { utils::init_genesis_validator(global_args, args) } }, - } + }*/ Ok(()) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f6f5607728..815e86b827 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,10 +52,7 @@ use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{ - Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, - PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; +use namada::types::token::{Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, Denomination}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; @@ -1155,38 +1152,45 @@ impl ShieldedContext { &mut self, client: &HttpClient, conv: AllowedConversion, - asset_type: AssetType, + asset_type: (Epoch, TokenAddress, MaspDenom), value: i128, usage: &mut i128, input: &mut MaspAmount, - output: &mut Amount, + output: &mut MaspAmount, ) { // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); + //println!("apply converson: {:?}", conv); // The amount required of current asset to qualify for conversion - let threshold = -conv[&asset_type]; + let masp_asset = make_asset_type( + Some(asset_type.0), + &asset_type.1.address, + &asset_type.1.sub_prefix, + asset_type.2 + ); + let threshold = -conv[&masp_asset]; + //println!("threshold {}, value {}", threshold, value); if threshold == 0 { eprintln!( "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", - asset_type + masp_asset ); } // We should use an amount of the AllowedConversion that almost // cancels the original amount - if threshold > value { - return - } let required = value / threshold; + //println!("required: {}", required); // Forget about the trace amount left over because we cannot // realize its value - let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); + let trace = MaspAmount(HashMap::from([((asset_type.0 , asset_type.1), Change::from(value % threshold))])); // Record how much more of the given conversion has been used *usage += required; // Apply the conversions to input and move the trace amount to output *input += self - .decode_all_amounts(client, conv.clone() * required - &trace) - .await; + .decode_all_amounts(client, conv.clone() * required) + .await + - trace.clone(); *output += trace; } @@ -1202,7 +1206,7 @@ impl ShieldedContext { mut conversions: Conversions, ) -> (Amount, Conversions) { // Where we will store our exchanged value - let mut output = Amount::zero(); + let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible loop { println!("\n\nInput {:?}\n\n", input); @@ -1223,7 +1227,7 @@ impl ShieldedContext { ); let at_target_asset_type = target_epoch == asset_epoch; - let denom_value = denom.denominate(&token::Amount::from(value)) as i128; + let denom_value = denom.denominate_i128(&value); _ = self .query_allowed_conversion( client.clone(), @@ -1246,7 +1250,7 @@ impl ShieldedContext { self.apply_conversion( &client, conv.clone(), - asset_type, + (asset_epoch, token_addr.clone(), denom), denom_value, usage, &mut input, @@ -1275,7 +1279,7 @@ impl ShieldedContext { self.apply_conversion( &client, conv.clone(), - asset_type, + (asset_epoch, token_addr.clone(), denom), denom_value, usage, &mut input, @@ -1292,7 +1296,7 @@ impl ShieldedContext { comp.insert((*e, key.clone()), *val); } } - output += Amount::from(&comp); + output += comp; } } } @@ -1301,9 +1305,9 @@ impl ShieldedContext { for ((_, key), val) in input.drain() { comp.insert((target_epoch, key), val); } - output += Amount::from(comp); + output += comp; println!("\n\nconversions {:?}\n\n", output); - (output, conversions) + (output.into(), conversions) } /// Collect enough unspent notes in this context to exceed the given amount @@ -1373,6 +1377,8 @@ impl ShieldedContext { } } } + println!("val_acc {:?}", val_acc); + println!("notes {:?}", notes); (val_acc, notes, conversions) } @@ -1679,7 +1685,7 @@ async fn gen_shielded_transfer( epoch, ) .await; - println!("used convs: {:?}", used_convs); + //println!("used convs: {:?}", used_convs); // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { let decoded = ctx.shielded.decode_asset_type(client.clone(), note.asset_type.clone()).await.unwrap(); From 5d5772deee191e3ce409696f075da8b0c5c9c54c Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 5 Jun 2023 17:10:17 +0200 Subject: [PATCH 64/69] WIP fixed unspent note calcs --- apps/src/bin/namada-client/cli.rs | 50 +++------------------ apps/src/lib/client/tx.rs | 13 +++--- tests/src/e2e/ledger_tests.rs | 74 +++++++++++++++++++++---------- wasm/checksums.json | 4 +- 4 files changed, 65 insertions(+), 76 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 3fff3c9bad..1a60d3f01b 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -3,56 +3,20 @@ use std::time::Duration; use color_eyre::eyre::Result; -use eyre::Chain; use namada_apps::cli::cmds::*; -use namada_apps::cli::{self, args, Context, safe_exit}; +use namada_apps::cli::{self, safe_exit}; use namada_apps::client::{rpc, tx, utils}; use namada_apps::facade::tendermint::block::Height; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; use tokio::time::sleep; -use namada::types::chain::ChainId; -use namada::types::token::{Amount, DenominatedAmount, Denomination}; -use namada_apps::cli::args::{InputAmount, Tx}; -use namada_apps::cli::context::FromContext; -use namada_apps::config::TendermintMode; pub async fn main() -> Result<()> { - let test_cmd = NamadaClientWithContext::TxTransfer(TxTransfer(args::TxTransfer{ - tx: Tx { - dry_run: false, - dump_tx: false, - force: false, - broadcast_only: false, - ledger_address: TendermintAddress::Tcp {peer_id: None, host: "127.0.0.1".to_string(), port: 27657}, - initialized_account_alias: None, - fee_amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::zero(), denom: Denomination(6) }), - fee_token: FromContext::new("NAM".into()), - gas_limit: Default::default(), - expiration: None, - signing_key: None, - signer: Some(FromContext::new("Bertha".into())), - }, - source: FromContext::new("xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0".into()), - target: FromContext::new("Christel".into()), - token: FromContext::new("ETH".into()), - sub_prefix: None, - amount: InputAmount::Unvalidated(DenominatedAmount { amount: Amount::from_uint(30, 0).unwrap(), denom: Denomination(0) }), - })); - //match cli::namada_client_cli()? { - //cli::NamadaClient::WithContext(cmd_box) => { - //let (cmd, ctx) = *cmd_box; + match cli::namada_client_cli()? { + cli::NamadaClient::WithContext(cmd_box) => { + let (cmd, ctx) = *cmd_box; use NamadaClientWithContext as Sub; - let global_args = args::Global { - chain_id: Some(ChainId("e2e-test.1842a9ed7737c1c61d588".into())), - base_dir: "/tmp/.tmpmalzmo".into(), - wasm_dir: None, - mode: Some(TendermintMode::Full) - }; - - let ctx = Context::new(global_args).unwrap(); - - match test_cmd { + match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { wait_until_node_is_synched(&args.tx.ledger_address).await; @@ -178,7 +142,7 @@ pub async fn main() -> Result<()> { rpc::query_protocol_parameters(ctx, args).await; } } - /*} + } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { // Utils cmds Utils::JoinNetwork(JoinNetwork(args)) => { @@ -194,7 +158,7 @@ pub async fn main() -> Result<()> { utils::init_genesis_validator(global_args, args) } }, - }*/ + } Ok(()) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 815e86b827..a0d646160a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1158,9 +1158,12 @@ impl ShieldedContext { input: &mut MaspAmount, output: &mut MaspAmount, ) { + // we do not need to convert negative values + if value <= 0 { + return; + } // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); - //println!("apply converson: {:?}", conv); // The amount required of current asset to qualify for conversion let masp_asset = make_asset_type( Some(asset_type.0), @@ -1169,7 +1172,6 @@ impl ShieldedContext { asset_type.2 ); let threshold = -conv[&masp_asset]; - //println!("threshold {}, value {}", threshold, value); if threshold == 0 { eprintln!( "Asset threshold of selected conversion for asset type {} is \ @@ -1180,7 +1182,6 @@ impl ShieldedContext { // We should use an amount of the AllowedConversion that almost // cancels the original amount let required = value / threshold; - //println!("required: {}", required); // Forget about the trace amount left over because we cannot // realize its value let trace = MaspAmount(HashMap::from([((asset_type.0 , asset_type.1), Change::from(value % threshold))])); @@ -1209,8 +1210,6 @@ impl ShieldedContext { let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible loop { - println!("\n\nInput {:?}\n\n", input); - let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( @@ -1228,7 +1227,7 @@ impl ShieldedContext { let at_target_asset_type = target_epoch == asset_epoch; let denom_value = denom.denominate_i128(&value); - _ = self + self .query_allowed_conversion( client.clone(), target_asset_type, @@ -1259,7 +1258,7 @@ impl ShieldedContext { .await; break; } - _ = self + self .query_allowed_conversion( client.clone(), asset_type, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 453a2dd1c7..9b761098ad 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1140,7 +1140,7 @@ fn masp_incentives() -> Result<()> { client.assert_success(); let amt20 = token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); - let amt30 = token::Amount::from_uint(30, ETH_DENOMINATION).unwrap(); + let amt10 = token::Amount::from_uint(10, ETH_DENOMINATION).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1258,7 +1258,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary let ep3 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30 ETH from Albert to PA(B) + // Send 10 ETH from Albert to PA(B) let mut client = run!( test, Bin::Client, @@ -1271,16 +1271,18 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "30", + "10", "--node", &validator_one_rpc ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); - // Assert ETH balance at VK(B) is 30 + // Assert ETH balance at VK(B) is 10 let mut client = run!( test, Bin::Client, @@ -1295,7 +1297,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - client.exp_string("eth: 30")?; + client.exp_string("eth: 10")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1319,7 +1321,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary let ep4 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Assert ETH balance at VK(B) is 30 + // Assert ETH balance at VK(B) is 10 let mut client = run!( test, Bin::Client, @@ -1334,10 +1336,10 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - client.exp_string("eth: 30")?; + client.exp_string("eth: 10")?; client.assert_success(); - // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_4-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_4-epoch_3) let mut client = run!( test, Bin::Client, @@ -1352,7 +1354,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1361,7 +1363,7 @@ fn masp_incentives() -> Result<()> { client.assert_success(); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_4-epoch_0)+30*ETH_reward*(epoch_4-epoch_3) + // 20*BTC_reward*(epoch_4-epoch_0)+10*ETH_reward*(epoch_4-epoch_3) let mut client = run!( test, Bin::Client, @@ -1377,7 +1379,7 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1388,7 +1390,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary let ep5 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30 ETH from SK(B) to Christel + // Send 10 ETH from SK(B) to Christel let mut client = run!( test, Bin::Client, @@ -1401,7 +1403,7 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "30", + "10", "--signer", BERTHA, "--node", @@ -1409,6 +1411,24 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + println!("{:?}", vec![ + "transfer", + "--source", + B_SPENDING_KEY, + "--target", + CHRISTEL, + "--token", + ETH, + "--amount", + "10", + "--signer", + BERTHA, + "--node", + &validator_one_rpc + ]); + assert!(false); + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1432,7 +1452,7 @@ fn masp_incentives() -> Result<()> { let mut ep = get_epoch(&test, &validator_one_rpc)?; - // Assert NAM balance at VK(B) is 30*ETH_reward*(ep-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(ep-epoch_3) let mut client = run!( test, Bin::Client, @@ -1447,7 +1467,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1457,7 +1477,7 @@ fn masp_incentives() -> Result<()> { ep = get_epoch(&test, &validator_one_rpc)?; // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_5-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + // 20*BTC_reward*(epoch_5-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1473,7 +1493,7 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1505,6 +1525,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1566,7 +1588,7 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1600,7 +1622,7 @@ fn masp_incentives() -> Result<()> { client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); - // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1615,7 +1637,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1624,7 +1646,7 @@ fn masp_incentives() -> Result<()> { client.assert_success(); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_6-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + // 20*BTC_reward*(epoch_6-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1640,7 +1662,7 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1652,7 +1674,7 @@ fn masp_incentives() -> Result<()> { // construction let _ep8 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30*ETH_reward*(epoch_5-epoch_3) NAM from SK(B) to Christel + // Send 10*ETH_reward*(epoch_5-epoch_3) NAM from SK(B) to Christel let mut client = run!( test, Bin::Client, @@ -1665,7 +1687,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt30 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) + &((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) .to_string_native(), "--signer", BERTHA, @@ -1674,6 +1696,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1702,6 +1726,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); diff --git a/wasm/checksums.json b/wasm/checksums.json index d40a0ba7b4..017817884b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -15,6 +15,6 @@ "vp_masp.wasm": "vp_masp.eb53123cad15bf82a09501dfe9da8dbfa0070468f6c8376dab4a62fd8ac58548.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.9059ec6cd09ce0a7bde581b487d1e53921a9c26f29c76071d3a8d68fdba9bdab.wasm", "vp_token.wasm": "vp_token.cd4d62be7dbb08a1f983857a9a59689b8ac12856ce15480173e26d94647adf86.wasm", - "vp_user.wasm": "vp_user.79e5cd7e4cb39fbb2b54c8a59b2a142f53050b030ec79acab37a36fd24402ece.wasm", - "vp_validator.wasm": "vp_validator.257b596e176e2db8057b2a6489f978b77c30dbd77282dd5e57809d75dd6d7f0d.wasm" + "vp_user.wasm": "vp_user.ef4584780d875a7fd9d242356f5606dcc91613083dcadc942b3f44dd64e2afbe.wasm", + "vp_validator.wasm": "vp_validator.02274dc44079fa9c8d06d2b85eb08e7a8cbc7e7b4831b16b2c44b851b0708361.wasm" } \ No newline at end of file From 057b4d3e1f4e4747a4d6da3134e97d0413cc64fb Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 7 Jun 2023 16:00:50 +0200 Subject: [PATCH 65/69] WIP masp incentives passes. But I did something reckless to get there. --- apps/src/lib/client/tx.rs | 19 +++++++++++------- core/src/ledger/storage/masp_conversions.rs | 2 +- tests/src/e2e/ledger_tests.rs | 16 --------------- wasm/checksums.json | 2 +- wasm/wasm_source/src/vp_masp.rs | 22 +++++++++++++++++++-- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a0d646160a..28e5a0cd9c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -479,12 +479,14 @@ pub fn find_valid_diversifier( /// Determine if using the current note would actually bring us closer to our /// target pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { - if delta > Amount::zero() { - let gap = dest - src; - for (asset_type, value) in gap.components() { - if *value >= 0 && delta[asset_type] >= 0 { - return true; - } + println!("delta: {:?}", delta); + println!("src: {:?}", src); + println!("dest: {:?}", dest); + + let gap = dest - src; + for (asset_type, value) in gap.components() { + if *value >= 0 && delta[asset_type] >= 0 { + return true; } } false @@ -1303,9 +1305,11 @@ impl ShieldedContext { let mut comp = MaspAmount::default(); for ((_, key), val) in input.drain() { comp.insert((target_epoch, key), val); + } output += comp; - println!("\n\nconversions {:?}\n\n", output); + println!("\n\noutput {:?}\n\n", output); + println!("\n\nconversions {:?}\n\n", conversions); (output.into(), conversions) } @@ -1347,6 +1351,7 @@ impl ShieldedContext { // The amount contributed by this note before conversion let pre_contr = Amount::from_pair(note.asset_type, note.value) .expect("received note has invalid value or asset type"); + println!("note.asset_type: {:?}, note.value {:?}", note.asset_type, note.value); let input = self.decode_all_amounts(&client, pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 4dec02d4f5..8cf7a3c21a 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -66,7 +66,7 @@ where // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. let reward_asset = - encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); + encode_asset_type(address::nam(), &None, MaspDenom::Zero, wl_storage.storage.block.epoch); // Conversions from the previous to current asset for each address let mut current_convs = BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9b761098ad..de2b11c9da 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1411,22 +1411,6 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - println!("{:?}", vec![ - "transfer", - "--source", - B_SPENDING_KEY, - "--target", - CHRISTEL, - "--token", - ETH, - "--amount", - "10", - "--signer", - BERTHA, - "--node", - &validator_one_rpc - ]); - assert!(false); client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; diff --git a/wasm/checksums.json b/wasm/checksums.json index 017817884b..880551cd83 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,7 +12,7 @@ "tx_vote_proposal.wasm": "tx_vote_proposal.c5a856595b0a76b7191aec1bf1912d69e9d29e94e412ce7ebd7c96467900bfd8.wasm", "tx_withdraw.wasm": "tx_withdraw.43cf91dc3fec04ffe740da865e55b00b6e6d71a6da9dcfde378506aedd715f24.wasm", "vp_implicit.wasm": "vp_implicit.377e14b90713516ed12fd10975a13c9d74bd3a4de8f40be17e9aa4315953c282.wasm", - "vp_masp.wasm": "vp_masp.eb53123cad15bf82a09501dfe9da8dbfa0070468f6c8376dab4a62fd8ac58548.wasm", + "vp_masp.wasm": "vp_masp.38d624456bf2d4f0151d5d3c3a8ef52c4d412243830a14c9e4bd408b24bad6a3.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.9059ec6cd09ce0a7bde581b487d1e53921a9c26f29c76071d3a8d68fdba9bdab.wasm", "vp_token.wasm": "vp_token.cd4d62be7dbb08a1f983857a9a59689b8ac12856ce15480173e26d94647adf86.wasm", "vp_user.wasm": "vp_user.ef4584780d875a7fd9d242356f5606dcc91613083dcadc942b3f44dd64e2afbe.wasm", diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 7fee57705b..a618aa165f 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -55,7 +55,13 @@ fn valid_transfer_amount( transparented value {}", unshielded_transfer_value, reporeted_transparent_value - ) + ); + log_string(format!( + "The unshielded amount {} disagrees with the calculated masp \ + transparented value {}", + unshielded_transfer_value, + reporeted_transparent_value + )) } res } @@ -165,6 +171,12 @@ fn validate_tx( beteween 1 and 4 but is {}", shielded_tx.vout.len() ); + + log_string(format!( + "Transparent output to a transaction to the masp must be \ + beteween 1 and 4 but is {}", + shielded_tx.vout.len() + )); return reject(); } @@ -194,10 +206,11 @@ fn validate_tx( // This is encoded via the asset types. continue; } - if valid_transfer_amount( + if !valid_transfer_amount( out.value, denom.denominate(&transfer.amount.amount), ) { + log_string("Invalid transfer amount"); return reject(); } @@ -229,6 +242,10 @@ fn validate_tx( "the public key of the output account does \ not match the transfer target" ); + log_string(format!( + "the public key of the output account does \ + not match the transfer target" + )); return reject(); } } @@ -238,6 +255,7 @@ fn validate_tx( // one or more of the denoms in the batch failed to verify // the asset derivation. if valid_count != out_length { + log_string("one or more of the denoms in the batch failed to verify the asset derivation."); return reject(); } } else { From 1899e65d238c211c5d5d688b467a943fa993ce86 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 8 Jun 2023 01:24:06 +0200 Subject: [PATCH 66/69] WIP fixed masp incentives --- apps/src/lib/client/tx.rs | 61 +++++++-------------- core/src/ledger/storage/masp_conversions.rs | 2 +- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 28e5a0cd9c..64203c4182 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -479,10 +479,6 @@ pub fn find_valid_diversifier( /// Determine if using the current note would actually bring us closer to our /// target pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { - println!("delta: {:?}", delta); - println!("src: {:?}", src); - println!("dest: {:?}", dest); - let gap = dest - src; for (asset_type, value) in gap.components() { if *value >= 0 && delta[asset_type] >= 0 { @@ -520,7 +516,7 @@ pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); impl MaspAmount { pub fn pop(&mut self) -> Option<((Epoch, TokenAddress), token::Change)> { - let key = self.keys().find(|(e, _)| e.0 != 0)?.clone(); + let key = self.keys().next().cloned()?; let value = self.remove(&key).unwrap(); Some((key, value)) } @@ -560,7 +556,6 @@ impl std::ops::AddAssign for MaspAmount { } } -// please stop copying and pasting make a function impl std::ops::Sub for MaspAmount { type Output = MaspAmount; @@ -570,6 +565,7 @@ impl std::ops::Sub for MaspAmount { .and_modify(|val| *val -= value) .or_insert(value); } + self.0.retain(|_, v| !v.is_zero()); self } } @@ -1166,6 +1162,7 @@ impl ShieldedContext { } // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); + println!("conv: {:?}", conv); // The amount required of current asset to qualify for conversion let masp_asset = make_asset_type( Some(asset_type.0), @@ -1212,7 +1209,10 @@ impl ShieldedContext { let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible loop { - let Some(((asset_epoch, token_addr), value)) = input.pop() else { break }; + let (asset_epoch, token_addr, value) = match input.iter().next() { + Some(((e, a), v)) => (*e, a.clone(), *v), + _ => break + }; for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( Some(target_epoch), @@ -1229,16 +1229,20 @@ impl ShieldedContext { let at_target_asset_type = target_epoch == asset_epoch; let denom_value = denom.denominate_i128(&value); - self - .query_allowed_conversion( + self.query_allowed_conversion( client.clone(), target_asset_type, &mut conversions, ) .await; - + self.query_allowed_conversion( + client.clone(), + asset_type, + &mut conversions, + ) + .await; if let (Some((conv, _wit, usage)), false) = ( - conversions.get_mut(&target_asset_type), + conversions.get_mut(&asset_type), at_target_asset_type, ) { println!( @@ -1258,22 +1262,13 @@ impl ShieldedContext { &mut output, ) .await; - break; - } - self - .query_allowed_conversion( - client.clone(), - asset_type, - &mut conversions, - ) - .await; - if let (Some((conv, _wit, usage)), false) = - (conversions.get_mut(&asset_type), at_target_asset_type) + } else if let (Some((conv, _wit, usage)), false) = + (conversions.get_mut(&target_asset_type), at_target_asset_type) { println!( "converting latest asset type to target asset type..." ); - // Not at the target asset type, yes at the latest asset + // Not at the target asset type, yet at the latest asset // type. Apply inverse conversion to get // from latest asset type to the target // asset type. @@ -1293,23 +1288,15 @@ impl ShieldedContext { let mut comp = MaspAmount::default(); comp.insert((asset_epoch, token_addr.clone()), denom_value.into()); for ((e, key), val) in input.iter() { - if *key == token_addr { + if *key == token_addr && *e == asset_epoch { comp.insert((*e, key.clone()), *val); } } - output += comp; + output += comp.clone(); + input -= comp; } } } - // finally convert the rewards in epoch 0. - let mut comp = MaspAmount::default(); - for ((_, key), val) in input.drain() { - comp.insert((target_epoch, key), val); - - } - output += comp; - println!("\n\noutput {:?}\n\n", output); - println!("\n\nconversions {:?}\n\n", conversions); (output.into(), conversions) } @@ -1351,7 +1338,6 @@ impl ShieldedContext { // The amount contributed by this note before conversion let pre_contr = Amount::from_pair(note.asset_type, note.value) .expect("received note has invalid value or asset type"); - println!("note.asset_type: {:?}, note.value {:?}", note.asset_type, note.value); let input = self.decode_all_amounts(&client, pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( @@ -1381,8 +1367,6 @@ impl ShieldedContext { } } } - println!("val_acc {:?}", val_acc); - println!("notes {:?}", notes); (val_acc, notes, conversions) } @@ -1689,7 +1673,6 @@ async fn gen_shielded_transfer( epoch, ) .await; - //println!("used convs: {:?}", used_convs); // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { let decoded = ctx.shielded.decode_asset_type(client.clone(), note.asset_type.clone()).await.unwrap(); @@ -1762,8 +1745,6 @@ async fn gen_shielded_transfer( target_enc.as_ref(), )); for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { - let decoded = ctx.shielded.decode_asset_type(client.clone(), asset_type.clone()).await.unwrap(); - println!("Adding transparent outpt: {:?}: {}", decoded, denom.denominate(&amt)); builder.add_transparent_output( &TransparentAddress::PublicKey(hash.into()), *asset_type, diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 8cf7a3c21a..4dec02d4f5 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -66,7 +66,7 @@ where // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. let reward_asset = - encode_asset_type(address::nam(), &None, MaspDenom::Zero, wl_storage.storage.block.epoch); + encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); // Conversions from the previous to current asset for each address let mut current_convs = BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); From b380c20fa77edfa07a17b5b2965bdc3c29e48cbb Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 9 Jun 2023 10:44:54 +0200 Subject: [PATCH 67/69] Masp e2e tests now passing --- apps/src/lib/client/rpc.rs | 4 +-- apps/src/lib/client/tx.rs | 38 ++++++++++------------ apps/src/lib/client/utils.rs | 60 +++++++++++++++++++++++++++++++++++ tests/src/e2e/ledger_tests.rs | 11 +++++-- tests/src/e2e/setup.rs | 3 +- 5 files changed, 88 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 317882f9a7..af64aa08d3 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -650,6 +650,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { ) .await } + // Now print out the received quantities according to CLI arguments match (balance, args.token.as_ref(), args.sub_prefix.as_ref()) { (Err(PinnedBalanceError::InvalidViewingKey), _, _) => println!( @@ -668,8 +669,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { sub_prefix: sub_prefix .map(|string| Key::parse(string).unwrap()), }; - - let total_balance = balance[&(epoch, token_address.clone())]; + let total_balance = balance.get(&(epoch, token_address.clone())).cloned().unwrap_or_default(); if total_balance.is_zero() { println!( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 64203c4182..ef27aa8fe1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,7 +52,7 @@ use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, Denomination}; +use namada::types::token::{Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; @@ -72,6 +72,7 @@ use crate::client::rpc::{ }; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; +use crate::client::utils::with_spinny_wheel; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; @@ -1162,7 +1163,6 @@ impl ShieldedContext { } // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); - println!("conv: {:?}", conv); // The amount required of current asset to qualify for conversion let masp_asset = make_asset_type( Some(asset_type.0), @@ -1549,20 +1549,11 @@ fn convert_amount( sub_prefix: &Option<&String>, val: &token::Amount, ) -> ([AssetType; 4], Amount) { - println!( - "{}", - DenominatedAmount { - amount: val.clone(), - denom: 18.into() - } - ); let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { let asset_type = make_asset_type(Some(epoch), token, sub_prefix, denom); - let inner = denom.denominate(val); - println!("{}", inner); // Combine the value and unit into one amount amount += Amount::from_nonnegative(asset_type, denom.denominate(val)) @@ -1675,14 +1666,11 @@ async fn gen_shielded_transfer( .await; // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { - let decoded = ctx.shielded.decode_asset_type(client.clone(), note.asset_type.clone()).await.unwrap(); - println!("Adding note value: {:?}: {:?}", decoded, note.value); builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { if value.is_positive() { - println!("adding conversion {:?} -> {}", conv.assets, *value as u64); builder.add_convert( conv.clone(), *value as u64, @@ -1745,18 +1733,24 @@ async fn gen_shielded_transfer( target_enc.as_ref(), )); for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - *asset_type, - denom.denominate(&amt), - )?; + let vout = denom.denominate(&amt); + if vout != 0 { + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + *asset_type, + vout, + )?; + } } } // Build and return the constructed transaction - builder - .build(consensus_branch_id, &prover) - .map(|(a, b)| Some((a, b, epoch))) + with_spinny_wheel( + "Building proofs for MASP transaction (this can take some time) ... ", + move || builder + .build(consensus_branch_id, &prover) + .map(|(a, b)| Some((a, b, epoch))) + ) } pub async fn submit_transfer(mut ctx: Context, mut args: args::TxTransfer) { diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 9e901cfaf1..588fd48428 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -44,6 +44,41 @@ const DEFAULT_NETWORK_CONFIGS_SERVER: &str = /// We do pre-genesis validator set up in this directory pub const PRE_GENESIS_DIR: &str = "pre-genesis"; +/// Environment variable set to reduce the amount of printing the CLI +/// tools perform. Extra prints, while good for UI, clog up test tooling. +pub const REDUCED_CLI_PRINTING: &str = "REDUCED_CLI_PRINTING"; + +macro_rules! cli_print { + ($($arg:tt)*) => {{ + if std::env::var(REDUCED_CLI_PRINTING) + .map(|v| if v.to_lowercase().trim() == "true" { + false + } else { + true + }).unwrap_or(true) { + let mut stdout = std::io::stdout().lock(); + _ = stdout.write_all(format!("{}", std::format_args!($($arg)*)).as_bytes()); + _ = stdout.flush(); + } + }}; +} + +#[allow(unused)] +macro_rules! cli_println { + ($($arg:tt)*) => {{ + if std::env::var(REDUCED_CLI_PRINTING) + .map(|v| if v.to_lowercase().trim() == "true" { + false + } else { + true + }).unwrap_or(true) { + let mut stdout = std::io::stdout().lock(); + _ = stdout.write_all(format!("{}\n", std::format_args!($($arg)*)).as_bytes()); + _ = stdout.flush(); + } + }}; +} + /// Configure Namada to join an existing network. The chain must be released in /// the repository. pub async fn join_network( @@ -1066,6 +1101,30 @@ pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } +/// Add a spinning wheel to a message for long running commands. +/// Can be turned off for E2E tests by setting the `REDUCED_CLI_PRINTING` +/// environment variable. +pub fn with_spinny_wheel(msg: &str, func: F) -> Out +where + F: FnOnce() -> Out + Send + 'static, + Out: Send + 'static +{ + let task = std::thread::spawn(func); + let spinny_wheel = "|/-\\"; + print!("{}", msg); + _ = std::io::stdout().flush(); + for c in spinny_wheel.chars().cycle() { + cli_print!("{}", c); + std::thread::sleep(std::time::Duration::from_secs(1)); + cli_print!("{}", (8u8 as char)); + if task.is_finished() { + break; + } + } + println!(""); + task.join().unwrap() +} + fn is_valid_validator_for_current_chain( tendermint_node_pk: &common::PublicKey, genesis_config: &GenesisConfig, @@ -1078,3 +1137,4 @@ fn is_valid_validator_for_current_chain( } }) } + diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index de2b11c9da..3a6725e0d9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -28,7 +28,7 @@ use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; use namada_core::types::token::{ - DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES, + DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, }; use namada_test_utils::TestWasms; use serde_json::json; @@ -833,7 +833,7 @@ fn masp_txs_and_queries() -> Result<()> { } else { tx_args.clone() }; - let mut client = run!(test, Bin::Client, tx_args, Some(300))?; + let mut client = run!(test, Bin::Client, tx_args, Some(720))?; if *tx_result == "Transaction is valid" && !dry_run { client.exp_string("Transaction accepted")?; @@ -863,7 +863,7 @@ fn masp_pinned_txs() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(60), + epochs_per_year: epochs_per_year_from_min_duration(120), ..genesis.parameters }; GenesisConfig { @@ -926,6 +926,9 @@ fn masp_pinned_txs() -> Result<()> { client.exp_string("has not yet been consumed")?; client.assert_success(); + // Wait till epoch boundary + let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + // Send 20 BTC from Albert to PPA(C) let mut client = run!( test, @@ -945,6 +948,8 @@ fn masp_pinned_txs() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 103312271d..c19039f87d 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -26,6 +26,7 @@ use namada_apps::{config, wallet}; use rand::Rng; use serde_json; use tempfile::{tempdir, TempDir}; +use namada_apps::client::utils::REDUCED_CLI_PRINTING; use crate::e2e::helpers::generate_bin_command; @@ -125,7 +126,7 @@ pub fn network( eprintln!("Failed setting up colorful error reports {}", err); } }); - + env::set_var(REDUCED_CLI_PRINTING, "true"); let working_dir = working_dir(); let test_dir = TestDir::new(); From 61e9bd62a7d664752153ea0320834e1f48771c95 Mon Sep 17 00:00:00 2001 From: satan Date: Sun, 11 Jun 2023 12:04:12 +0200 Subject: [PATCH 68/69] [fix]: Fixed serialization bug with amounts. Fixed remaining masp e2e tests --- apps/src/lib/client/rpc.rs | 5 +- apps/src/lib/client/tx.rs | 53 +++++++++++-------- apps/src/lib/client/utils.rs | 3 +- .../lib/node/ledger/shell/finalize_block.rs | 4 +- apps/src/lib/node/ledger/shell/init_chain.rs | 15 +++--- apps/src/lib/node/ledger/storage/rocksdb.rs | 9 ++-- core/src/ledger/storage/mockdb.rs | 5 +- core/src/ledger/storage/mod.rs | 4 +- core/src/ledger/storage/wl_storage.rs | 4 +- core/src/types/token.rs | 29 ++++------ tests/src/e2e/ledger_tests.rs | 4 +- tests/src/e2e/setup.rs | 2 +- wasm/checksums.json | 2 +- wasm/wasm_source/src/vp_masp.rs | 8 +-- 14 files changed, 74 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index af64aa08d3..7dbc6f6923 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -669,7 +669,10 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { sub_prefix: sub_prefix .map(|string| Key::parse(string).unwrap()), }; - let total_balance = balance.get(&(epoch, token_address.clone())).cloned().unwrap_or_default(); + let total_balance = balance + .get(&(epoch, token_address.clone())) + .cloned() + .unwrap_or_default(); if total_balance.is_zero() { println!( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ef27aa8fe1..f079e2ef4b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,7 +52,10 @@ use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX}; +use namada::types::token::{ + Change, DenominatedAmount, MaspDenom, TokenAddress, Transfer, HEAD_TX_KEY, + PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; @@ -1168,7 +1171,7 @@ impl ShieldedContext { Some(asset_type.0), &asset_type.1.address, &asset_type.1.sub_prefix, - asset_type.2 + asset_type.2, ); let threshold = -conv[&masp_asset]; if threshold == 0 { @@ -1183,7 +1186,10 @@ impl ShieldedContext { let required = value / threshold; // Forget about the trace amount left over because we cannot // realize its value - let trace = MaspAmount(HashMap::from([((asset_type.0 , asset_type.1), Change::from(value % threshold))])); + let trace = MaspAmount(HashMap::from([( + (asset_type.0, asset_type.1), + Change::from(value % threshold), + )])); // Record how much more of the given conversion has been used *usage += required; // Apply the conversions to input and move the trace amount to output @@ -1211,7 +1217,7 @@ impl ShieldedContext { loop { let (asset_epoch, token_addr, value) = match input.iter().next() { Some(((e, a), v)) => (*e, a.clone(), *v), - _ => break + _ => break, }; for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( @@ -1230,21 +1236,20 @@ impl ShieldedContext { let denom_value = denom.denominate_i128(&value); self.query_allowed_conversion( - client.clone(), - target_asset_type, - &mut conversions, - ) - .await; + client.clone(), + target_asset_type, + &mut conversions, + ) + .await; self.query_allowed_conversion( client.clone(), asset_type, &mut conversions, ) .await; - if let (Some((conv, _wit, usage)), false) = ( - conversions.get_mut(&asset_type), - at_target_asset_type, - ) { + if let (Some((conv, _wit, usage)), false) = + (conversions.get_mut(&asset_type), at_target_asset_type) + { println!( "converting current asset type to latest asset type..." ); @@ -1262,9 +1267,10 @@ impl ShieldedContext { &mut output, ) .await; - } else if let (Some((conv, _wit, usage)), false) = - (conversions.get_mut(&target_asset_type), at_target_asset_type) - { + } else if let (Some((conv, _wit, usage)), false) = ( + conversions.get_mut(&target_asset_type), + at_target_asset_type, + ) { println!( "converting latest asset type to target asset type..." ); @@ -1281,12 +1287,15 @@ impl ShieldedContext { &mut input, &mut output, ) - .await; + .await; } else { // At the target asset type. Then move component over to // output. let mut comp = MaspAmount::default(); - comp.insert((asset_epoch, token_addr.clone()), denom_value.into()); + comp.insert( + (asset_epoch, token_addr.clone()), + denom_value.into(), + ); for ((e, key), val) in input.iter() { if *key == token_addr && *e == asset_epoch { comp.insert((*e, key.clone()), *val); @@ -1747,9 +1756,11 @@ async fn gen_shielded_transfer( // Build and return the constructed transaction with_spinny_wheel( "Building proofs for MASP transaction (this can take some time) ... ", - move || builder - .build(consensus_branch_id, &prover) - .map(|(a, b)| Some((a, b, epoch))) + move || { + builder + .build(consensus_branch_id, &prover) + .map(|(a, b)| Some((a, b, epoch))) + }, ) } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 588fd48428..94a1b84b99 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1107,7 +1107,7 @@ pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { pub fn with_spinny_wheel(msg: &str, func: F) -> Out where F: FnOnce() -> Out + Send + 'static, - Out: Send + 'static + Out: Send + 'static, { let task = std::thread::spawn(func); let spinny_wheel = "|/-\\"; @@ -1137,4 +1137,3 @@ fn is_valid_validator_for_current_chain( } }) } - diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e9870d8ff1..189852d836 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -885,7 +885,6 @@ fn pos_votes_from_abci( #[cfg(test)] mod test_finalize_block { use std::collections::{BTreeMap, BTreeSet}; - use std::str::FromStr; use data_encoding::HEXUPPER; use namada::ledger::parameters::EpochDuration; @@ -1341,12 +1340,11 @@ mod test_finalize_block { // Collect all storage key-vals into a sorted map let store_block_state = |shell: &TestShell| -> BTreeMap<_, _> { - let prefix: Key = FromStr::from_str("").unwrap(); shell .wl_storage .storage .db - .iter_prefix(&prefix) + .iter_prefix(None) .map(|(key, val, _gas)| (key, val)) .collect() }; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 7f44315706..6d94bde41d 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -52,11 +52,11 @@ where let errors = self.wl_storage.storage.chain_id.validate(genesis_bytes); use itertools::Itertools; - // assert!( - // errors.is_empty(), - // "Chain ID validation failed: {}", - // errors.into_iter().format(". ") - // ); + assert!( + errors.is_empty(), + "Chain ID validation failed: {}", + errors.into_iter().format(". ") + ); } #[cfg(feature = "dev")] let genesis = genesis::genesis(num_validators); @@ -475,11 +475,9 @@ where #[cfg(test)] mod test { use std::collections::BTreeMap; - use std::str::FromStr; use namada::ledger::storage::DBIter; use namada::types::chain::ChainId; - use namada::types::storage; use crate::facade::tendermint_proto::abci::RequestInitChain; use crate::facade::tendermint_proto::google::protobuf::Timestamp; @@ -493,12 +491,11 @@ mod test { // Collect all storage key-vals into a sorted map let store_block_state = |shell: &TestShell| -> BTreeMap<_, _> { - let prefix: storage::Key = FromStr::from_str("").unwrap(); shell .wl_storage .storage .db - .iter_prefix(&prefix) + .iter_prefix(None) .map(|(key, val, _gas)| (key, val)) .collect() }; diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index ce3802e0fe..8dba637a37 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -380,7 +380,7 @@ impl RocksDB { let batch = Mutex::new(batch); tracing::info!("Restoring previous hight subspace diffs"); - self.iter_prefix(&Key::default()) + self.iter_prefix(None) .par_bridge() .try_for_each(|(key, _value, _gas)| -> Result<()> { // Restore previous height diff if present, otherwise delete the @@ -1138,7 +1138,7 @@ impl<'iter> DBIter<'iter> for RocksDB { fn iter_prefix( &'iter self, - prefix: &Key, + prefix: Option<&Key>, ) -> PersistentPrefixIterator<'iter> { iter_subspace_prefix(self, prefix) } @@ -1180,10 +1180,11 @@ impl<'iter> DBIter<'iter> for RocksDB { fn iter_subspace_prefix<'iter>( db: &'iter RocksDB, - prefix: &Key, + prefix: Option<&Key>, ) -> PersistentPrefixIterator<'iter> { let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); + let prefix_str = prefix.map(|k| k.to_string()).unwrap_or_default(); + let prefix = format!("{}{}", db_prefix, prefix_str); iter_prefix(db, db_prefix, prefix) } diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 585edb1381..cb2a7a9679 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -478,9 +478,10 @@ impl DB for MockDB { impl<'iter> DBIter<'iter> for MockDB { type PrefixIter = MockPrefixIterator; - fn iter_prefix(&'iter self, prefix: &Key) -> MockPrefixIterator { + fn iter_prefix(&'iter self, prefix: Option<&Key>) -> MockPrefixIterator { let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); + let prefix_str = prefix.map(|k| k.to_string()).unwrap_or_default(); + let prefix = format!("{}{}", db_prefix, prefix_str); let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 0560844d6a..5bc9a7bf3f 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -319,7 +319,7 @@ pub trait DBIter<'iter> { /// /// Read account subspace key value pairs with the given prefix from the DB, /// ordered by the storage keys. - fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; + fn iter_prefix(&'iter self, prefix: Option<&Key>) -> Self::PrefixIter; /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; @@ -546,7 +546,7 @@ where &self, prefix: &Key, ) -> (>::PrefixIter, u64) { - (self.db.iter_prefix(prefix), prefix.len() as _) + (self.db.iter_prefix(Some(prefix)), prefix.len() as _) } /// Returns a prefix iterator and the gas cost diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 88d44bf871..ada2f4ecef 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -235,7 +235,7 @@ where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, { - let storage_iter = storage.db.iter_prefix(prefix).peekable(); + let storage_iter = storage.db.iter_prefix(Some(prefix)).peekable(); let write_log_iter = write_log.iter_prefix_pre(prefix).peekable(); ( PrefixIter { @@ -260,7 +260,7 @@ where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, { - let storage_iter = storage.db.iter_prefix(prefix).peekable(); + let storage_iter = storage.db.iter_prefix(Some(prefix)).peekable(); let write_log_iter = write_log.iter_prefix_post(prefix).peekable(); ( PrefixIter { diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 910e51b180..b72804962f 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -392,12 +392,10 @@ impl<'de> serde::Deserialize<'de> for Amount { where D: serde::Deserializer<'de>, { - use serde::de::Error; let amount_string: String = serde::Deserialize::deserialize(deserializer)?; - Ok(Self { - raw: Uint::from_str(&amount_string).map_err(D::Error::custom)?, - }) + let amt = DenominatedAmount::from_str(&amount_string).unwrap(); + Ok(amt.amount) } } @@ -1065,6 +1063,14 @@ mod tests { assert_eq!(max.checked_sub(max), Some(zero)); } + #[test] + fn test_serialization_round_trip() { + let amount: Amount = serde_json::from_str(r#""1000000000""#).unwrap(); + assert_eq!(amount, Amount{raw: Uint::from(1000000000)}); + let serialized = serde_json::to_string(&amount).unwrap(); + assert_eq!(serialized, r#""1000000000""#); + } + #[test] fn test_amount_checked_add() { let max = Amount::max(); @@ -1150,21 +1156,6 @@ mod tests { let non_zero = Amount::from_uint(1, 0).expect("Test failed"); assert!(!non_zero.is_zero()); } - - #[test] - fn testy_poo() { - let change = Change::from(30000000000000000000i128); - let output = Change::from(6893488147419103231i128); - - let amt = DenominatedAmount { - amount: Amount::from(change), - denom: 18.into(), - }; - println!("{}", amt); - println!("{:?}", change.0.0); - println!("{:?}", output.0.0); - assert!(false); - } } /// Helpers for testing with addresses. diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 3a6725e0d9..0dad2ed1d8 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,9 +27,7 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_core::types::token::{ - DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, -}; +use namada_core::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index c19039f87d..fc72fcf262 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -21,12 +21,12 @@ use eyre::{eyre, Context}; use itertools::{Either, Itertools}; use namada::types::chain::ChainId; use namada_apps::client::utils; +use namada_apps::client::utils::REDUCED_CLI_PRINTING; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; use namada_apps::{config, wallet}; use rand::Rng; use serde_json; use tempfile::{tempdir, TempDir}; -use namada_apps::client::utils::REDUCED_CLI_PRINTING; use crate::e2e::helpers::generate_bin_command; diff --git a/wasm/checksums.json b/wasm/checksums.json index 880551cd83..c23470d836 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,7 +12,7 @@ "tx_vote_proposal.wasm": "tx_vote_proposal.c5a856595b0a76b7191aec1bf1912d69e9d29e94e412ce7ebd7c96467900bfd8.wasm", "tx_withdraw.wasm": "tx_withdraw.43cf91dc3fec04ffe740da865e55b00b6e6d71a6da9dcfde378506aedd715f24.wasm", "vp_implicit.wasm": "vp_implicit.377e14b90713516ed12fd10975a13c9d74bd3a4de8f40be17e9aa4315953c282.wasm", - "vp_masp.wasm": "vp_masp.38d624456bf2d4f0151d5d3c3a8ef52c4d412243830a14c9e4bd408b24bad6a3.wasm", + "vp_masp.wasm": "vp_masp.68c7729e65ba47cfcca93009f6c1e7bfc36d785d6defb7f03b5d362276602c00.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.9059ec6cd09ce0a7bde581b487d1e53921a9c26f29c76071d3a8d68fdba9bdab.wasm", "vp_token.wasm": "vp_token.cd4d62be7dbb08a1f983857a9a59689b8ac12856ce15480173e26d94647adf86.wasm", "vp_user.wasm": "vp_user.ef4584780d875a7fd9d242356f5606dcc91613083dcadc942b3f44dd64e2afbe.wasm", diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index a618aa165f..186e0d21aa 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -59,8 +59,7 @@ fn valid_transfer_amount( log_string(format!( "The unshielded amount {} disagrees with the calculated masp \ transparented value {}", - unshielded_transfer_value, - reporeted_transparent_value + unshielded_transfer_value, reporeted_transparent_value )) } res @@ -255,7 +254,10 @@ fn validate_tx( // one or more of the denoms in the batch failed to verify // the asset derivation. if valid_count != out_length { - log_string("one or more of the denoms in the batch failed to verify the asset derivation."); + log_string( + "one or more of the denoms in the batch failed to verify \ + the asset derivation.", + ); return reject(); } } else { From 38e514c2e498f46d5d7d6ed93871688e72d8ec0e Mon Sep 17 00:00:00 2001 From: satan Date: Sun, 11 Jun 2023 12:27:34 +0200 Subject: [PATCH 69/69] [chore] Fixed clippy and formatting --- apps/src/lib/client/tx.rs | 10 +++---- apps/src/lib/client/utils.rs | 2 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 8 +++--- core/src/types/token.rs | 7 ++++- wasm/wasm_source/src/vp_implicit.rs | 22 ++++------------ wasm/wasm_source/src/vp_masp.rs | 29 --------------------- wasm/wasm_source/src/vp_user.rs | 24 +++++------------ wasm/wasm_source/src/vp_validator.rs | 24 +++++------------ wasm_for_tests/wasm_source/Cargo.lock | 4 +-- 9 files changed, 35 insertions(+), 95 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f079e2ef4b..26e98bf5cf 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1214,11 +1214,11 @@ impl ShieldedContext { // Where we will store our exchanged value let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible - loop { - let (asset_epoch, token_addr, value) = match input.iter().next() { - Some(((e, a), v)) => (*e, a.clone(), *v), - _ => break, - }; + while let Some(((asset_epoch, token_addr), value)) = input.iter().next() + { + let value = *value; + let asset_epoch = *asset_epoch; + let token_addr = token_addr.clone(); for denom in MaspDenom::iter() { let target_asset_type = make_asset_type( Some(target_epoch), diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 94a1b84b99..bddbefb7ab 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1121,7 +1121,7 @@ where break; } } - println!(""); + println!(); task.join().unwrap() } diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 8dba637a37..9adea77f4d 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -380,9 +380,8 @@ impl RocksDB { let batch = Mutex::new(batch); tracing::info!("Restoring previous hight subspace diffs"); - self.iter_prefix(None) - .par_bridge() - .try_for_each(|(key, _value, _gas)| -> Result<()> { + self.iter_prefix(None).par_bridge().try_for_each( + |(key, _value, _gas)| -> Result<()> { // Restore previous height diff if present, otherwise delete the // subspace key @@ -401,7 +400,8 @@ impl RocksDB { } Ok(()) - })?; + }, + )?; // Delete any height-prepended key, including subspace diff keys let mut batch = batch.into_inner().unwrap(); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b72804962f..429388e6e3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1066,7 +1066,12 @@ mod tests { #[test] fn test_serialization_round_trip() { let amount: Amount = serde_json::from_str(r#""1000000000""#).unwrap(); - assert_eq!(amount, Amount{raw: Uint::from(1000000000)}); + assert_eq!( + amount, + Amount { + raw: Uint::from(1000000000) + } + ); let serialized = serde_json::to_string(&amount).unwrap(); assert_eq!(serialized, r#""1000000000""#); } diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 272d613bd4..14570bb2a0 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -11,7 +11,7 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::{Key, KeySeg}; +use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; @@ -19,8 +19,6 @@ enum KeyType<'a> { /// Public key - written once revealed Pk(&'a Address), Token { - token: &'a Address, - sub_prefix: Option, owner: &'a Address, }, PoS, @@ -32,22 +30,12 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some([token, owner]) = - token::is_any_token_balance_key(key) - { - Self::Token { - token, - owner, - sub_prefix: None, - } - } else if let Some((sub, [token, owner])) = + } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token { - token, - owner, - sub_prefix: Some(sub), - } + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 186e0d21aa..eb0cd6cf3d 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -56,11 +56,6 @@ fn valid_transfer_amount( unshielded_transfer_value, reporeted_transparent_value ); - log_string(format!( - "The unshielded amount {} disagrees with the calculated masp \ - transparented value {}", - unshielded_transfer_value, reporeted_transparent_value - )) } res } @@ -117,7 +112,6 @@ fn validate_tx( transparent_tx_pool += shielded_tx.value_balance.clone(); if transfer.source != masp() { - log_string("transparent input"); // Handle transparent input // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp @@ -131,7 +125,6 @@ fn validate_tx( denom, ); - log_string(format!("transparent amount: {:?}", transp_amt)); // Non-masp sources add to transparent tx pool transparent_tx_pool += transp_amt; } @@ -153,7 +146,6 @@ fn validate_tx( } if transfer.target != masp() { - log_string("transparent output"); // Handle transparent output // The following boundary conditions must be satisfied // 1. One to 4 transparent outputs @@ -171,11 +163,6 @@ fn validate_tx( shielded_tx.vout.len() ); - log_string(format!( - "Transparent output to a transaction to the masp must be \ - beteween 1 and 4 but is {}", - shielded_tx.vout.len() - )); return reject(); } @@ -209,7 +196,6 @@ fn validate_tx( out.value, denom.denominate(&transfer.amount.amount), ) { - log_string("Invalid transfer amount"); return reject(); } @@ -241,10 +227,6 @@ fn validate_tx( "the public key of the output account does \ not match the transfer target" ); - log_string(format!( - "the public key of the output account does \ - not match the transfer target" - )); return reject(); } } @@ -254,10 +236,6 @@ fn validate_tx( // one or more of the denoms in the batch failed to verify // the asset derivation. if valid_count != out_length { - log_string( - "one or more of the denoms in the batch failed to verify \ - the asset derivation.", - ); return reject(); } } else { @@ -267,7 +245,6 @@ fn validate_tx( // Satisfies 1. if !shielded_tx.vout.is_empty() { - log_string(format!("transparent vout {:?}", shielded_tx.vout)); debug_log!( "Transparent output to a transaction from the masp must \ be 0 but is {}", @@ -286,11 +263,6 @@ fn validate_tx( ); // Section 3.4: The remaining value in the transparent // transaction value pool MUST be nonnegative. - log_string(format!( - "would give the masp a negative balance; transparent tx \ - {:?}", - transparent_tx_pool - )); return reject(); } _ => {} @@ -298,6 +270,5 @@ fn validate_tx( } // Do the expensive proof verification in the VM at the end. - log_string("reached proof verification"); ctx.verify_masp(data) } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 429008f660..7abc85df0b 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -9,16 +9,12 @@ //! Any other storage key changes are allowed only with a valid signature. use namada_vp_prelude::address::masp; -use namada_vp_prelude::storage::{Key, KeySeg}; +use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token { - token: &'a Address, - sub_prefix: Option, - owner: &'a Address, - }, + Token { owner: &'a Address }, PoS, Vp(&'a Address), Masp, @@ -28,20 +24,12 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([token, owner]) = token::is_any_token_balance_key(key) { - Self::Token { - token, - owner, - sub_prefix: None, - } - } else if let Some((sub, [token, owner])) = + if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token { - token, - owner, - sub_prefix: Some(sub), - } + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index f3cad12e93..92fe809c68 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -12,16 +12,12 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::{Key, KeySeg}; +use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token { - token: &'a Address, - sub_prefix: Option, - owner: &'a Address, - }, + Token { owner: &'a Address }, PoS, Vp(&'a Address), GovernanceVote(&'a Address), @@ -30,20 +26,12 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([token, owner]) = token::is_any_token_balance_key(key) { - Self::Token { - token, - owner, - sub_prefix: None, - } - } else if let Some((sub, [token, owner])) = + if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token { - token, - owner, - sub_prefix: Some(sub), - } + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 04f4c6989b..333360873e 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2425,7 +2425,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "aes", "bip0039", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=ea4fcc68d9412b94042c1c2f0ea0212394e1be37#ea4fcc68d9412b94042c1c2f0ea0212394e1be37" dependencies = [ "bellman", "blake2b_simd 1.0.0",