abstract_std/objects/
time_weighted_average.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! # Time Weighted Average (TWA) helper
//!
//! A time weighted average is an accumulating value that is updated irregularly.
//! Whenever an update is applied, the time between the current update and the last update is used, along with the current value,
//! to accumulate the cumulative value.
//!
//!

use cosmwasm_std::{Addr, Decimal, Env, QuerierWrapper, Storage, Timestamp, Uint128};
use cw_storage_plus::Item;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::AbstractResult;

pub const DEFAULT_PRECISION: u8 = 6;

/// Time Weighted Average (TWA) helper
pub struct TimeWeightedAverage(Item<TimeWeightedAverageData>);

impl TimeWeightedAverage {
    pub const fn new(key: &'static str) -> Self {
        Self(Item::new(key))
    }
    pub fn instantiate(
        &self,
        store: &mut dyn Storage,
        env: &Env,
        precision: Option<u8>,
        averaging_period: u64,
    ) -> AbstractResult<()> {
        let block_time = env.block.time;

        let twa = TimeWeightedAverageData {
            cumulative_value: 0,
            last_block_time: block_time,
            precision: precision.unwrap_or(DEFAULT_PRECISION),
            average_value: Decimal::zero(),
            averaging_period,
            last_averaging_cumulative_value: 0,
            last_averaging_block_time: block_time,
            last_averaging_block_height: env.block.height,
        };
        self.0.save(store, &twa).map_err(Into::into)
    }

    /// Applies the current value to the TWA for the duration since the last update
    /// and returns the cumulative value and block time.
    pub fn accumulate(
        &self,
        env: &Env,
        store: &mut dyn Storage,
        current_value: Decimal,
    ) -> AbstractResult<Option<u128>> {
        let mut twa = self.0.load(store)?;
        let block_time = env.block.time;
        if block_time <= twa.last_block_time {
            return Ok(None);
        }

        let time_elapsed = Uint128::from(block_time.seconds() - twa.last_block_time.seconds());
        twa.last_block_time = block_time;

        if !current_value.is_zero() {
            twa.cumulative_value = twa
                .cumulative_value
                .wrapping_add(time_elapsed.mul_floor(current_value).u128());
        };
        self.0.save(store, &twa)?;
        Ok(Some(twa.cumulative_value))
    }

    pub fn get_value(&self, store: &dyn Storage) -> AbstractResult<Decimal> {
        Ok(self.0.load(store)?.average_value)
    }

    pub fn load(&self, store: &dyn Storage) -> AbstractResult<TimeWeightedAverageData> {
        self.0.load(store).map_err(Into::into)
    }

    pub fn query(
        &self,
        querier: &QuerierWrapper,
        remote_contract_addr: Addr,
    ) -> AbstractResult<TimeWeightedAverageData> {
        self.0
            .query(querier, remote_contract_addr)
            .map_err(Into::into)
    }

    /// Get average value, updates when possible
    pub fn try_update_value(
        &self,
        env: &Env,
        store: &mut dyn Storage,
    ) -> AbstractResult<Option<Decimal>> {
        let mut twa = self.0.load(store)?;

        let block_time = env.block.time;

        let time_elapsed = block_time.seconds() - twa.last_averaging_block_time.seconds();

        // Ensure that at least one full period has passed since the last update
        if time_elapsed < twa.averaging_period {
            return Ok(None);
        }

        // (current_cum - last_cum) / time
        let new_average_value = Decimal::from_ratio(
            twa.cumulative_value
                .wrapping_sub(twa.last_averaging_cumulative_value),
            time_elapsed,
        );

        twa = TimeWeightedAverageData {
            average_value: new_average_value,
            last_averaging_block_time: block_time,
            last_averaging_cumulative_value: twa.cumulative_value,
            ..twa
        };
        self.0.save(store, &twa)?;
        Ok(Some(new_average_value))
    }

    pub fn update_settings(
        &self,
        _env: &Env,
        store: &mut dyn Storage,
        averaging_period: u64,
    ) -> AbstractResult<()> {
        let mut twa = self.0.load(store)?;
        twa.averaging_period = averaging_period;
        self.0.save(store, &twa).map_err(Into::into)
    }
}

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct TimeWeightedAverageData {
    // settings for accumulating value data
    pub precision: u8,
    pub last_block_time: Timestamp,
    pub cumulative_value: u128,

    // Data to get average value
    pub last_averaging_block_time: Timestamp,
    pub last_averaging_block_height: u64,
    pub last_averaging_cumulative_value: u128,
    pub averaging_period: u64,
    /// The requested average value
    pub average_value: Decimal,
}

impl TimeWeightedAverageData {
    pub fn needs_refresh(&self, env: &Env) -> bool {
        let block_time = env.block.time;

        let time_elapsed = block_time.seconds() - self.last_averaging_block_time.seconds();

        // At least one full period has passed since the last update
        time_elapsed >= self.averaging_period
    }
}