Skip to content

Commit 78e2b2b

Browse files
authored
Futarchy/PBPP revisions (#429)
* Fix TWAP accumulator weighting and add virtual crank to get_twap * prevent same mint on dao init, adjust error * reject performance package init/change that would result in same authority and recipient * Relax calculate_twap check to allow finalization when last trade is before deadline
1 parent 4fb2e84 commit 78e2b2b

17 files changed

Lines changed: 291 additions & 14 deletions

programs/futarchy/src/error.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub enum FutarchyError {
3232
PassThresholdTooHigh,
3333
#[msg("Question must have exactly 2 outcomes for binary futarchy")]
3434
QuestionMustBeBinary,
35-
#[msg("Squads proposal must be in Draft status")]
35+
#[msg("Squads proposal must be in Active status")]
3636
InvalidSquadsProposalStatus,
3737
#[msg("Casting overflow. If you're seeing this, please report this")]
3838
CastingOverflow,
@@ -76,4 +76,6 @@ pub enum FutarchyError {
7676
InvalidTargetK,
7777
#[msg("Failed to compile transaction message for Squads vault transaction")]
7878
InvalidTransactionMessage,
79+
#[msg("Base mint and quote mint must be different")]
80+
InvalidMint,
7981
}

programs/futarchy/src/instructions/finalize_proposal.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,19 @@ impl FinalizeProposal<'_> {
115115
];
116116
let proposal_signer = &[&proposal_seeds[..]];
117117

118+
let clock = Clock::get()?;
119+
118120
let calculate_twap = |amm: &Pool| -> Result<u128> {
119-
let seconds_passed = amm.oracle.last_updated_timestamp - proposal.timestamp_enqueued;
121+
let twap_start_timestamp =
122+
amm.oracle.created_at_timestamp + amm.oracle.start_delay_seconds as i64;
120123

121-
require_gte!(
122-
seconds_passed,
123-
proposal.duration_in_seconds as i64,
124+
require_gt!(
125+
amm.oracle.last_updated_timestamp,
126+
twap_start_timestamp,
124127
FutarchyError::MarketsTooYoung
125128
);
126129

127-
amm.get_twap()
130+
amm.get_twap(clock.unix_timestamp)
128131
};
129132

130133
let PoolState::Futarchy {
@@ -269,8 +272,6 @@ impl FinalizeProposal<'_> {
269272

270273
dao.seq_num += 1;
271274

272-
let clock = Clock::get()?;
273-
274275
emit_cpi!(FinalizeProposalEvent {
275276
common: CommonFields::new(&clock, dao.seq_num),
276277
proposal: proposal.key(),

programs/futarchy/src/instructions/initialize_dao.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ pub mod permissionless_account {
7070
}
7171

7272
impl InitializeDao<'_> {
73+
pub fn validate(&self) -> Result<()> {
74+
require_keys_neq!(
75+
self.base_mint.key(),
76+
self.quote_mint.key(),
77+
FutarchyError::InvalidMint
78+
);
79+
Ok(())
80+
}
81+
7382
pub fn handle(ctx: Context<Self>, params: InitializeDaoParams) -> Result<()> {
7483
let InitializeDaoParams {
7584
twap_initial_observation,

programs/futarchy/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub const DEFAULT_MAX_OBSERVATION_CHANGE_PER_UPDATE_LOTS: u64 = 5_000;
5959
pub mod futarchy {
6060
use super::*;
6161

62+
#[access_control(ctx.accounts.validate())]
6263
pub fn initialize_dao(ctx: Context<InitializeDao>, params: InitializeDaoParams) -> Result<()> {
6364
InitializeDao::handle(ctx, params)
6465
}

programs/futarchy/src/state/futarchy_amm.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ impl Pool {
406406

407407
// if this saturates, the aggregator will wrap back to 0, so this value doesn't
408408
// really matter. we just can't panic.
409-
let weighted_observation = new_observation.saturating_mul(time_difference);
409+
let weighted_observation = last_observation.saturating_mul(time_difference);
410410

411411
oracle.aggregator.wrapping_add(weighted_observation)
412412
};
@@ -449,17 +449,23 @@ impl Pool {
449449
}
450450

451451
/// Returns the time-weighted average price since market creation
452-
pub fn get_twap(&self) -> Result<u128> {
452+
pub fn get_twap(&self, current_timestamp: i64) -> Result<u128> {
453453
let start_timestamp =
454454
self.oracle.created_at_timestamp + self.oracle.start_delay_seconds as i64;
455455

456456
require_gt!(self.oracle.last_updated_timestamp, start_timestamp);
457-
let seconds_passed = (self.oracle.last_updated_timestamp - start_timestamp) as u128;
457+
458+
let seconds_passed = (current_timestamp - start_timestamp) as u128;
458459

459460
require_neq!(seconds_passed, 0);
460461
require_neq!(self.oracle.aggregator, 0);
461462

462-
Ok(self.oracle.aggregator / seconds_passed)
463+
// include the final interval that hasn't been accumulated yet
464+
let final_interval = (current_timestamp - self.oracle.last_updated_timestamp) as u128;
465+
let final_contribution = self.oracle.last_observation.saturating_mul(final_interval);
466+
let total_aggregator = self.oracle.aggregator.wrapping_add(final_contribution);
467+
468+
Ok(total_aggregator / seconds_passed)
463469
}
464470
}
465471

programs/price_based_performance_package/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ pub enum PriceBasedPerformancePackageError {
3232
InvalidAdmin,
3333
#[msg("Total token amount calculation would overflow")]
3434
TotalTokenAmountOverflow,
35+
#[msg("Recipient and performance package authority must be different keys")]
36+
RecipientAuthorityMustDiffer,
3537
}

programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ pub struct ChangePerformancePackageAuthority<'info> {
2020
}
2121

2222
impl<'info> ChangePerformancePackageAuthority<'info> {
23+
pub fn validate(&self, params: &ChangePerformancePackageAuthorityParams) -> Result<()> {
24+
require_keys_neq!(
25+
params.new_performance_package_authority,
26+
self.performance_package.recipient,
27+
PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer
28+
);
29+
30+
Ok(())
31+
}
32+
2333
pub fn handle(
2434
ctx: Context<Self>,
2535
params: ChangePerformancePackageAuthorityParams,

programs/price_based_performance_package/src/instructions/execute_change.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ impl<'info> ExecuteChange<'info> {
4343
return Err(PriceBasedPerformancePackageError::UnauthorizedChangeRequest.into());
4444
}
4545

46+
if let ChangeType::Recipient { new_recipient } = &self.change_request.change_type {
47+
require_keys_neq!(
48+
*new_recipient,
49+
self.performance_package.performance_package_authority,
50+
PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer
51+
);
52+
}
53+
4654
Ok(())
4755
}
4856

programs/price_based_performance_package/src/instructions/initialize_performance_package.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ pub struct InitializePerformancePackage<'info> {
5656
}
5757

5858
impl InitializePerformancePackage<'_> {
59+
pub fn validate(&self, params: &InitializePerformancePackageParams) -> Result<()> {
60+
require_keys_neq!(
61+
params.grantee,
62+
params.performance_package_authority,
63+
PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer
64+
);
65+
66+
Ok(())
67+
}
68+
5969
pub fn handle(ctx: Context<Self>, params: InitializePerformancePackageParams) -> Result<()> {
6070
let Self {
6171
performance_package,

programs/price_based_performance_package/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ declare_id!("pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS");
3939
pub mod price_based_performance_package {
4040
use super::*;
4141

42+
#[access_control(ctx.accounts.validate(&params))]
4243
pub fn initialize_performance_package(
4344
ctx: Context<InitializePerformancePackage>,
4445
params: InitializePerformancePackageParams,
@@ -66,6 +67,7 @@ pub mod price_based_performance_package {
6667
ExecuteChange::handle(ctx)
6768
}
6869

70+
#[access_control(ctx.accounts.validate(&params))]
6971
pub fn change_performance_package_authority(
7072
ctx: Context<ChangePerformancePackageAuthority>,
7173
params: ChangePerformancePackageAuthorityParams,

0 commit comments

Comments
 (0)