diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 33e3ac233..8cfe72945 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -447,10 +447,28 @@ impl FactoryTrait for Factory { vec![&env, sender.into_val(&env)], ); - let sum_of_lp_share_staked: i128 = - lp_share_staked.stakes.iter().map(|stake| stake.stake).sum(); + let sum_of_lp_share_staked: i128 = lp_share_staked + .stakes + .iter() + .map(|stake| stake.stake) + .try_fold(0i128, |acc, stake| acc.checked_add(stake)) + .unwrap_or_else(|| { + log!( + &env, + "Factory: Query User Portfolio: Cannot addition more stakes" + ); + panic_with_error!(env, ContractError::OverflowingOps); + }); - let total_lp_share_for_user = lp_share_balance + sum_of_lp_share_staked; + let total_lp_share_for_user = lp_share_balance + .checked_add(sum_of_lp_share_staked) + .unwrap_or_else(|| { + log!( + &env, + "Factory: Query User Portfolio: Cannot add lp_share_balance with sum_of_lp_share_staked" + ); + panic_with_error!(env, ContractError::OverflowingOps); + }); // query the balance of the liquidity tokens let (asset_a, asset_b) = env.invoke_contract::<(Asset, Asset)>( diff --git a/contracts/factory/src/error.rs b/contracts/factory/src/error.rs index ced4476ac..1046b1298 100644 --- a/contracts/factory/src/error.rs +++ b/contracts/factory/src/error.rs @@ -12,4 +12,5 @@ pub enum ContractError { MinStakeInvalid = 6, MinRewardInvalid = 7, AdminNotSet = 8, + OverflowingOps = 9, } diff --git a/contracts/pool/src/contract.rs b/contracts/pool/src/contract.rs index 1f54580c2..3911d07f6 100644 --- a/contracts/pool/src/contract.rs +++ b/contracts/pool/src/contract.rs @@ -360,8 +360,25 @@ impl LiquidityPoolTrait for LiquidityPool { let final_balance_b = token_b_client.balance(&env.current_contract_address()); // Calculate the actual received amounts - let actual_received_a = final_balance_a - initial_balance_a; - let actual_received_b = final_balance_b - initial_balance_b; + let actual_received_a = final_balance_a + .checked_sub(initial_balance_a) + .unwrap_or_else(|| { + log!( + &env, + "Pool: Provide Liquidity: subtraction ended up in underflow/overflow for balance_a" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); + + let actual_received_b = final_balance_b + .checked_sub(initial_balance_b) + .unwrap_or_else(|| { + log!( + &env, + "Pool: Provide Liquidity: subtraction ended up in underflow/overflow for balance_b" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); let pool_balance_a = utils::get_pool_balance_a(&env); let pool_balance_b = utils::get_pool_balance_b(&env); @@ -373,12 +390,42 @@ impl LiquidityPoolTrait for LiquidityPool { let new_total_shares = if pool_balance_a > 0 && pool_balance_b > 0 { // use 10_000 multiplier to acheieve a bit bigger precision - let shares_a = (balance_a * total_shares) / pool_balance_a; - let shares_b = (balance_b * total_shares) / pool_balance_b; + + let shares_a = balance_a + .checked_mul(total_shares) + .and_then(|result| result.checked_div(pool_balance_a)) + .unwrap_or_else(|| { + log!( + env, + "Pool: Provide Liquidity: overflow/underflow for shares_a" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); + let shares_b = balance_b + .checked_mul(total_shares) + .and_then(|result| result.checked_div(pool_balance_b)) + .unwrap_or_else(|| { + log!( + env, + "Pool: Provide Liquidity: overflow/underflow for shares_b" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); shares_a.min(shares_b) } else { // In case of empty pool, just produce X*Y shares - let shares = (amounts.0 * amounts.1).sqrt(); + let shares = amounts + .0 + .checked_mul(amounts.1) + .map(|product| product.sqrt()) + .unwrap_or_else(|| { + log!( + env, + "Pool: Provide Liquidity: multiplication overflow or invalid square root for shares" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); + if MINIMUM_LIQUIDITY_AMOUNT >= shares { log!(env, "Pool: Provide Liquidity: Not enough liquidity!"); panic_with_error!(env, ContractError::TotalSharesEqualZero); @@ -390,15 +437,28 @@ impl LiquidityPoolTrait for LiquidityPool { &env.current_contract_address(), MINIMUM_LIQUIDITY_AMOUNT, ); - shares - MINIMUM_LIQUIDITY_AMOUNT + shares + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) + .unwrap_or_else(|| { + log!( + &env, + "Pool: Provide Liquidity: subtraction got an underflow for shares" + ); + panic_with_error!(env, ContractError::ContractMathError); + }) }; - utils::mint_shares( - &env, - &config.share_token, - &sender, - new_total_shares - total_shares, - ); + let shares_amount = new_total_shares + .checked_sub(total_shares) + .unwrap_or_else(|| { + log!( + &env, + "Pool: Provide Liquidity: subtraction got an underflow for shares_amount" + ); + panic_with_error!(env, ContractError::ContractMathError); + }); + + utils::mint_shares(&env, &config.share_token, &sender, shares_amount); utils::save_pool_balance_a(&env, balance_a); utils::save_pool_balance_b(&env, balance_b); @@ -497,6 +557,7 @@ impl LiquidityPoolTrait for LiquidityPool { let share_ratio = Decimal::from_ratio(share_amount, total_shares); + //safe math done in Decimal let return_amount_a = pool_balance_a * share_ratio; let return_amount_b = pool_balance_b * share_ratio; @@ -699,9 +760,14 @@ impl LiquidityPoolTrait for LiquidityPool { 0i64, ); - let total_return = compute_swap.return_amount - + compute_swap.commission_amount - + compute_swap.spread_amount; + let total_return = compute_swap + .return_amount + .checked_add(compute_swap.commission_amount) + .and_then(|partial_sum| partial_sum.checked_add(compute_swap.spread_amount)) + .unwrap_or_else(|| { + log!(&env, "Pool: Simulate Swap: addition overflowed"); + panic_with_error!(env, ContractError::ContractMathError); + }); SimulateSwapResponse { ask_amount: compute_swap.return_amount, @@ -733,6 +799,7 @@ impl LiquidityPoolTrait for LiquidityPool { }; let (offer_amount, spread_amount, commission_amount) = compute_offer_amount( + &env, pool_balance_offer, pool_balance_ask, ask_amount, @@ -760,6 +827,7 @@ impl LiquidityPoolTrait for LiquidityPool { share_ratio = Decimal::from_ratio(amount, total_share); } + //safe math done in Decimal multiplication let amount_a = token_a_amount * share_ratio; let amount_b = token_b_amount * share_ratio; ( @@ -875,9 +943,14 @@ fn do_swap( } } - let total_return_amount = compute_swap.return_amount - + compute_swap.commission_amount - + compute_swap.referral_fee_amount; + let total_return_amount = compute_swap + .return_amount + .checked_add(compute_swap.commission_amount) + .and_then(|partial_sum| partial_sum.checked_add(compute_swap.referral_fee_amount)) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: addition overflowed"); + panic_with_error!(env, ContractError::ContractMathError); + }); assert_max_spread( &env, @@ -905,7 +978,12 @@ fn do_swap( let balance_after_transfer = sell_token_client.balance(&env.current_contract_address()); // calculate how much did the contract actually got - let actual_received_amount = balance_after_transfer - balance_before_transfer; + let actual_received_amount = balance_after_transfer + .checked_sub(balance_before_transfer) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: Subtraction underflowed."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let buy_token_client = token_contract::Client::new(&env, &buy_token); @@ -939,21 +1017,41 @@ fn do_swap( // user is offering to sell A, so they will receive B // A balance is bigger, B balance is smaller let (balance_a, balance_b) = if offer_asset == config.token_a { - ( - pool_balance_a + actual_received_amount, - pool_balance_b - - compute_swap.commission_amount - - compute_swap.referral_fee_amount - - compute_swap.return_amount, - ) + let balance_a = pool_balance_a + .checked_add(actual_received_amount) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: addition overflowed"); + panic_with_error!(&env, ContractError::ContractMathError) + }); + + let balance_b = pool_balance_b + .checked_sub(compute_swap.commission_amount) + .and_then(|partial| partial.checked_sub(compute_swap.referral_fee_amount)) + .and_then(|partial| partial.checked_sub(compute_swap.return_amount)) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: subtraction underflowed"); + panic_with_error!(&env, ContractError::ContractMathError) + }); + + (balance_a, balance_b) } else { - ( - pool_balance_a - - compute_swap.commission_amount - - compute_swap.referral_fee_amount - - compute_swap.return_amount, - pool_balance_b + actual_received_amount, - ) + let balance_a = pool_balance_a + .checked_sub(compute_swap.commission_amount) + .and_then(|partial| partial.checked_sub(compute_swap.referral_fee_amount)) + .and_then(|partial| partial.checked_sub(compute_swap.return_amount)) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: subtraction underflowed"); + panic_with_error!(&env, ContractError::ContractMathError) + }); + + let balance_b = pool_balance_b + .checked_add(actual_received_amount) + .unwrap_or_else(|| { + log!(&env, "Pool: Do Swap: addition overflowed"); + panic_with_error!(&env, ContractError::ContractMathError) + }); + + (balance_a, balance_b) }; utils::save_pool_balance_a(&env, balance_a); utils::save_pool_balance_b(&env, balance_b); @@ -1026,7 +1124,17 @@ fn split_deposit_based_on_pool_ratio( let mut final_ask_amount = 0; // amount of other tokens to be received while high - low > tolerance { - let mid = (low + high) / 2; // Calculate middle point + // Calculate middle point + let mid = low + .checked_add(high) + .and_then(|sum| sum.checked_div(2)) + .unwrap_or_else(|| { + log!( + &env, + "Pool: Split Deposit Based On Pool Ratio: overflow/underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError) + }); // Simulate swap to get amount of other tokens to be received for `mid` amount of deposit tokens let SimulateSwapResponse { @@ -1041,10 +1149,17 @@ fn split_deposit_based_on_pool_ratio( final_ask_amount = ask_amount; // Calculate the ratio that would result from swapping `mid` deposit tokens + let diff = deposit.checked_sub(mid).unwrap_or_else(|| { + log!( + &env, + "Pool: Split Deposit Baed On Pool Ratio: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); let ratio = if offer_asset == &config.token_a { - Decimal::from_ratio(ask_amount, deposit - mid) + Decimal::from_ratio(ask_amount, diff) } else { - Decimal::from_ratio(deposit - mid, ask_amount) + Decimal::from_ratio(diff, ask_amount) }; // If the resulting ratio is approximately equal (1%) to the target ratio, break the loop @@ -1102,11 +1217,13 @@ fn assert_slippage_tolerance( } // Calculate the limit below which the deposit-to-pool ratio must not fall for each token + //safe math division done in Decimal let one_minus_slippage_tolerance = Decimal::one() - slippage_tolerance; let deposits: [i128; 2] = [deposits[0], deposits[1]]; let pools: [i128; 2] = [pools[0], pools[1]]; // Ensure each price does not change more than what the slippage tolerance allows + //safe math done in Decimal if deposits[0] * pools[1] * one_minus_slippage_tolerance > deposits[1] * pools[0] * Decimal::one() || deposits[1] * pools[0] * one_minus_slippage_tolerance @@ -1211,13 +1328,20 @@ pub fn compute_swap( /// * **ask_amount** amount of ask assets to swap to. /// * **commission_rate** total amount of fees charged for the swap. pub fn compute_offer_amount( + env: &Env, offer_pool: i128, ask_pool: i128, ask_amount: i128, commission_rate: Decimal, ) -> (i128, i128, i128) { // Calculate the cross product of offer_pool and ask_pool - let cp: i128 = offer_pool * ask_pool; + let cp: i128 = offer_pool.checked_mul(ask_pool).unwrap_or_else(|| { + log!( + env, + "Pool: Compute Offer Amount: checked multiplication overflowed." + ); + panic_with_error!(env, ContractError::ContractMathError); + }); // Calculate one minus the commission rate let one_minus_commission = Decimal::one() - commission_rate; @@ -1231,7 +1355,17 @@ pub fn compute_offer_amount( let ask_before_commission = ask_amount * inv_one_minus_commission; // Calculate the spread amount, representing the difference between the expected and actual swap amounts - let spread_amount: i128 = (offer_amount * ask_pool / offer_pool) - ask_before_commission; + let spread_amount: i128 = offer_amount + .checked_mul(ask_pool) + .and_then(|product| product.checked_div(offer_pool)) + .and_then(|result| result.checked_sub(ask_before_commission)) + .unwrap_or_else(|| { + log!( + env, + "Pool: Compute Offer Amount: overflow/underflow occured." + ); + panic_with_error!(env, ContractError::ContractMathError) + }); // Calculate the commission amount let commission_amount: i128 = ask_before_commission * commission_rate; @@ -1386,12 +1520,13 @@ mod tests { #[test] fn test_compute_offer_amount() { + let env = Env::default(); let offer_pool = 1000000; let ask_pool = 1000000; let commission_rate = Decimal::percent(10); let ask_amount = 1000; - let result = compute_offer_amount(offer_pool, ask_pool, ask_amount, commission_rate); + let result = compute_offer_amount(&env, offer_pool, ask_pool, ask_amount, commission_rate); // Test that the offer amount is less than the original pool size, due to commission assert!(result.0 < offer_pool); diff --git a/contracts/pool/src/error.rs b/contracts/pool/src/error.rs index e16cb4761..482f0aba4 100644 --- a/contracts/pool/src/error.rs +++ b/contracts/pool/src/error.rs @@ -36,4 +36,5 @@ pub enum ContractError { NotEnoughSharesToBeMinted = 26, NotEnoughLiquidityProvided = 27, AdminNotSet = 28, + ContractMathError = 29, } diff --git a/contracts/pool/src/storage.rs b/contracts/pool/src/storage.rs index a8062dc4b..4329a1bc2 100644 --- a/contracts/pool/src/storage.rs +++ b/contracts/pool/src/storage.rs @@ -389,7 +389,13 @@ pub mod utils { } let amount_a = { - let mut amount_a = desired_b * pool_balance_a / pool_balance_b; + let mut amount_a = desired_b + .checked_mul(pool_balance_a) + .and_then(|result| result.checked_div(pool_balance_b)) + .unwrap_or_else(|| { + log!(&env, "Pool: Get Deposit Amounts: overflow/underflow error"); + panic_with_error!(env, ContractError::ContractMathError); + }); if amount_a > desired_a { // If the amount is within the desired amount of slippage, we accept it if Decimal::from_ratio(amount_a, desired_a) - Decimal::one() <= allowed_slippage { @@ -422,7 +428,13 @@ pub mod utils { }; let amount_b = { - let mut amount_b = desired_a * pool_balance_b / pool_balance_a; + let mut amount_b = desired_a + .checked_mul(pool_balance_b) + .and_then(|result| result.checked_div(pool_balance_a)) + .unwrap_or_else(|| { + log!(&env, "Pool: Get Deposit Amounts: overflow/underflow error"); + panic_with_error!(env, ContractError::ContractMathError); + }); if amount_b > desired_b { // If the amount is within the set threshold of the desired amount, we accept it if Decimal::from_ratio(amount_b, desired_b) - Decimal::one() <= allowed_slippage { diff --git a/contracts/pool_stable/src/contract.rs b/contracts/pool_stable/src/contract.rs index 81ac18e5f..b3b2805b3 100644 --- a/contracts/pool_stable/src/contract.rs +++ b/contracts/pool_stable/src/contract.rs @@ -251,12 +251,17 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { log!(&env, "Pool Stable: Initialize: AMP parameter is incorrect"); panic_with_error!(&env, ContractError::InvalidAMP); } + + let amp_precision = amp.checked_mul(AMP_PRECISION).unwrap_or_else(|| { + log!(&env, "Stable Pool: Initialize: Multiplication overflowed."); + panic_with_error!(&env, ContractError::ContractMathError); + }); save_amp( &env, AmplifierParameters { - init_amp: amp * AMP_PRECISION, + init_amp: amp_precision, init_amp_time: current_time, - next_amp: amp * AMP_PRECISION, + next_amp: amp_precision, next_amp_time: current_time, }, ); @@ -334,12 +339,47 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { let balance_b_after = token_b_client.balance(&env.current_contract_address()); // calculate actual amounts received - let actual_received_a = balance_a_after - balance_a_before; - let actual_received_b = balance_b_after - balance_b_before; + let actual_received_a = balance_a_after + .checked_sub(balance_a_before) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Provide Liquidity: underflow when calculating actual_received_a." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let actual_received_b = balance_b_after + .checked_sub(balance_b_before) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Provide Liquidity: underflow when calculating actual_received_b." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); // Invariant (D) after deposit added - let new_balance_a = convert_i128_to_u128(actual_received_a + old_balance_a); - let new_balance_b = convert_i128_to_u128(actual_received_b + old_balance_b); + let new_balance_a = actual_received_a + .checked_add(old_balance_a) + .map(convert_i128_to_u128) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Provide Liquidity: overflow when calculating new_balance_a." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); + + let new_balance_b = actual_received_b + .checked_add(old_balance_b) + .map(convert_i128_to_u128) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Provide Liquidity: overflow when calculating new_balance_b." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); let new_invariant = compute_d( &env, @@ -353,11 +393,18 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { let total_shares = utils::get_total_shares(&env); let shares = if total_shares == 0 { let divisor = 10u128.pow(DECIMAL_PRECISION - greatest_precision); - let share = (new_invariant + let share = new_invariant .to_u128() .expect("Pool stable: provide_liquidity: conversion to u128 failed") - / divisor) - - MINIMUM_LIQUIDITY_AMOUNT; + .checked_div(divisor) + .and_then(|quotient| quotient.checked_sub(MINIMUM_LIQUIDITY_AMOUNT)) + .unwrap_or_else(|| { + log!( + &env, + "Pool stable: provide_liquidity: overflow or underflow occurred while calculating share." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); if share == 0 { log!( &env, @@ -390,12 +437,19 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { .expect("Pool stable: provide_liquidity: conversion to u128 failed"); // Calculate the proportion of the change in invariant - let invariant_delta = convert_u128_to_i128( - new_invariant - .to_u128() - .expect("Pool stable: provide_liquidity: conversion to u128 failed") - - initial_invariant, - ); + let new_inv = new_invariant + .to_u128() + .expect("Pool stable: provide_liquidity: conversion to u128 failed"); + + let diff = new_inv.checked_sub(initial_invariant).unwrap_or_else(|| { + log!( + &env, + "Pool stable: provide_liquidity: overflow or underflow occurred while calculating invariant_delta." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); + + let invariant_delta = convert_u128_to_i128(diff); let initial_invariant = convert_u128_to_i128(initial_invariant); convert_i128_to_u128( @@ -724,7 +778,13 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { config.protocol_fee_rate(), ); - let total_return = ask_amount + commission_amount + spread_amount; + let total_return = ask_amount + .checked_add(commission_amount) + .and_then(|sum| sum.checked_add(spread_amount)) + .unwrap_or_else(|| { + log!(&env, "overflow occurred while calculating total_return."); + panic_with_error!(&env, ContractError::ContractMathError); + }); SimulateSwapResponse { ask_amount, @@ -918,12 +978,14 @@ fn do_swap( } } - assert_max_spread( - &env, - max_spread, - return_amount + commission_amount, - spread_amount, - ); + let return_amount_result = return_amount + .checked_add(commission_amount) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Do Swap: overflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); + + assert_max_spread(&env, max_spread, return_amount_result, spread_amount); // we check the balance of the transferred token for the contract prior to the transfer let balance_before_transfer = @@ -941,8 +1003,12 @@ fn do_swap( token_contract::Client::new(&env, &sell_token).balance(&env.current_contract_address()); // calculate how much did the contract actually got - let actual_received_amount = balance_after_transfer - balance_before_transfer; - + let actual_received_amount = balance_after_transfer + .checked_sub(balance_before_transfer) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Do Swap: underflow occurred."); + panic_with_error!(&env, ContractError::ContractMathError); + }); // return swapped tokens to user token_contract::Client::new(&env, &buy_token).transfer( &env.current_contract_address(), @@ -961,13 +1027,41 @@ fn do_swap( // A balance is bigger, B balance is smaller let (balance_a, balance_b) = if offer_asset == config.token_a { ( - pool_balance_a + actual_received_amount, - pool_balance_b - commission_amount - return_amount, + pool_balance_a + .checked_add(actual_received_amount) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: overflow when adding to pool_balance_a."); + panic_with_error!(&env, ContractError::ContractMathError); + }), + pool_balance_b + .checked_sub(commission_amount) + .and_then(|res| res.checked_sub(return_amount)) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: underflow when subtracting from pool_balance_b." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }), ) } else { ( - pool_balance_a - commission_amount - return_amount, - pool_balance_b + actual_received_amount, + pool_balance_a + .checked_sub(commission_amount) + .and_then(|res| res.checked_sub(return_amount)) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Underflow in subtracting from pool_balance_a." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }), + pool_balance_b + .checked_add(actual_received_amount) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: overflow in adding to pool_balance_b."); + panic_with_error!(&env, ContractError::ContractMathError); + }), ) }; utils::save_pool_balance_a(&env, balance_a); @@ -1027,12 +1121,17 @@ pub fn compute_swap( let greatest_precision = get_greatest_precision(env); + let atomics = offer_pool.checked_add(offer_amount).unwrap_or_else(|| { + log!(&env, "Stable Pool: overflow occured for atomics."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let new_ask_pool = calc_y( env, amp as u128, scale_value( + //TODO: safe math env, - offer_pool + offer_amount, + atomics, greatest_precision, DECIMAL_PRECISION, ), @@ -1043,18 +1142,21 @@ pub fn compute_swap( greatest_precision, ); - let return_amount = ask_pool - new_ask_pool; + let return_amount = ask_pool.checked_sub(new_ask_pool).unwrap_or_else(|| { + log!(&env, "Pool Stable: Compute Swap: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); // We consider swap rate 1:1 in stable swap thus any difference is considered as spread. - let spread_amount = if offer_amount > return_amount { - convert_u128_to_i128(offer_amount - return_amount) - } else { - // saturating sub equivalent - 0 - }; + let spread_amount = convert_u128_to_i128(offer_amount.saturating_sub(return_amount)); let return_amount = convert_u128_to_i128(return_amount); let commission_amount = return_amount * commission_rate; // Because of issue #211 - let return_amount = return_amount - commission_amount; + let return_amount = return_amount + .checked_sub(commission_amount) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Compute Swap: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); (return_amount, spread_amount, commission_amount) } @@ -1083,12 +1185,22 @@ pub fn compute_offer_amount( let greatest_precision = get_greatest_precision(env); + let atomics = ask_pool + .checked_sub(convert_i128_to_u128(before_commission)) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Compute Offer Amount: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); let new_offer_pool = calc_y( env, amp as u128, scale_value( + //TODO: safe math env, - ask_pool - convert_i128_to_u128(before_commission), + atomics, greatest_precision, DECIMAL_PRECISION, ), @@ -1099,16 +1211,18 @@ pub fn compute_offer_amount( greatest_precision, ); - let offer_amount = new_offer_pool - offer_pool; + let offer_amount = new_offer_pool.checked_sub(offer_pool).unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Compute Offer Amount: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); + //safe math done in decimal let ask_before_commission = convert_u128_to_i128(ask_amount) * inv_one_minus_commission; // We consider swap rate 1:1 in stable swap thus any difference is considered as spread. - let spread_amount = if offer_amount > ask_amount { - offer_amount - ask_amount - } else { - // saturating sub equivalent - 0 - }; + let spread_amount = offer_amount.saturating_sub(ask_amount); // Calculate the commission amount let commission_amount: i128 = ask_before_commission * commission_rate; diff --git a/contracts/pool_stable/src/error.rs b/contracts/pool_stable/src/error.rs index a8f8bfd4d..f509bf1fa 100644 --- a/contracts/pool_stable/src/error.rs +++ b/contracts/pool_stable/src/error.rs @@ -28,4 +28,5 @@ pub enum ContractError { SwapFeeBpsOverLimit = 22, UserDeclinesPoolFee = 23, AdminNotSet = 24, + ContractMathError = 25, } diff --git a/contracts/pool_stable/src/math.rs b/contracts/pool_stable/src/math.rs index 992eccace..bc404d145 100644 --- a/contracts/pool_stable/src/math.rs +++ b/contracts/pool_stable/src/math.rs @@ -1,4 +1,5 @@ use soroban_sdk::{log, panic_with_error, Env, U256}; +//TODO: safe math the whole thing use crate::{error::ContractError, storage::AmplifierParameters, DECIMAL_PRECISION}; @@ -30,11 +31,21 @@ pub fn scale_value( let atomics = U256::from_u128(env, atomics); let scaled_value = if decimal_places < target_decimal_places { - let power = target_decimal_places - decimal_places; + let power = target_decimal_places + .checked_sub(decimal_places) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Scale Value: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let factor = ten.pow(power); atomics.mul(&factor) } else { - let power = decimal_places - target_decimal_places; + let power = decimal_places + .checked_sub(target_decimal_places) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Scale Value: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let factor = ten.pow(power); atomics.div(&factor) }; @@ -64,12 +75,38 @@ pub(crate) fn compute_current_amp(env: &Env, amp_params: &AmplifierParameters) - let next_amp = amp_params.next_amp as u128; if next_amp > init_amp { - let amp_range = next_amp - init_amp; - let res = init_amp + (amp_range * elapsed_time) / time_range as u128; + let amp_range = next_amp.checked_sub(init_amp).unwrap_or_else(|| { + log!(&env, "Pool Stable: Compute Current Amp: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let res = amp_range + .checked_mul(elapsed_time) + .and_then(|product| product.checked_div(time_range as u128)) + .and_then(|quotient| init_amp.checked_add(quotient)) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Compute Current Amp: overflow / division." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); res as u64 } else { - let amp_range = init_amp - next_amp; - let res = init_amp - (amp_range * elapsed_time) / time_range as u128; + let amp_range = init_amp.checked_sub(next_amp).unwrap_or_else(|| { + log!(&env, "Pool Stable: Compute Current Amp: underflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let res = amp_range + .checked_mul(elapsed_time) + .and_then(|product| product.checked_div(time_range.into())) // or time_range as u128 + .and_then(|quotient| init_amp.checked_sub(quotient)) + .unwrap_or_else(|| { + log!( + &env, + "Pool Stable: Compute Current Amp: underflow or overflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); res as u64 } } else { diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index de24bf62c..6076cfa0b 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -160,7 +160,10 @@ impl StakingTrait for Staking { let mut stakes = get_stakes(&env, &sender); - stakes.total_stake += tokens; + stakes.total_stake = stakes.total_stake.checked_add(tokens).unwrap_or_else(|| { + log!(&env, "Stake: Bond: overflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let stake = Stake { stake: tokens, stake_timestamp: ledger.timestamp(), @@ -187,7 +190,13 @@ impl StakingTrait for Staking { let mut stakes = get_stakes(&env, &sender); remove_stake(&env, &mut stakes.stakes, stake_amount, stake_timestamp); - stakes.total_stake -= stake_amount; + stakes.total_stake = stakes + .total_stake + .checked_sub(stake_amount) + .unwrap_or_else(|| { + log!(&env, "Stake: Unbond: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); let lp_token_client = token_contract::Client::new(&env, &config.lp_token); lp_token_client.transfer(&env.current_contract_address(), &sender, &stake_amount); diff --git a/contracts/stake/src/distribution.rs b/contracts/stake/src/distribution.rs index 5937172e5..33e8f5ac4 100644 --- a/contracts/stake/src/distribution.rs +++ b/contracts/stake/src/distribution.rs @@ -1,7 +1,7 @@ use soroban_decimal::Decimal; -use soroban_sdk::{contracttype, Address, Env, Map}; +use soroban_sdk::{contracttype, log, panic_with_error, Address, Env, Map}; -use crate::storage::BondingInfo; +use crate::{error::ContractError, storage::BondingInfo}; use phoenix::ttl::{PERSISTENT_BUMP_AMOUNT, PERSISTENT_LIFETIME_THRESHOLD}; const SECONDS_PER_DAY: u64 = 24 * 60 * 60; @@ -99,7 +99,13 @@ pub fn calculate_pending_rewards( // Calculate multiplier based on the age of each stake for stake in user_info.stakes.iter() { // Calculate the user's share of the total staked amount at the time - let user_share = stake.stake as u128 * daily_reward / total_staked; + let user_share = (stake.stake as u128) + .checked_mul(daily_reward) + .and_then(|product| product.checked_div(total_staked)) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: Math error in user share calculation"); + panic_with_error!(&env, ContractError::ContractMathError); + }); let stake_age_days = (staking_reward_day .saturating_sub(stake.stake_timestamp)) / SECONDS_PER_DAY; @@ -114,7 +120,12 @@ pub fn calculate_pending_rewards( // Apply the multiplier and accumulate the rewards let adjusted_reward = user_share as i128 * multiplier; - pending_rewards += adjusted_reward; + pending_rewards = pending_rewards + .checked_add(adjusted_reward) + .unwrap_or_else(|| { + log!(&env, "Pool Stable: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); } } } diff --git a/contracts/stake/src/error.rs b/contracts/stake/src/error.rs index f56702914..485661e55 100644 --- a/contracts/stake/src/error.rs +++ b/contracts/stake/src/error.rs @@ -18,4 +18,5 @@ pub enum ContractError { InvalidMaxComplexity = 12, DistributionNotFound = 13, AdminNotSet = 14, + ContractMathError = 15, } diff --git a/contracts/stake_rewards/src/contract.rs b/contracts/stake_rewards/src/contract.rs index a320372cb..67ada0167 100644 --- a/contracts/stake_rewards/src/contract.rs +++ b/contracts/stake_rewards/src/contract.rs @@ -170,12 +170,14 @@ impl StakingRewardsTrait for StakingRewards { let last_stake = stakes.stakes.last().unwrap(); let old_power = calc_power(&config, stakes.total_stake, Decimal::one(), TOKEN_PER_POWER); // while bonding we use Decimal::one() - let new_power = calc_power( - &config, - stakes.total_stake + last_stake.stake, - Decimal::one(), - TOKEN_PER_POWER, - ); + let stakes_sum = stakes + .total_stake + .checked_add(last_stake.stake) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: calculate bond: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let new_power = calc_power(&config, stakes_sum, Decimal::one(), TOKEN_PER_POWER); update_rewards( &env, &sender, @@ -209,12 +211,14 @@ impl StakingRewardsTrait for StakingRewards { let mut distribution = get_distribution(&env, &config.reward_token); let old_power = calc_power(&config, stakes.total_stake, Decimal::one(), TOKEN_PER_POWER); // while bonding we use Decimal::one() - let new_power = calc_power( - &config, - stakes.total_stake - removed_stake, - Decimal::one(), - TOKEN_PER_POWER, - ); + let stakes_diff = stakes + .total_stake + .checked_sub(removed_stake) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: Calculate bond: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let new_power = calc_power(&config, stakes_diff, Decimal::one(), TOKEN_PER_POWER); update_rewards( &env, &sender, @@ -261,24 +265,62 @@ impl StakingRewardsTrait for StakingRewards { // Calculate how much we have received since the last time Distributed was called, // including only the reward config amount that is eligible for distribution. // This is the amount we will distribute to all mem - let amount = undistributed_rewards - withdrawable - curve.value(env.ledger().timestamp()); + let amount = undistributed_rewards + .checked_sub(withdrawable) + .and_then(|diff| diff.checked_sub(curve.value(env.ledger().timestamp()))) + .unwrap_or_else(|| { + log!( + &env, + "Stake Rewards: Distribute Rewards: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); if amount == 0 { return; } let leftover: u128 = distribution.shares_leftover.into(); - let points = (amount << SHARES_SHIFT) + leftover; - let points_per_share = points / total_rewards_power; + let points = (amount << SHARES_SHIFT) + .checked_add(leftover) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: Distribute Rewards: overflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); + let points_per_share = points.checked_div(total_rewards_power).unwrap_or_else(|| { + log!( + &env, + "Stake Rewards: Distribute Rewards: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); distribution.shares_leftover = (points % total_rewards_power) as u64; // Everything goes back to 128-bits/16-bytes // Full amount is added here to total withdrawable, as it should not be considered on its own // on future distributions - even if because of calculation offsets it is not fully // distributed, the error is handled by leftover. - distribution.shares_per_point += points_per_share; - distribution.distributed_total += amount; - distribution.withdrawable_total += amount; + distribution.shares_per_point = distribution + .shares_per_point + .checked_add(points_per_share) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); + distribution.distributed_total = distribution + .distributed_total + .checked_add(amount) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); + distribution.withdrawable_total = distribution + .withdrawable_total + .checked_add(amount) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); save_distribution(&env, &config.reward_token, &distribution); @@ -306,6 +348,7 @@ impl StakingRewardsTrait for StakingRewards { // calculate current reward amount given the distribution and subtracting withdraw // adjustments let reward_amount = withdrawable_rewards( + &env, stakes.total_stake, &distribution, &withdraw_adjustment, @@ -314,13 +357,27 @@ impl StakingRewardsTrait for StakingRewards { // calculate the actual reward amounts - each stake is worth 1/60th per each staked day let reward_multiplier = calc_withdraw_power(&env, &stakes.stakes); + //safe math implemented in decimal let reward_amount = convert_u128_to_i128(reward_amount) * reward_multiplier; if reward_amount == 0 { return; } - withdraw_adjustment.withdrawn_rewards += reward_amount as u128; - distribution.withdrawable_total -= reward_amount as u128; + withdraw_adjustment.withdrawn_rewards = withdraw_adjustment + .withdrawn_rewards + .checked_add(reward_amount as u128) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); + + distribution.withdrawable_total = distribution + .withdrawable_total + .checked_sub(reward_amount as u128) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); save_distribution(&env, &config.reward_token, &distribution); save_withdraw_adjustment(&env, &sender, &config.reward_token, &withdraw_adjustment); @@ -378,7 +435,12 @@ impl StakingRewardsTrait for StakingRewards { let reward_token_client = token_contract::Client::new(&env, &config.reward_token); reward_token_client.transfer(&admin, &env.current_contract_address(), &token_amount); - let end_time = current_time + distribution_duration; + let end_time = current_time + .checked_add(distribution_duration) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); // define a distribution curve starting at start_time with token_amount of tokens // and ending at end_time with 0 tokens let new_reward_distribution = Curve::saturating_linear( @@ -500,6 +562,7 @@ impl StakingRewardsTrait for StakingRewards { // calculate current reward amount given the distribution and subtracting withdraw // adjustments let reward_amount = withdrawable_rewards( + &env, stakes.total_stake, &distribution, &withdraw_adjustment, @@ -533,7 +596,12 @@ impl StakingRewardsTrait for StakingRewards { let distribution = get_distribution(&env, &asset); let reward_token_client = token_contract::Client::new(&env, &asset); let reward_token_balance = reward_token_client.balance(&env.current_contract_address()); - convert_i128_to_u128(reward_token_balance) - distribution.withdrawable_total + convert_i128_to_u128(reward_token_balance) + .checked_sub(distribution.withdrawable_total) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }) } fn migrate_admin_key(env: Env) -> Result<(), ContractError> { diff --git a/contracts/stake_rewards/src/distribution.rs b/contracts/stake_rewards/src/distribution.rs index 745607e32..1bd55d07e 100644 --- a/contracts/stake_rewards/src/distribution.rs +++ b/contracts/stake_rewards/src/distribution.rs @@ -1,10 +1,11 @@ use phoenix::utils::convert_i128_to_u128; -use soroban_sdk::{contracttype, Address, Env, Vec}; +use soroban_sdk::{contracttype, log, panic_with_error, Address, Env, Vec}; use curve::Curve; use soroban_decimal::Decimal; use crate::{ + error::ContractError, storage::{Config, Stake}, TOKEN_PER_POWER, }; @@ -93,7 +94,12 @@ pub fn update_rewards( if old_rewards_power == new_rewards_power { return; } - let diff = new_rewards_power - old_rewards_power; + let diff = new_rewards_power + .checked_sub(old_rewards_power) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: Update Rewards: underflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); // Apply the points correction with the calculated difference. let ppw = distribution.shares_per_point; apply_points_correction(env, user, asset, diff, ppw); @@ -112,8 +118,13 @@ fn apply_points_correction( ) { let mut withdraw_adjustment = get_withdraw_adjustment(env, user, asset); let shares_correction = withdraw_adjustment.shares_correction; - withdraw_adjustment.shares_correction = - shares_correction - convert_u128_to_i128(shares_per_point) * diff; + withdraw_adjustment.shares_correction = convert_u128_to_i128(shares_per_point) + .checked_mul(diff) + .and_then(|product| shares_correction.checked_sub(product)) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: underflow/overflow occured."); + panic_with_error!(&env, ContractError::ContractMathError); + }); save_withdraw_adjustment(env, user, asset, &withdraw_adjustment); } @@ -165,6 +176,7 @@ pub fn get_withdraw_adjustment( pub fn withdrawable_rewards( // total amount of staked tokens by given user + env: &Env, total_staked: i128, distribution: &Distribution, adjustment: &WithdrawAdjustment, @@ -175,12 +187,25 @@ pub fn withdrawable_rewards( // Decimal::one() represents the standart multiplier per token // 1_000 represents the contsant token per power. TODO: make it configurable let points = calc_power(config, total_staked, Decimal::one(), TOKEN_PER_POWER); - let points = convert_u128_to_i128(ppw) * points; + let points = convert_u128_to_i128(ppw) + .checked_mul(points) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: overflow"); + panic_with_error!(&env, ContractError::ContractMathError); + }); let correction = adjustment.shares_correction; - let points = points + correction; - let amount = points >> SHARES_SHIFT; - convert_i128_to_u128(amount) - adjustment.withdrawn_rewards + points + .checked_add(correction) + .and_then(|sum| { + let shifted = sum >> SHARES_SHIFT; + u128::try_from(shifted).ok() + }) + .and_then(|converted| converted.checked_sub(adjustment.withdrawn_rewards)) + .unwrap_or_else(|| { + log!(&env, "Stake Rewards: underflow/overflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }) } pub fn calculate_annualized_payout(reward_curve: Option, now: u64) -> Decimal { @@ -253,7 +278,17 @@ pub fn calc_withdraw_power(env: &Env, stakes: &Vec) -> Decimal { for stake in stakes.iter() { // Calculate the number of days the stake has been active - let days_active = (dbg!(current_date) - dbg!(stake.stake_timestamp)) / SECONDS_PER_DAY; + + let days_active = current_date + .checked_sub(stake.stake_timestamp) + .and_then(|diff| diff.checked_div(SECONDS_PER_DAY)) + .unwrap_or_else(|| { + log!( + &env, + "Stake Rewards: Calc Withdraw Power: underflow/overflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); // If stake is younger than 60 days, calculate its power let power = if days_active < 60 { @@ -263,9 +298,27 @@ pub fn calc_withdraw_power(env: &Env, stakes: &Vec) -> Decimal { }; // Add the weighted power to the sum - weighted_sum += power * convert_i128_to_u128(stake.stake); + weighted_sum = power + .checked_mul(convert_i128_to_u128(stake.stake)) + .and_then(|product| weighted_sum.checked_add(product)) + .unwrap_or_else(|| { + log!( + &env, + "Stake Rewards: Calc Withdraw Power: underflow/overflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); // Accumulate the total weight - total_weight += 60 * convert_i128_to_u128(stake.stake); + total_weight = 60u128 + .checked_mul(convert_i128_to_u128(stake.stake)) + .and_then(|product| total_weight.checked_add(product)) + .unwrap_or_else(|| { + log!( + &env, + "Stake Rewards: Calc Withdraw Power: underflow/overflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }) } // Calculate and return the average staking power diff --git a/contracts/stake_rewards/src/error.rs b/contracts/stake_rewards/src/error.rs index 3587d74c8..22fbfa760 100644 --- a/contracts/stake_rewards/src/error.rs +++ b/contracts/stake_rewards/src/error.rs @@ -17,4 +17,5 @@ pub enum ContractError { InvalidRewardAmount = 11, InvalidMaxComplexity = 12, AdminNotSet = 13, + ContractMathError = 14, } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 6e8082e13..a3a8ae91c 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -168,7 +168,7 @@ impl VestingTrait for Vesting { check_duplications(&env, vesting_schedules.clone()); let max_vesting_complexity = get_max_vesting_complexity(&env); - let mut total_vested_amount = 0; + let mut total_vested_amount: u128 = 0; vesting_schedules.into_iter().for_each(|vesting_schedule| { let vested_amount = validate_vesting_schedule(&env, &vesting_schedule.curve) @@ -193,7 +193,12 @@ impl VestingTrait for Vesting { }, ); - total_vested_amount += vested_amount; + total_vested_amount = total_vested_amount + .checked_add(vested_amount) + .unwrap_or_else(|| { + log!(&env, "Vesting: Create Vesting Schedule: overflow ocurred."); + panic_with_error!(&env, ContractError::ContractMathError); + }); }); // check if the admin has enough tokens to start the vesting contract @@ -246,12 +251,18 @@ impl VestingTrait for Vesting { panic_with_error!(env, ContractError::CantMoveVestingTokens); } + let updated_balance = sender_balance + .checked_sub(convert_i128_to_u128(available_to_claim)) + .unwrap_or_else(|| { + log!(&env, "Vesting: Claim: underflow occured"); + panic_with_error!(&env, ContractError::ContractMathError); + }); update_vesting( &env, &sender, index, &VestingInfo { - balance: (sender_balance - convert_i128_to_u128(available_to_claim)), + balance: updated_balance, ..vesting_info }, ); @@ -466,9 +477,17 @@ impl VestingTrait for Vesting { .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); let vesting_info = get_vesting(&env, &address, index); - convert_u128_to_i128( - vesting_info.balance - vesting_info.schedule.value(env.ledger().timestamp()), - ) + let difference = vesting_info + .balance + .checked_sub(vesting_info.schedule.value(env.ledger().timestamp())) + .unwrap_or_else(|| { + log!( + &env, + "Vesting: Query Available To Claim: underflow occured." + ); + panic_with_error!(&env, ContractError::ContractMathError); + }); + convert_u128_to_i128(difference) } fn update(env: Env, new_wasm_hash: BytesN<32>) { diff --git a/contracts/vesting/src/error.rs b/contracts/vesting/src/error.rs index cd289c5f6..baa2101c2 100644 --- a/contracts/vesting/src/error.rs +++ b/contracts/vesting/src/error.rs @@ -40,6 +40,7 @@ pub enum ContractError { CurveSLNotDecreasing = 30, AlreadyInitialized = 31, AdminNotFound = 32, + ContractMathError = 33, } impl From for ContractError { diff --git a/packages/decimal/src/decimal.rs b/packages/decimal/src/decimal.rs index ed89696bc..8981906f2 100644 --- a/packages/decimal/src/decimal.rs +++ b/packages/decimal/src/decimal.rs @@ -1,6 +1,7 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. +//TODO: safe math the whole thing use soroban_sdk::{Env, String}; use core::{ @@ -271,14 +272,22 @@ impl Add for Decimal { type Output = Self; fn add(self, other: Self) -> Self { - Decimal(self.0 + other.0) + Decimal( + self.0 + .checked_add(other.0) + .expect("attempt to add with overflow"), + ) } } impl Sub for Decimal { type Output = Self; fn sub(self, other: Self) -> Self { - Decimal(self.0 - other.0) + Decimal( + self.0 + .checked_sub(other.0) + .expect("Decimal subtraction underflowed."), + ) } } @@ -296,8 +305,14 @@ impl Mul for Decimal { // let other_numerator = other.numerator().to_bigint().unwrap(); // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL - let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; - + let product = self + .numerator() + .checked_mul(other.numerator()) + .expect("attempt to multiply with overflow"); + + let result = product + .checked_div(Self::DECIMAL_FRACTIONAL) + .expect("attempt to divide with underflow"); // Convert the result back to i128, and panic on overflow // let result = result // .to_i128()