From 292586070c97b6d9d8f4c98e43d4f272ff7bcc4a Mon Sep 17 00:00:00 2001 From: Jan Kuczma <63134918+JanKuczma@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:13:52 +0200 Subject: [PATCH] Remove expiration for cached rates (#32) * remove expiration for cached rates * rename method * add getter for rate providers * auto deref * clippy * compute block_no inside TokenRate --- amm/contracts/stable_pool/lib.rs | 111 +++++------------- amm/contracts/stable_pool/token_rate.rs | 94 ++++----------- .../src/stable_swap_tests/tests_rated.rs | 15 +-- amm/traits/stable_pool.rs | 47 +++++--- 4 files changed, 85 insertions(+), 182 deletions(-) diff --git a/amm/contracts/stable_pool/lib.rs b/amm/contracts/stable_pool/lib.rs index 8761230..3291630 100644 --- a/amm/contracts/stable_pool/lib.rs +++ b/amm/contracts/stable_pool/lib.rs @@ -68,11 +68,6 @@ pub mod stable_pool { pub reserves: Vec, } - #[ink(event)] - pub struct RatesUpdated { - pub rates: Vec, - } - #[ink(event)] pub struct Approval { /// Account providing allowance. @@ -244,26 +239,19 @@ pub mod stable_pool { tokens: Vec, tokens_decimals: Vec, external_rates: Vec>, - rate_expiration_duration_ms: u64, init_amp_coef: u128, owner: AccountId, trade_fee: u32, protocol_fee: u32, fee_receiver: Option, ) -> Result { - let current_time = Self::env().block_timestamp(); let token_rates: Vec = external_rates .into_iter() .map(|rate| match rate { - Some(contract) => { - TokenRate::new_external(current_time, contract, rate_expiration_duration_ms) - } + Some(contract) => TokenRate::new_external(contract), None => TokenRate::new_constant(RATE_PRECISION), }) .collect(); - Self::env().emit_event(RatesUpdated { - rates: token_rates.clone(), - }); Self::new_pool( tokens, tokens_decimals, @@ -305,47 +293,18 @@ pub mod stable_pool { self.pool.tokens[token_id].into() } - /// Update cached rates if expired. - /// - /// NOTE: - /// If the pool contains a token with rate oracle, this function makes - /// a cross-contract call to the `RateProvider` contract if the cached rate is expired. - /// This means that the gas cost of the contract methods that use this function may vary, - /// depending on the state of the expiration timestamp of the cached rate. - fn update_rates(&mut self) { - let current_time = self.env().block_timestamp(); - let mut rate_changed = false; - for rate in self.pool.token_rates.iter_mut() { - rate_changed |= rate.update_rate(current_time); - } - if rate_changed { - Self::env().emit_event(RatesUpdated { - rates: self.pool.token_rates.clone(), - }); - } - } - - /// Get updated rates - fn get_updated_rates(&self) -> Vec { - let current_time = self.env().block_timestamp(); - let mut rates = self.pool.token_rates.clone(); - rates.iter_mut().for_each(|rate| { - rate.update_rate(current_time); - }); - rates - } - /// Scaled rates are rates multiplied by precision. They are assumed to fit in u128. /// If TOKEN_TARGET_DECIMALS is 18 and RATE_DECIMALS is 12, then rates not exceeding ~340282366 should fit. /// That's because if precision <= 10^18 and rate <= 10^12 * 340282366, then rate * precision < 2^128. - fn get_scaled_rates(&self, rates: &[TokenRate]) -> Result, MathError> { - rates - .iter() + fn get_scaled_rates(&mut self) -> Result, MathError> { + self.pool + .token_rates + .iter_mut() .zip(self.pool.precisions.iter()) .map(|(rate, &precision)| { rate.get_rate() .checked_mul(precision) - .ok_or(MathError::MulOverflow(114)) + .ok_or(MathError::MulOverflow(104)) }) .collect() } @@ -377,7 +336,7 @@ pub mod stable_pool { if let Some(fee_to) = self.fee_receiver() { let protocol_fee = self.pool.fees.protocol_trade_fee(fee)?; if protocol_fee > 0 { - let rates = self.get_scaled_rates(&self.pool.token_rates)?; + let rates = self.get_scaled_rates()?; let mut protocol_deposit_amounts = vec![0u128; self.pool.tokens.len()]; protocol_deposit_amounts[token_id] = protocol_fee; let mut reserves = self.pool.reserves.clone(); @@ -437,8 +396,7 @@ pub mod stable_pool { let token_in_amount = self._transfer_in(token_in_id, token_in_amount)?; // Make sure rates are up to date before we attempt any calculations - self.update_rates(); - let rates = self.get_scaled_rates(&self.pool.token_rates)?; + let rates = self.get_scaled_rates()?; // calc amount_out and fees let (token_out_amount, fee) = math::rated_swap_to( @@ -498,8 +456,7 @@ pub mod stable_pool { ); // Make sure rates are up to date before we attempt any calculations - self.update_rates(); - let rates = self.get_scaled_rates(&self.pool.token_rates)?; + let rates = self.get_scaled_rates()?; // calc amount_out and fees let (token_in_amount, fee) = math::rated_swap_from( @@ -592,8 +549,7 @@ pub mod stable_pool { ); // Make sure rates are up to date before we attempt any calculations - self.update_rates(); - let rates = self.get_scaled_rates(&self.pool.token_rates)?; + let rates = self.get_scaled_rates()?; // calc lp tokens (shares_to_mint, fee) let (shares, fee_part) = math::rated_compute_lp_amount_for_deposit( @@ -713,9 +669,7 @@ pub mod stable_pool { StablePoolError::IncorrectAmountsCount ); - // Make sure rates are up to date before we attempt any calculations - self.update_rates(); - let rates = self.get_scaled_rates(&self.pool.token_rates)?; + let rates = self.get_scaled_rates()?; // calc comparable amounts let (shares_to_burn, fee_part) = math::rated_compute_lp_amount_for_withdraw( @@ -764,20 +718,6 @@ pub mod stable_pool { Ok((shares_to_burn, fee_part)) } - #[ink(message)] - fn force_update_rates(&mut self) { - let current_time = self.env().block_timestamp(); - let mut rate_changed = false; - for rate in self.pool.token_rates.iter_mut() { - rate_changed |= rate.force_update_rate(current_time); - } - if rate_changed { - Self::env().emit_event(RatesUpdated { - rates: self.pool.token_rates.clone(), - }) - } - } - #[ink(message)] fn swap_exact_in( &mut self, @@ -888,23 +828,31 @@ pub mod stable_pool { #[ink(message)] fn token_rates(&mut self) -> Vec { - self.update_rates(); self.pool .token_rates - .iter() + .iter_mut() .map(|rate| rate.get_rate()) .collect() } + #[ink(message)] + fn token_rates_providers(&self) -> Vec> { + self.pool + .token_rates + .iter() + .map(|rate| rate.get_rate_provider()) + .collect() + } + #[ink(message)] fn get_swap_amount_out( - &self, + &mut self, token_in: AccountId, token_out: AccountId, token_in_amount: u128, ) -> Result<(u128, u128), StablePoolError> { let (token_in_id, token_out_id) = self.check_tokens(token_in, token_out)?; - let rates = self.get_scaled_rates(&self.get_updated_rates())?; + let rates = self.get_scaled_rates()?; Ok(math::rated_swap_to( &rates, token_in_id, @@ -918,13 +866,13 @@ pub mod stable_pool { #[ink(message)] fn get_swap_amount_in( - &self, + &mut self, token_in: AccountId, token_out: AccountId, token_out_amount: u128, ) -> Result<(u128, u128), StablePoolError> { let (token_in_id, token_out_id) = self.check_tokens(token_in, token_out)?; - let rates = self.get_scaled_rates(&self.get_updated_rates())?; + let rates = self.get_scaled_rates()?; Ok(math::rated_swap_from( &rates, token_in_id, @@ -938,15 +886,14 @@ pub mod stable_pool { #[ink(message)] fn get_mint_liquidity_for_amounts( - &self, + &mut self, amounts: Vec, ) -> Result<(u128, u128), StablePoolError> { ensure!( amounts.len() == self.pool.tokens.len(), StablePoolError::IncorrectAmountsCount ); - let rates = self.get_scaled_rates(&self.get_updated_rates())?; - + let rates = self.get_scaled_rates()?; Ok(math::rated_compute_lp_amount_for_deposit( &rates, &amounts, @@ -971,14 +918,14 @@ pub mod stable_pool { #[ink(message)] fn get_burn_liquidity_for_amounts( - &self, + &mut self, amounts: Vec, ) -> Result<(u128, u128), StablePoolError> { ensure!( amounts.len() == self.pool.tokens.len(), StablePoolError::IncorrectAmountsCount ); - let rates = self.get_scaled_rates(&self.get_updated_rates())?; + let rates = self.get_scaled_rates()?; math::rated_compute_lp_amount_for_withdraw( &rates, &amounts, diff --git a/amm/contracts/stable_pool/token_rate.rs b/amm/contracts/stable_pool/token_rate.rs index 38c2a6d..71a0f04 100644 --- a/amm/contracts/stable_pool/token_rate.rs +++ b/amm/contracts/stable_pool/token_rate.rs @@ -5,10 +5,9 @@ use traits::RateProvider; #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct ExternalTokenRate { + rate_provider: AccountId, cached_token_rate: u128, - last_token_rate_update_ts: u64, - token_rate_contract: AccountId, - expiration_duration_ms: u64, + last_update_block_no: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] @@ -23,96 +22,47 @@ impl TokenRate { Self::Constant(rate) } - pub fn new_external( - current_time: u64, - token_rate_contract: AccountId, - expiration_duration_ms: u64, - ) -> Self { - let mut rate = Self::External(ExternalTokenRate::new( - current_time, - token_rate_contract, - expiration_duration_ms, - )); - _ = rate.force_update_rate(current_time); - rate + pub fn new_external(rate_provider: AccountId) -> Self { + Self::External(ExternalTokenRate::new(rate_provider)) } - /// Returns cached rate. - /// - /// NOTE: To make sure the rate is up-to-date, the caller should call `update_rate` before calling this method. - pub fn get_rate(&self) -> u128 { + /// Get current rate and update the cache. + pub fn get_rate(&mut self) -> u128 { match self { + Self::External(external) => external.get_rate_update(), Self::Constant(rate) => *rate, - Self::External(external) => external.get_rate(), } } - /// Update rate. - /// - /// Returns `true` if the rate was expired and value of the new rate is different than the previous. - pub fn update_rate(&mut self, current_time: u64) -> bool { + pub fn get_rate_provider(&self) -> Option { match self { - Self::External(external) => external.update_rate(current_time), - Self::Constant(_) => false, - } - } - - /// Update rate without expiry check. - /// - /// Returns `true` if value of the new rate is different than the previous. - pub fn force_update_rate(&mut self, current_time: u64) -> bool { - match self { - Self::External(external) => external.update_rate_no_cache(current_time), - Self::Constant(_) => false, + Self::External(external) => Some(external.rate_provider), + Self::Constant(_) => None, } } } impl ExternalTokenRate { - pub fn new( - current_time: u64, - token_rate_contract: AccountId, - expiration_duration_ms: u64, - ) -> Self { - let rate = Self::query_rate(token_rate_contract); + pub fn new(rate_provider: AccountId) -> Self { Self { - cached_token_rate: rate, - last_token_rate_update_ts: current_time, - token_rate_contract, - expiration_duration_ms, + rate_provider, + cached_token_rate: 0, + last_update_block_no: 0, } } - pub fn get_rate(&self) -> u128 { - self.cached_token_rate - } - - pub fn update_rate(&mut self, current_time: u64) -> bool { - if self.is_outdated(current_time) { - self.update(current_time) - } else { - false + pub fn get_rate_update(&mut self) -> u128 { + let current_block_no = ink::env::block_number::(); + if self.last_update_block_no < current_block_no { + self.cached_token_rate = self.query_rate(); + self.last_update_block_no = current_block_no; } + self.cached_token_rate } - pub fn update_rate_no_cache(&mut self, current_time: u64) -> bool { - self.update(current_time) - } - - fn query_rate(token_rate_contract: AccountId) -> u128 { + fn query_rate(&self) -> u128 { let mut rate_provider: contract_ref!(RateProvider, DefaultEnvironment) = - token_rate_contract.into(); + self.rate_provider.into(); rate_provider.get_rate() } - - fn is_outdated(&self, current_time: u64) -> bool { - current_time.saturating_sub(self.last_token_rate_update_ts) >= self.expiration_duration_ms - } - - fn update(&mut self, current_time: u64) -> bool { - let old_rate = self.cached_token_rate; - self.cached_token_rate = Self::query_rate(self.token_rate_contract); - self.last_token_rate_update_ts = current_time; - old_rate != self.cached_token_rate - } } diff --git a/amm/drink-tests/src/stable_swap_tests/tests_rated.rs b/amm/drink-tests/src/stable_swap_tests/tests_rated.rs index 83acb14..f2249f0 100644 --- a/amm/drink-tests/src/stable_swap_tests/tests_rated.rs +++ b/amm/drink-tests/src/stable_swap_tests/tests_rated.rs @@ -15,9 +15,6 @@ const ONE_LPT: u128 = 10u128.pow(18); const ONE_WAZERO: u128 = 10u128.pow(WAZERO_DEC as u32); const ONE_SAZERO: u128 = 10u128.pow(SAZERO_DEC as u32); -/// Cached token rate expiry time in milliseconds -const EXPIRE_TIME_MILLIS: u64 = 24 * 3600 * 1000; // 24h - fn deploy_rate_provider(session: &mut Session, salt: Vec) -> AccountId { let instance = mock_sazero_rate_contract::Instance::new().with_salt(salt); session @@ -34,7 +31,6 @@ fn setup_rated_swap_with_tokens( rate_providers: Vec>, initial_token_supply: u128, init_amp_coef: u128, - rate_expiration_duration_ms: u64, trade_fee: u32, protocol_fee: u32, ) -> (AccountId, Vec) { @@ -59,7 +55,6 @@ fn setup_rated_swap_with_tokens( tokens.clone(), vec![WAZERO_DEC; rate_providers.len()], rate_providers, - rate_expiration_duration_ms, init_amp_coef, caller.to_account_id(), trade_fee, @@ -115,13 +110,12 @@ fn test_01(mut session: Session) { vec![Some(mock_sazero_rate), None], initial_token_supply, 10000, - EXPIRE_TIME_MILLIS, 2_500_000, 200_000_000, ); let [sazero, wazero]: [AccountId; 2] = tokens.try_into().unwrap(); - set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS + 1); + set_timestamp(&mut session, now + 1); set_mock_rate(&mut session, mock_sazero_rate, 2 * RATE_PRECISION); _ = stable_swap::add_liquidity( @@ -248,12 +242,11 @@ fn test_02(mut session: Session) { vec![None, Some(mock_token_2_rate), None], initial_token_supply, 10000, - EXPIRE_TIME_MILLIS, 2_500_000, 200_000_000, ); - set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS); + set_timestamp(&mut session, now); set_mock_rate(&mut session, mock_token_2_rate, 2 * RATE_PRECISION); _ = stable_swap::add_liquidity( @@ -359,12 +352,11 @@ fn test_03(mut session: Session) { vec![None, Some(mock_token_2_rate), Some(mock_token_3_rate)], initial_token_supply, 10000, - EXPIRE_TIME_MILLIS, 2_500_000, 200_000_000, ); - set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS); + set_timestamp(&mut session, now); set_mock_rate(&mut session, mock_token_2_rate, 2 * RATE_PRECISION); set_mock_rate(&mut session, mock_token_3_rate, 4 * RATE_PRECISION); @@ -467,7 +459,6 @@ fn test_04(mut session: Session) { vec![None, None], initial_token_supply, 10000, - EXPIRE_TIME_MILLIS, 2_500_000, 200_000_000, ); diff --git a/amm/traits/stable_pool.rs b/amm/traits/stable_pool.rs index 9bb2f53..95f98c0 100644 --- a/amm/traits/stable_pool.rs +++ b/amm/traits/stable_pool.rs @@ -27,18 +27,27 @@ pub trait StablePool { #[ink(message)] fn fee_receiver(&self) -> Option; - /// Updates cached token rates if expired and - /// returns current tokens rates with precision of 12 decimal places. + /// Updates cached token rates if there was a new block since the previous update. + /// + /// Returns current tokens rates with precision of 12 decimal places. #[ink(message)] fn token_rates(&mut self) -> Vec; + /// Returns list of RateProvider address for each token. + /// If the rate is constant, returns None. + #[ink(message)] + fn token_rates_providers(&self) -> Vec>; + /// Calculate swap amount of `token_out` /// given `token_in amount`. + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns a tuple of (amount out, fee) /// NOTE: fee is applied on `token_out` #[ink(message)] fn get_swap_amount_out( - &self, + &mut self, token_in: AccountId, token_out: AccountId, token_in_amount: u128, @@ -46,11 +55,14 @@ pub trait StablePool { /// Calculate required swap amount of `token_in` /// to get `token_out_amount`. + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns a tuple of (amount in, fee) /// NOTE: fee is applied on `token_out` #[ink(message)] fn get_swap_amount_in( - &self, + &mut self, token_in: AccountId, token_out: AccountId, token_out_amount: u128, @@ -58,15 +70,21 @@ pub trait StablePool { /// Calculate how many lp tokens will be minted /// given deposit `amounts`. + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns a tuple of (lpt amount, fee) #[ink(message)] fn get_mint_liquidity_for_amounts( - &self, + &mut self, amounts: Vec, ) -> Result<(u128, u128), StablePoolError>; /// Calculate ideal deposit amounts required /// to mint `liquidity` amount of lp tokens + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns required deposit amounts #[ink(message)] fn get_amounts_for_liquidity_mint( @@ -76,15 +94,21 @@ pub trait StablePool { /// Calculate how many lp tokens will be burned /// given withdraw `amounts`. + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns a tuple of (lpt amount, fee part) #[ink(message)] fn get_burn_liquidity_for_amounts( - &self, + &mut self, amounts: Vec, ) -> Result<(u128, u128), StablePoolError>; /// Calculate ideal withdraw amounts for /// burning `liquidity` amount of lp tokens + /// + /// Updates cached token rates if there was a new block since the previous update. + /// /// Returns withdraw amounts #[ink(message)] fn get_amounts_for_liquidity_burn( @@ -176,11 +200,6 @@ pub trait StablePool { to: AccountId, ) -> Result<(u128, u128), StablePoolError>; - /// Update cached rates without expiry check. - /// Can be called by anyone. - #[ink(message)] - fn force_update_rates(&mut self); - // --- OWNER RESTRICTED FUNCTIONS --- // #[ink(message)] @@ -190,11 +209,7 @@ pub trait StablePool { /// - trade_fee given as an integer with 1e9 precision. The the maximum is 1% (10000000) /// - protocol_fee given as an integer with 1e9 precision. The maximum is 50% (500000000) #[ink(message)] - fn set_fees( - &mut self, - trade_fee: u32, - protocol_fee: u32, - ) -> Result<(), StablePoolError>; + fn set_fees(&mut self, trade_fee: u32, protocol_fee: u32) -> Result<(), StablePoolError>; #[ink(message)] fn set_amp_coef(&mut self, amp_coef: u128) -> Result<(), StablePoolError>;