Skip to content

Commit 4092c39

Browse files
suitemetaproph3t
andauthored
Add a delay before TWAP starts getting recorded (#264)
* added twap_start_delay_slots field to dao, implemented into update_twap * validate twap_start_delay_slots matches dao config on proposal create * update sdk with new twapStartDelaySlots * added slot calc utils * update existing tests to account for new twapStartDelaySlots * update devnet script to account for new twapStartDelaySlots * add specific twap delay tests * account for twapStartDelaySlots * twap integration test * test twap expected value * bump sdk version * v0.4.0-alpha.53 * Update package version * Have TWAP delay update the latest observation, just not aggregator --------- Co-authored-by: metaproph3t <metaproph3t@protonmail.com>
1 parent 25a06e6 commit 4092c39

File tree

28 files changed

+398
-62
lines changed

28 files changed

+398
-62
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"dependencies": {
99
"@coral-xyz/anchor": "0.29.0",
10-
"@metadaoproject/futarchy": "0.4.0-alpha.51",
10+
"@metadaoproject/futarchy": "0.4.0-alpha.53",
1111
"@metaplex-foundation/mpl-token-metadata": "^3.2.0",
1212
"@metaplex-foundation/umi": "^0.9.1",
1313
"@metaplex-foundation/umi-bundle-defaults": "^0.9.1",

programs/amm/src/instructions/create_amm.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::events::{CommonFields, CreateAmmEvent};
1111
pub struct CreateAmmArgs {
1212
pub twap_initial_observation: u128,
1313
pub twap_max_observation_change_per_update: u128,
14+
pub twap_start_delay_slots: u64,
1415
}
1516

1617
#[event_cpi]
@@ -94,6 +95,7 @@ impl CreateAmm<'_> {
9495
let CreateAmmArgs {
9596
twap_initial_observation,
9697
twap_max_observation_change_per_update,
98+
twap_start_delay_slots,
9799
} = args;
98100

99101
amm.set_inner(Amm {
@@ -115,6 +117,7 @@ impl CreateAmm<'_> {
115117
current_slot,
116118
twap_initial_observation,
117119
twap_max_observation_change_per_update,
120+
twap_start_delay_slots,
118121
),
119122

120123
seq_num: 0,

programs/amm/src/state/amm.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@ pub struct TwapOracle {
4444
pub max_observation_change_per_update: u128,
4545
/// What the initial `latest_observation` is set to.
4646
pub initial_observation: u128,
47+
/// Number of slots after amm.created_at_slot to start recording TWAP
48+
pub start_delay_slots: u64,
4749
}
4850

4951
impl TwapOracle {
5052
pub fn new(
5153
current_slot: Slot,
5254
initial_observation: u128,
5355
max_observation_change_per_update: u128,
56+
start_delay_slots: u64,
5457
) -> Self {
5558
Self {
5659
last_updated_slot: current_slot,
@@ -59,6 +62,7 @@ impl TwapOracle {
5962
aggregator: 0,
6063
max_observation_change_per_update,
6164
initial_observation,
65+
start_delay_slots,
6266
}
6367
}
6468
}
@@ -163,9 +167,12 @@ impl Amm {
163167
((lp_tokens as u128 * self.quote_amount as u128) / lp_total_supply as u128) as u64
164168
}
165169

166-
/// Returns the time-weighted average price since market creation in UQ64x32 form.
170+
/// Returns the time-weighted average price since market creation
167171
pub fn get_twap(&self) -> Result<u128> {
168-
let slots_passed = (self.oracle.last_updated_slot - self.created_at_slot) as u128;
172+
let start_slot = self.created_at_slot + self.oracle.start_delay_slots;
173+
174+
require_gt!(self.oracle.last_updated_slot, start_slot, AmmError::NoSlotsPassed);
175+
let slots_passed = (self.oracle.last_updated_slot - start_slot) as u128;
169176

170177
require_neq!(slots_passed, 0, AmmError::NoSlotsPassed);
171178
require!(self.oracle.aggregator != 0, AmmError::AssertFailed);
@@ -179,6 +186,7 @@ impl Amm {
179186
/// Returns an observation if one was recorded.
180187
pub fn update_twap(&mut self, current_slot: Slot) -> Result<Option<u128>> {
181188
let oracle = &mut self.oracle;
189+
182190
// a manipulator is likely to be "bursty" with their usage, such as a
183191
// validator who abuses their slots to manipulate the TWAP.
184192
// meanwhile, regular trading is less likely to happen in each slot.
@@ -228,22 +236,35 @@ impl Amm {
228236
max(price, min_observation)
229237
};
230238

231-
let slot_difference = (current_slot - oracle.last_updated_slot) as u128;
239+
// if the start delay hasn't passed, we don't update the aggregator
240+
// but we still update the observation
241+
let twap_start_slot = self.created_at_slot + oracle.start_delay_slots;
242+
243+
let new_aggregator = if current_slot <= twap_start_slot {
244+
oracle.aggregator
245+
} else {
246+
// so that we don't act as if the first update ocurred over the whole
247+
// pre-start delay period
248+
let effective_last_updated_slot = oracle.last_updated_slot.max(twap_start_slot);
249+
250+
let slot_difference = (current_slot - effective_last_updated_slot) as u128;
232251

233-
// if this saturates, the aggregator will wrap back to 0, so this value doesn't
234-
// really matter. we just can't panic.
235-
let weighted_observation = new_observation.saturating_mul(slot_difference);
252+
// if this saturates, the aggregator will wrap back to 0, so this value doesn't
253+
// really matter. we just can't panic.
254+
let weighted_observation = new_observation.saturating_mul(slot_difference);
236255

237-
let new_aggregator = oracle.aggregator.wrapping_add(weighted_observation);
256+
oracle.aggregator.wrapping_add(weighted_observation)
257+
};
238258

239259
let new_oracle = TwapOracle {
240260
last_updated_slot: current_slot,
241261
last_price: price,
242262
last_observation: new_observation,
243263
aggregator: new_aggregator,
244-
// these two shouldn't change
264+
// these three shouldn't change
245265
max_observation_change_per_update: oracle.max_observation_change_per_update,
246266
initial_observation: oracle.initial_observation,
267+
start_delay_slots: oracle.start_delay_slots,
247268
};
248269

249270
require!(

programs/autocrat/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub enum AutocratError {
1010
"An amm has a `max_observation_change_per_update` that doesn't match the `dao`'s config"
1111
)]
1212
InvalidMaxObservationChange,
13+
#[msg("An amm has a `start_delay_slots` that doesn't match the `dao`'s config")]
14+
InvalidStartDelaySlots,
1315
#[msg("One of the vaults has an invalid `settlement_authority`")]
1416
InvalidSettlementAuthority,
1517
#[msg("Proposal is too young to be executed or rejected")]

programs/autocrat/src/instructions/initialize_dao.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::*;
44
pub struct InitializeDaoParams {
55
pub twap_initial_observation: u128,
66
pub twap_max_observation_change_per_update: u128,
7+
pub twap_start_delay_slots: u64,
78
pub min_quote_futarchic_liquidity: u64,
89
pub min_base_futarchic_liquidity: u64,
910
pub pass_threshold_bps: Option<u16>,
@@ -33,6 +34,7 @@ impl InitializeDao<'_> {
3334
let InitializeDaoParams {
3435
twap_initial_observation,
3536
twap_max_observation_change_per_update,
37+
twap_start_delay_slots,
3638
min_base_futarchic_liquidity,
3739
min_quote_futarchic_liquidity,
3840
pass_threshold_bps,
@@ -54,6 +56,7 @@ impl InitializeDao<'_> {
5456
slots_per_proposal: slots_per_proposal.unwrap_or(THREE_DAYS_IN_SLOTS),
5557
twap_initial_observation,
5658
twap_max_observation_change_per_update,
59+
twap_start_delay_slots,
5760
min_base_futarchic_liquidity,
5861
min_quote_futarchic_liquidity,
5962
seq_num: 0,

programs/autocrat/src/instructions/initialize_proposal.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ impl InitializeProposal<'_> {
106106
self.dao.twap_max_observation_change_per_update,
107107
AutocratError::InvalidMaxObservationChange
108108
);
109+
110+
require_eq!(
111+
amm.oracle.start_delay_slots,
112+
self.dao.twap_start_delay_slots,
113+
AutocratError::InvalidStartDelaySlots
114+
);
109115
}
110116

111117
// Should never be the case because the oracle is the proposal account, and you can't re-initialize a proposal

programs/autocrat/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ security_txt! {
6464
declare_id!("autowMzCbM29YXMgVG3T62Hkgo7RcyrvgQQkd54fDQL");
6565

6666
pub const SLOTS_PER_10_SECS: u64 = 25;
67-
pub const THREE_DAYS_IN_SLOTS: u64 = 3 * 24 * 60 * 6 * SLOTS_PER_10_SECS;
67+
pub const DAY_IN_SLOTS: u64 = 24 * 60 * 6 * SLOTS_PER_10_SECS;
68+
pub const THREE_DAYS_IN_SLOTS: u64 = 3 * DAY_IN_SLOTS;
6869

6970
pub const TEN_DAYS_IN_SECONDS: i64 = 10 * 24 * 60 * 60;
7071

programs/autocrat/src/state/dao.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub struct Dao {
2727
/// in 50 minutes.
2828
pub twap_initial_observation: u128,
2929
pub twap_max_observation_change_per_update: u128,
30+
/// Forces TWAP calculation to start after amm.created_at_slot + twap_start_delay_slots
31+
pub twap_start_delay_slots: u64,
3032
/// As an anti-spam measure and to help liquidity, you need to lock up some liquidity
3133
/// in both futarchic markets in order to create a proposal.
3234
///

programs/launchpad/src/instructions/complete_launch.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use raydium_cpmm_cpi::{
2222

2323
use autocrat::program::Autocrat;
2424
use autocrat::InitializeDaoParams;
25+
use autocrat::{DAY_IN_SLOTS};
2526

2627
pub const PRICE_SCALE: u128 = 1_000_000_000_000;
2728

@@ -287,7 +288,8 @@ impl CompleteLaunch<'_> {
287288
min_quote_futarchic_liquidity: total_committed_amount / 100,
288289
min_base_futarchic_liquidity: AVAILABLE_TOKENS / 100,
289290
pass_threshold_bps: None,
290-
slots_per_proposal: None,
291+
slots_per_proposal: Some(3 * DAY_IN_SLOTS),
292+
twap_start_delay_slots: DAY_IN_SLOTS,
291293
},
292294
)?;
293295

scripts/setupDevnet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ async function main() {
144144
let storedAmm: Amm | null = await ammProgram.fetchAmm(amm);
145145
console.log(storedAmm);
146146
if (!storedAmm) {
147-
await ammProgram.initializeAmmIx(pUp, pDown, new BN(0), new BN(0)).rpc();
147+
await ammProgram.initializeAmmIx(pUp, pDown, new BN(0), new BN(0), new BN(0)).rpc();
148148
storedAmm = await ammProgram.fetchAmm(amm);
149149
}
150150

0 commit comments

Comments
 (0)