diff --git a/packages/neutron-sdk/src/errors/error.rs b/packages/neutron-sdk/src/errors/error.rs index bb05d320..566a7a3a 100644 --- a/packages/neutron-sdk/src/errors/error.rs +++ b/packages/neutron-sdk/src/errors/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DecimalRangeExceeded, OverflowError, StdError}; +use cosmwasm_std::{Decimal256RangeExceeded, DecimalRangeExceeded, OverflowError, StdError}; use serde_json_wasm; use thiserror::Error; @@ -33,6 +33,9 @@ pub enum NeutronError { #[error("Decimal range exceeded")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), + #[error("Decimal256 range exceeded")] + Decimal256RangeExceeded(#[from] Decimal256RangeExceeded), + #[error("Overflow error")] OverflowError(#[from] OverflowError), diff --git a/packages/neutron-sdk/src/interchain_queries/v045/testing.rs b/packages/neutron-sdk/src/interchain_queries/v045/testing.rs index 10648784..f8ae1bd6 100644 --- a/packages/neutron-sdk/src/interchain_queries/v045/testing.rs +++ b/packages/neutron-sdk/src/interchain_queries/v045/testing.rs @@ -1150,3 +1150,79 @@ fn test_unbonding_delegations_reconstruct_from_hex() { } ); } + +#[test] +fn test_delegations_reconstruct_overflow() { + struct TestCase { + stake_denom: String, + delegations: Vec, + validators: Vec, + expected_result: NeutronResult, + } + let test_cases: Vec = vec![TestCase { + stake_denom: "stake".to_string(), + delegations: vec![Delegation { + delegator_address: "osmo1yz54ncxj9csp7un3xled03q6thrrhy9cztkfzs".to_string(), + validator_address: "osmovaloper1r2u5q6t6w0wssrk6l66n3t2q3dw2uqny4gj2e3".to_string(), + shares: "340282366920938463463".to_string(), + }], + validators: vec![Validator { + operator_address: "osmovaloper1r2u5q6t6w0wssrk6l66n3t2q3dw2uqny4gj2e3".to_string(), + consensus_pubkey: None, + jailed: false, + status: 0, + tokens: "340282366920938463463".to_string(), + delegator_shares: "340282366920938463463".to_string(), + description: None, + unbonding_height: 0, + unbonding_time: None, + commission: None, + min_self_delegation: "".to_string(), + }], + expected_result: Ok(Delegations { + delegations: vec![StdDelegation { + delegator: Addr::unchecked("osmo1yz54ncxj9csp7un3xled03q6thrrhy9cztkfzs"), + validator: "osmovaloper1r2u5q6t6w0wssrk6l66n3t2q3dw2uqny4gj2e3".to_string(), + amount: StdCoin::new(340282366920938463463u128, "stake"), + }], + }), + }]; + + for ts in &test_cases { + // prepare storage values + let mut st_values: Vec = vec![StorageValue { + storage_prefix: STAKING_STORE_KEY.to_string(), + key: Binary(create_params_store_key(STAKING_STORE_KEY, KEY_BOND_DENOM)), + value: { + if ts.stake_denom.is_empty() { + return Default::default(); + } + to_json_binary(&ts.stake_denom).unwrap() + }, + }]; + + for (i, d) in ts.delegations.iter().enumerate() { + let delegator_addr = decode_and_convert(&d.delegator_address).unwrap(); + let val_addr = decode_and_convert(&d.validator_address).unwrap(); + + st_values.push(StorageValue { + storage_prefix: STAKING_STORE_KEY.to_string(), + key: Binary(create_delegation_key(&delegator_addr, &val_addr).unwrap()), + value: Binary::from(d.encode_to_vec()), + }); + + if let Some(v) = ts.validators.get(i) { + st_values.push(StorageValue { + storage_prefix: STAKING_STORE_KEY.to_string(), + key: Binary(create_validator_key(&val_addr).unwrap()), + value: Binary::from(v.encode_to_vec()), + }); + } + } + + // test reconstruction + let delegations = Delegations::reconstruct(&st_values); + + assert_eq!(delegations, ts.expected_result) + } +} diff --git a/packages/neutron-sdk/src/interchain_queries/v045/types.rs b/packages/neutron-sdk/src/interchain_queries/v045/types.rs index 74113236..4a0f6ea4 100644 --- a/packages/neutron-sdk/src/interchain_queries/v045/types.rs +++ b/packages/neutron-sdk/src/interchain_queries/v045/types.rs @@ -11,7 +11,9 @@ use cosmos_sdk_proto::cosmos::{ staking::v1beta1::{Delegation, UnbondingDelegation, Validator as CosmosValidator}, }; use cosmos_sdk_proto::traits::Message; -use cosmwasm_std::{from_json, Addr, Coin, Decimal, StdError, Timestamp, Uint128}; +use cosmwasm_std::{ + from_json, Addr, Coin, Decimal, Decimal256, StdError, Timestamp, Uint128, Uint256, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::{ops::Div, str::FromStr}; @@ -443,15 +445,18 @@ impl KVReconstruct for Delegations { } let validator: CosmosValidator = CosmosValidator::decode(chunk[1].value.as_slice())?; - let delegation_shares = - Decimal::from_atomics(Uint128::from_str(&delegation_sdk.shares)?, DECIMAL_PLACES)?; + let delegation_shares = Decimal256::from_atomics( + Uint256::from_str(&delegation_sdk.shares)?, + DECIMAL_PLACES, + )?; - let delegator_shares = Decimal::from_atomics( - Uint128::from_str(&validator.delegator_shares)?, + let delegator_shares = Decimal256::from_atomics( + Uint256::from_str(&validator.delegator_shares)?, DECIMAL_PLACES, )?; - let validator_tokens = Decimal::from_atomics(Uint128::from_str(&validator.tokens)?, 0)?; + let validator_tokens = + Decimal256::from_atomics(Uint128::from_str(&validator.tokens)?, 0)?; // https://github.com/cosmos/cosmos-sdk/blob/35ae2c4c72d4aeb33447d5a7af23ca47f786606e/x/staking/keeper/querier.go#L463 // delegated_tokens = quotient(delegation.shares * validator.tokens / validator.total_shares); @@ -459,10 +464,9 @@ impl KVReconstruct for Delegations { .checked_mul(validator_tokens)? .div(delegator_shares) .atomics() - .u128() - .div(DECIMAL_FRACTIONAL); + .div(Uint256::from_u128(DECIMAL_FRACTIONAL)); - delegation_std.amount = Coin::new(delegated_tokens, &denom); + delegation_std.amount = Coin::new(uint256_to_u128(delegated_tokens)?, &denom); delegations.push(delegation_std); } @@ -471,6 +475,13 @@ impl KVReconstruct for Delegations { } } +fn uint256_to_u128(value: Uint256) -> Result { + let converted: Uint128 = value + .try_into() + .map_err(|_| StdError::generic_err("Uint256 value exceeds u128 limits"))?; + Ok(converted.u128()) +} + /// Represents a single unbonding delegation from some validator to some delegator on remote chain #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct UnbondingEntry {