-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
todo: add rolling time period
// two possible ways of structuring this, the current method which reduces
// the need for multiple transactions triggering rate limit evaluation within the same block
// from having to pay the gas costs associated with calculating the decayed value of the
// previous time period, at the expense of increased storage costs of ~16 bytes per rate limit
// and slightly increased gas consumption the first time a rate limit is evaluated in a new period
pub struct RateLimitValue {
pub previous_period_value: u64,
pub current_period_value: u64,
// height that interval parameters were last updated, and if this differs
// from the height reported in cosmwasm_std::Env, a decay update operation is applied
pub interval_check_height: u64,
// timestamp at which the current period began
pub period_start: Timestamp,
// timestamp at which the current period ends
pub period_end: Timestamp,
// @note: needs to be updated whenever the interval_check_height differs from the block's current height
pub decayed_value: cosmwasm_std::Decimal
}
impl RateLimitValue {
// averages the input against the decayed value of the previous period
// @note: for brevity the process of computing the input value is left out, for example it may be
// the net flow of a channel (such as in the existing osmosis rate limits)
pub fn averaged_value(&mut self, env: cosmwasm_std::Env, input: u64) -> Option<cosmwasm_std::Decimal> {
self.current_period_value = input;
// when a rule is first initialized there is no previous period value, so there is nothing to average
if self.previous_period_value == 0 {
return cosmwasm_std::Decimal::from_atomics(self.current_period_value, 0).ok();
}
let current_value = cosmwasm_std::Decimal::from_atomics(self.current_period_value, 0).ok()?;
let decayed_value = self.check_decay_rate(env)?;
Some((current_value + decayed_value) / cosmwasm_std::Decimal::from_atomics(2_u64, 0).ok()?)
}
// returns the amount of time that has passed in the given time period, based on the current timestamp recorded in the block
// this transaction is executing in
pub fn period_percent_passed(&self, block_time_second: u64) -> cosmwasm_std::Decimal {
// todo: measure the gas costs of calling `self.period_start.seconds()` twice vs storing the result of the function call in memory as is done now
let period_start_seconds = self.period_start.seconds();
return cosmwasm_std::Decimal::percent(((block_time_second - period_start_seconds) * 100) / (self.period_end.seconds() - period_start_seconds));
}
// checks if a decay operation should be applied to the value from the previous time period
// returning the existing decayed value if there is no difference in block height or timestamp
pub fn check_decay_rate(&mut self, env: cosmwasm_std::Env) -> Option<cosmwasm_std::Decimal> {
if self.interval_check_height == env.block.height {
return Some(self.decayed_value);
}
// should realistically only happen the first period after the rate limit is initialized
if self.previous_period_value == 0 {
return cosmwasm_std::Decimal::from_atomics(self.current_period_value, 0).ok();
}
if self.period_start == env.block.time {
// no time passed, return zero, this has the edge case of two blocks potentially having
// the same timestamp under certain conditions (fast block, loose constraints around timestamp requirements, etc...)
return Some(self.decayed_value);
}
let percent_passed = self.period_percent_passed(env.block.time.seconds());
self.decayed_value = cosmwasm_std::Decimal::from_atomics(self.previous_period_value, 0).ok()? * percent_passed;
return Some(self.decayed_value);
}
}
#[cfg(test)]
mod test {
use super::*;
use cosmwasm_std::testing::{mock_env};
#[test]
fn test_rate_limit_value() {
let mut env = mock_env();
env.block.height = 12_345;
env.block.time = Timestamp::from_seconds(1690757434);
let mut rv = RateLimitValue {
previous_period_value: 10_000,
current_period_value: 5_000,
period_start: Timestamp::from_seconds(1690757434),
period_end: Timestamp::from_seconds(1690786248),
interval_check_height: 12_344,
decayed_value: cosmwasm_std::Decimal::raw(0),
};
let rate = rv.check_decay_rate(env.clone()).unwrap();
assert!(rate == cosmwasm_std::Decimal::zero());
env.block.height = 12_346;
let rate = rv.check_decay_rate(env.clone()).unwrap();
assert!(rate == cosmwasm_std::Decimal::zero());
env.block.time = Timestamp::from_seconds(1690763805);
let rate: u128 = rv.check_decay_rate(env.clone()).unwrap().atomics().into();
assert!(rate == 2200000000000000000000);
let val: u128 = rv.averaged_value(env.clone(), 8_000).unwrap().atomics().into();
assert!(val == 5100000000000000000000);
}
}```Metadata
Metadata
Assignees
Labels
No labels