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
use crate::AbstractResult;
use cosmwasm_std::{Decimal, Env, Storage, Uint128};
use cw_storage_plus::Item;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::ops::Mul;

pub const DEFAULT_PRECISION: u8 = 6;

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

impl<'a> TimeWeightedAverage<'a> {
    pub const fn new(key: &'a 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.seconds();

        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)
    }
    pub fn accumulate(
        &self,
        env: &Env,
        store: &mut dyn Storage,
        current_value: Decimal,
    ) -> AbstractResult<Option<(u128, u64)>> {
        let mut twa = self.0.load(store)?;
        let block_time = env.block.time.seconds();
        if block_time <= twa.last_block_time {
            return Ok(None);
        }

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

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

    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)
    }

    /// 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.seconds();

        let time_elapsed = block_time - twa.last_averaging_block_time;

        // 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: u64,
    pub cumulative_value: u128,

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