diff --git a/.changelog/unreleased/improvements/436-remove-f64.md b/.changelog/unreleased/improvements/436-remove-f64.md new file mode 100644 index 0000000000..e55af7ee8f --- /dev/null +++ b/.changelog/unreleased/improvements/436-remove-f64.md @@ -0,0 +1,2 @@ +- Refactored token decimal formatting. + ([#436](https://github.com/anoma/namada/pull/436)) \ No newline at end of file diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 7ee0abdf98..1c265bf1a8 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -71,11 +71,14 @@ pub enum ValidationError { UnbondingLenTooShort(u64, u64), } +/// The number of fundamental units per whole token of the native staking token +pub const TOKENS_PER_NAM: u64 = 1_000_000; + /// From Tendermint: const MAX_TOTAL_VOTING_POWER: i64 = i64::MAX / 8; /// Assuming token amount is `u64` in micro units. -const TOKEN_MAX_AMOUNT: u64 = u64::MAX / 1_000_000; +const TOKEN_MAX_AMOUNT: u64 = u64::MAX / TOKENS_PER_NAM; impl PosParams { /// Validate PoS parameters values. Returns an empty list if the values are diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..b19642b85a 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -6,6 +6,7 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -37,7 +38,6 @@ pub struct Amount { pub const MAX_DECIMAL_PLACES: u32 = 6; /// Decimal scale of token [`Amount`] and [`Change`]. pub const SCALE: u64 = 1_000_000; -const SCALE_F64: f64 = SCALE as f64; /// A change in tokens amount pub type Change = i128; @@ -109,21 +109,16 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for f64 { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. +impl From for Decimal { fn from(amount: Amount) -> Self { - amount.micro as f64 / SCALE_F64 + Into::::into(amount.micro) / Into::::into(SCALE) } } -impl From for Amount { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. - fn from(micro: f64) -> Self { - Self { - micro: (micro * SCALE_F64).round() as u64, - } +impl From for Amount { + fn from(micro: Decimal) -> Self { + let res = (micro * Into::::into(SCALE)).to_u64().unwrap(); + Self { micro: res } } } @@ -205,7 +200,7 @@ impl FromStr for Amount { match rust_decimal::Decimal::from_str(s) { Ok(decimal) => { let scale = decimal.scale(); - if scale > 6 { + if scale > MAX_DECIMAL_PLACES { return Err(AmountParseError::ScaleTooLarge(scale)); } let whole = @@ -440,11 +435,11 @@ mod tests { /// The upper limit is set to `2^51`, because then the float is /// starting to lose precision. #[test] - fn test_token_amount_f64_conversion(raw_amount in 0..2_u64.pow(51)) { + fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { let amount = Amount::from(raw_amount); - // A round-trip conversion to and from f64 should be an identity - let float = f64::from(amount); - let identity = Amount::from(float); + // A round-trip conversion to and from Decimal should be an identity + let decimal = Decimal::from(amount); + let identity = Amount::from(decimal); assert_eq!(amount, identity); } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..61b859a7d6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -594,6 +594,10 @@ pub mod testing { use crate::tx::{self, tx_host_env}; + const TOKENS_PER_NAM: i128 = + namada::ledger::pos::namada_proof_of_stake::parameters::TOKENS_PER_NAM + as i128; + #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, @@ -940,9 +944,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = - params.votes_per_token * ((total_delta) / 1_000_000); + params.votes_per_token * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; @@ -1001,12 +1005,12 @@ pub mod testing { let total_delta = validator_total_deltas .get(epoch) .unwrap_or_default(); - // We convert the tokens from micro units to whole + // We convert the tokens from micro units to whole // tokens with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta) / 1_000_000); + * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; @@ -1080,9 +1084,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta_cur) / 1_000_000); + * (total_delta_cur / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta_cur + token_delta) / 1_000_000); + * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; diff --git a/wasm/checksums.json b/wasm/checksums.json index 9b04b0cb25..870580fc71 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,15 @@ { - "tx_bond.wasm": "tx_bond.e19c3845447cac4ca90b17fdc7d12428afca654b26f8c7d08475a87d7fa1627f.wasm", - "tx_ibc.wasm": "tx_ibc.dc153eadbb7e11bc80491f818060a9ee6d4c9ff1d73dfc8fc725c70545224c97.wasm", - "tx_init_account.wasm": "tx_init_account.d0163b28bcd3863935f080742a89758f2b6b19ca63e5f62c0730ef59ef07aecc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26e6e564222920bec2790140d9e71d6baa2ae2e932c7a974db628e9c370c0604.wasm", - "tx_init_validator.wasm": "tx_init_validator.fc728de452311fc28c89d6f2e42f42034422d12a5a57248bcc0317598da098b0.wasm", - "tx_transfer.wasm": "tx_transfer.c5272f6d5b17f4466cd66246f037646c4ef1cf1fc9f51d4855dbcf346fc6cc6f.wasm", - "tx_unbond.wasm": "tx_unbond.ff803f73eee8585a3a31ad9c6ca84579ed5e4a519214d1634efb7799a5f73d1d.wasm", - "tx_update_vp.wasm": "tx_update_vp.94ea72ce2052394b3d03ce1de168a7c0e8d2c9b63c2432cd279ad5d7eb4956b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ed220b8b090dc029e1ed30024f27c0d4527a785994eba44b7a0b17baf72cb82c.wasm", - "tx_withdraw.wasm": "tx_withdraw.72248dcad1caf3063d8a7c80ccb1080069212bc65aefc26546ec61d33d820e22.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.bdea8acbcaff11d90e6dd3e0588aba64c2836e45f151886f22fd55e8d689e658.wasm", - "vp_token.wasm": "vp_token.ce12c276d47ec44569a4dc32af6f6c9226f01587ec7ff5037edb12e3ba9af763.wasm", - "vp_user.wasm": "vp_user.58eacd2d8d8dbb7bddb210e27cc28f1cf1f386943258f35ca35862b7ce5e5b90.wasm" + "tx_bond.wasm": "tx_bond.973b43b3ec59d559ad0caf05ec29b9c859022084d96fc02b951d27ee45941379.wasm", + "tx_ibc.wasm": "tx_ibc.82e789f249e37005cd6f3e565897d42c0516413092b5e6db01b5b5a9ffdf2283.wasm", + "tx_init_account.wasm": "tx_init_account.4eb2ae9ef7b5df5d7b224b666fb1a1d6c4556dd8f88b8e7fc118e26c63fb3e95.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8625baf54979720e3ef7bc4b7c809b8d43c8e23b65b54ac640708db903dc6176.wasm", + "tx_init_validator.wasm": "tx_init_validator.9a6111478ee6d7e6c178d979ef4bee164132db309802d25382867388fcbf7ae1.wasm", + "tx_transfer.wasm": "tx_transfer.a1b6a5682606a258f4f6fd0503ceb999acbef63530c5e72a924ae5c0c8f835f1.wasm", + "tx_unbond.wasm": "tx_unbond.84c83cf42252ee2d783664122fdda93ab2a7aab9e86d27615ffeb17f7ed8e7e3.wasm", + "tx_update_vp.wasm": "tx_update_vp.5397ef094da377fbfdb1002ff63ad85ada77754b4c17f6cd74acc34c4b24b6cd.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.14dbe6186029858a325a1e4212e8d24a764afd09ea21bce067ff607aad0ec6b5.wasm", + "tx_withdraw.wasm": "tx_withdraw.4b099dc1e0969672b8dd53573c6839f9b450321d415e2675ef5bef3ab0d56f0f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c825297e6835841c11978c7f5da9b7e1795e4a8f4065836c36eb147a34f1d8a9.wasm", + "vp_token.wasm": "vp_token.1fc42a09e0908ba5d6efcd536463348821dddbf573f2fffdd70d19923e63676b.wasm", + "vp_user.wasm": "vp_user.0a9ad2b746114525ba48ddc2be2bf8892e56a17c3a1924de03ed5d07dd56d75f.wasm" } \ No newline at end of file