Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove expiration for cached rates #32

Merged
merged 6 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 29 additions & 82 deletions amm/contracts/stable_pool/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,6 @@ pub mod stable_pool {
pub reserves: Vec<u128>,
}

#[ink(event)]
pub struct RatesUpdated {
pub rates: Vec<TokenRate>,
}

#[ink(event)]
pub struct Approval {
/// Account providing allowance.
Expand Down Expand Up @@ -244,26 +239,19 @@ pub mod stable_pool {
tokens: Vec<AccountId>,
tokens_decimals: Vec<u8>,
external_rates: Vec<Option<AccountId>>,
rate_expiration_duration_ms: u64,
init_amp_coef: u128,
owner: AccountId,
trade_fee: u32,
protocol_fee: u32,
fee_receiver: Option<AccountId>,
) -> Result<Self, StablePoolError> {
let current_time = Self::env().block_timestamp();
let token_rates: Vec<TokenRate> = 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,
Expand Down Expand Up @@ -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<TokenRate> {
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<Vec<u128>, MathError> {
rates
.iter()
fn get_scaled_rates(&mut self) -> Result<Vec<u128>, 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()
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -888,23 +828,31 @@ pub mod stable_pool {

#[ink(message)]
fn token_rates(&mut self) -> Vec<u128> {
self.update_rates();
self.pool
.token_rates
.iter()
.iter_mut()
.map(|rate| rate.get_rate())
.collect()
}

#[ink(message)]
fn token_rates_providers(&self) -> Vec<Option<AccountId>> {
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,
Expand All @@ -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,
Expand All @@ -938,15 +886,14 @@ pub mod stable_pool {

#[ink(message)]
fn get_mint_liquidity_for_amounts(
&self,
&mut self,
amounts: Vec<u128>,
) -> 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,
Expand All @@ -971,14 +918,14 @@ pub mod stable_pool {

#[ink(message)]
fn get_burn_liquidity_for_amounts(
&self,
&mut self,
amounts: Vec<u128>,
) -> 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,
Expand Down
94 changes: 22 additions & 72 deletions amm/contracts/stable_pool/token_rate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<AccountId> {
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::<DefaultEnvironment>();
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
}
}
Loading
Loading