abstract_core/objects/
time_weighted_average.rs

1//! # Time Weighted Average (TWA) helper
2//!
3//! A time weighted average is an accumulating value that is updated irregularly.
4//! Whenever an update is applied, the time between the current update and the last update is used, along with the current value,
5//! to accumulate the cumulative value.
6//!
7//!
8
9use std::ops::Mul;
10
11use cosmwasm_std::{Addr, Decimal, Env, QuerierWrapper, Storage, Timestamp, Uint128};
12use cw_storage_plus::Item;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16use crate::AbstractResult;
17
18pub const DEFAULT_PRECISION: u8 = 6;
19
20/// Time Weighted Average (TWA) helper
21pub struct TimeWeightedAverage<'a>(Item<'a, TimeWeightedAverageData>);
22
23impl<'a> TimeWeightedAverage<'a> {
24    pub const fn new(key: &'a str) -> Self {
25        Self(Item::new(key))
26    }
27    pub fn instantiate(
28        &self,
29        store: &mut dyn Storage,
30        env: &Env,
31        precision: Option<u8>,
32        averaging_period: u64,
33    ) -> AbstractResult<()> {
34        let block_time = env.block.time;
35
36        let twa = TimeWeightedAverageData {
37            cumulative_value: 0,
38            last_block_time: block_time,
39            precision: precision.unwrap_or(DEFAULT_PRECISION),
40            average_value: Decimal::zero(),
41            averaging_period,
42            last_averaging_cumulative_value: 0,
43            last_averaging_block_time: block_time,
44            last_averaging_block_height: env.block.height,
45        };
46        self.0.save(store, &twa).map_err(Into::into)
47    }
48
49    /// Applies the current value to the TWA for the duration since the last update
50    /// and returns the cumulative value and block time.
51    pub fn accumulate(
52        &self,
53        env: &Env,
54        store: &mut dyn Storage,
55        current_value: Decimal,
56    ) -> AbstractResult<Option<u128>> {
57        let mut twa = self.0.load(store)?;
58        let block_time = env.block.time;
59        if block_time <= twa.last_block_time {
60            return Ok(None);
61        }
62
63        let time_elapsed = Uint128::from(block_time.seconds() - twa.last_block_time.seconds());
64        twa.last_block_time = block_time;
65
66        if !current_value.is_zero() {
67            twa.cumulative_value = twa
68                .cumulative_value
69                .wrapping_add(time_elapsed.mul(current_value).u128());
70        };
71        self.0.save(store, &twa)?;
72        Ok(Some(twa.cumulative_value))
73    }
74
75    pub fn get_value(&self, store: &dyn Storage) -> AbstractResult<Decimal> {
76        Ok(self.0.load(store)?.average_value)
77    }
78
79    pub fn load(&self, store: &dyn Storage) -> AbstractResult<TimeWeightedAverageData> {
80        self.0.load(store).map_err(Into::into)
81    }
82
83    pub fn query(
84        &self,
85        querier: &QuerierWrapper,
86        remote_contract_addr: Addr,
87    ) -> AbstractResult<TimeWeightedAverageData> {
88        self.0
89            .query(querier, remote_contract_addr)
90            .map_err(Into::into)
91    }
92
93    /// Get average value, updates when possible
94    pub fn try_update_value(
95        &self,
96        env: &Env,
97        store: &mut dyn Storage,
98    ) -> AbstractResult<Option<Decimal>> {
99        let mut twa = self.0.load(store)?;
100
101        let block_time = env.block.time;
102
103        let time_elapsed = block_time.seconds() - twa.last_averaging_block_time.seconds();
104
105        // Ensure that at least one full period has passed since the last update
106        if time_elapsed < twa.averaging_period {
107            return Ok(None);
108        }
109
110        // (current_cum - last_cum) / time
111        let new_average_value = Decimal::from_ratio(
112            twa.cumulative_value
113                .wrapping_sub(twa.last_averaging_cumulative_value),
114            time_elapsed,
115        );
116
117        twa = TimeWeightedAverageData {
118            average_value: new_average_value,
119            last_averaging_block_time: block_time,
120            last_averaging_cumulative_value: twa.cumulative_value,
121            ..twa
122        };
123        self.0.save(store, &twa)?;
124        Ok(Some(new_average_value))
125    }
126
127    pub fn update_settings(
128        &self,
129        _env: &Env,
130        store: &mut dyn Storage,
131        averaging_period: u64,
132    ) -> AbstractResult<()> {
133        let mut twa = self.0.load(store)?;
134        twa.averaging_period = averaging_period;
135        self.0.save(store, &twa).map_err(Into::into)
136    }
137}
138
139#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
140pub struct TimeWeightedAverageData {
141    // settings for accumulating value data
142    pub precision: u8,
143    pub last_block_time: Timestamp,
144    pub cumulative_value: u128,
145
146    // Data to get average value
147    pub last_averaging_block_time: Timestamp,
148    pub last_averaging_block_height: u64,
149    pub last_averaging_cumulative_value: u128,
150    pub averaging_period: u64,
151    /// The requested average value
152    pub average_value: Decimal,
153}
154
155impl TimeWeightedAverageData {
156    pub fn needs_refresh(&self, env: &Env) -> bool {
157        let block_time = env.block.time;
158
159        let time_elapsed = block_time.seconds() - self.last_averaging_block_time.seconds();
160
161        // At least one full period has passed since the last update
162        time_elapsed >= self.averaging_period
163    }
164}