From 896a4117662d64b223afb912f4c35535233a6324 Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 20 Feb 2026 00:47:15 +0100 Subject: [PATCH 1/9] Fix TWAP accumulator weighting and add virtual crank to get_twap --- .../futarchy/src/instructions/finalize_proposal.rs | 6 +++--- programs/futarchy/src/state/futarchy_amm.rs | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/programs/futarchy/src/instructions/finalize_proposal.rs b/programs/futarchy/src/instructions/finalize_proposal.rs index aeacfe146..84345f58b 100644 --- a/programs/futarchy/src/instructions/finalize_proposal.rs +++ b/programs/futarchy/src/instructions/finalize_proposal.rs @@ -115,6 +115,8 @@ impl FinalizeProposal<'_> { ]; let proposal_signer = &[&proposal_seeds[..]]; + let clock = Clock::get()?; + let calculate_twap = |amm: &Pool| -> Result { let seconds_passed = amm.oracle.last_updated_timestamp - proposal.timestamp_enqueued; @@ -124,7 +126,7 @@ impl FinalizeProposal<'_> { FutarchyError::MarketsTooYoung ); - amm.get_twap() + amm.get_twap(clock.unix_timestamp) }; let PoolState::Futarchy { @@ -269,8 +271,6 @@ impl FinalizeProposal<'_> { dao.seq_num += 1; - let clock = Clock::get()?; - emit_cpi!(FinalizeProposalEvent { common: CommonFields::new(&clock, dao.seq_num), proposal: proposal.key(), diff --git a/programs/futarchy/src/state/futarchy_amm.rs b/programs/futarchy/src/state/futarchy_amm.rs index 4dc6d7cd4..9344d9d75 100644 --- a/programs/futarchy/src/state/futarchy_amm.rs +++ b/programs/futarchy/src/state/futarchy_amm.rs @@ -406,7 +406,7 @@ impl Pool { // if this saturates, the aggregator will wrap back to 0, so this value doesn't // really matter. we just can't panic. - let weighted_observation = new_observation.saturating_mul(time_difference); + let weighted_observation = last_observation.saturating_mul(time_difference); oracle.aggregator.wrapping_add(weighted_observation) }; @@ -449,17 +449,23 @@ impl Pool { } /// Returns the time-weighted average price since market creation - pub fn get_twap(&self) -> Result { + pub fn get_twap(&self, current_timestamp: i64) -> Result { let start_timestamp = self.oracle.created_at_timestamp + self.oracle.start_delay_seconds as i64; require_gt!(self.oracle.last_updated_timestamp, start_timestamp); - let seconds_passed = (self.oracle.last_updated_timestamp - start_timestamp) as u128; + + let seconds_passed = (current_timestamp - start_timestamp) as u128; require_neq!(seconds_passed, 0); require_neq!(self.oracle.aggregator, 0); - Ok(self.oracle.aggregator / seconds_passed) + // include the final interval that hasn't been accumulated yet + let final_interval = (current_timestamp - self.oracle.last_updated_timestamp) as u128; + let final_contribution = self.oracle.last_observation.saturating_mul(final_interval); + let total_aggregator = self.oracle.aggregator.wrapping_add(final_contribution); + + Ok(total_aggregator / seconds_passed) } } From ebb8c7fc85c85310ffc5d948884220a549e6c8ae Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 20 Feb 2026 02:06:02 +0100 Subject: [PATCH 2/9] prevent same mint on dao init, adjust error --- programs/futarchy/src/error.rs | 4 ++- .../src/instructions/initialize_dao.rs | 9 ++++++ programs/futarchy/src/lib.rs | 1 + sdk/src/v0.7/types/futarchy.ts | 14 ++++++++-- tests/futarchy/unit/initializeDao.test.ts | 28 +++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/programs/futarchy/src/error.rs b/programs/futarchy/src/error.rs index e40223c50..150e72973 100644 --- a/programs/futarchy/src/error.rs +++ b/programs/futarchy/src/error.rs @@ -32,7 +32,7 @@ pub enum FutarchyError { PassThresholdTooHigh, #[msg("Question must have exactly 2 outcomes for binary futarchy")] QuestionMustBeBinary, - #[msg("Squads proposal must be in Draft status")] + #[msg("Squads proposal must be in Active status")] InvalidSquadsProposalStatus, #[msg("Casting overflow. If you're seeing this, please report this")] CastingOverflow, @@ -76,4 +76,6 @@ pub enum FutarchyError { InvalidTargetK, #[msg("Failed to compile transaction message for Squads vault transaction")] InvalidTransactionMessage, + #[msg("Base mint and quote mint must be different")] + InvalidMint, } diff --git a/programs/futarchy/src/instructions/initialize_dao.rs b/programs/futarchy/src/instructions/initialize_dao.rs index b271f51c0..5854bc7e5 100644 --- a/programs/futarchy/src/instructions/initialize_dao.rs +++ b/programs/futarchy/src/instructions/initialize_dao.rs @@ -70,6 +70,15 @@ pub mod permissionless_account { } impl InitializeDao<'_> { + pub fn validate(&self) -> Result<()> { + require_keys_neq!( + self.base_mint.key(), + self.quote_mint.key(), + FutarchyError::InvalidMint + ); + Ok(()) + } + pub fn handle(ctx: Context, params: InitializeDaoParams) -> Result<()> { let InitializeDaoParams { twap_initial_observation, diff --git a/programs/futarchy/src/lib.rs b/programs/futarchy/src/lib.rs index b060cb11e..642e9a7de 100644 --- a/programs/futarchy/src/lib.rs +++ b/programs/futarchy/src/lib.rs @@ -59,6 +59,7 @@ pub const DEFAULT_MAX_OBSERVATION_CHANGE_PER_UPDATE_LOTS: u64 = 5_000; pub mod futarchy { use super::*; + #[access_control(ctx.accounts.validate())] pub fn initialize_dao(ctx: Context, params: InitializeDaoParams) -> Result<()> { InitializeDao::handle(ctx, params) } diff --git a/sdk/src/v0.7/types/futarchy.ts b/sdk/src/v0.7/types/futarchy.ts index fc8d3ddd4..383868227 100644 --- a/sdk/src/v0.7/types/futarchy.ts +++ b/sdk/src/v0.7/types/futarchy.ts @@ -3146,7 +3146,7 @@ export type Futarchy = { { code: 6014; name: "InvalidSquadsProposalStatus"; - msg: "Squads proposal must be in Draft status"; + msg: "Squads proposal must be in Active status"; }, { code: 6015; @@ -3253,6 +3253,11 @@ export type Futarchy = { name: "InvalidTransactionMessage"; msg: "Failed to compile transaction message for Squads vault transaction"; }, + { + code: 6036; + name: "InvalidMint"; + msg: "Base mint and quote mint must be different"; + }, ]; }; @@ -6404,7 +6409,7 @@ export const IDL: Futarchy = { { code: 6014, name: "InvalidSquadsProposalStatus", - msg: "Squads proposal must be in Draft status", + msg: "Squads proposal must be in Active status", }, { code: 6015, @@ -6511,5 +6516,10 @@ export const IDL: Futarchy = { name: "InvalidTransactionMessage", msg: "Failed to compile transaction message for Squads vault transaction", }, + { + code: 6036, + name: "InvalidMint", + msg: "Base mint and quote mint must be different", + }, ], }; diff --git a/tests/futarchy/unit/initializeDao.test.ts b/tests/futarchy/unit/initializeDao.test.ts index 06948538b..5470340fc 100644 --- a/tests/futarchy/unit/initializeDao.test.ts +++ b/tests/futarchy/unit/initializeDao.test.ts @@ -177,6 +177,34 @@ export default function suite() { assert.equal(storedSpendingLimit.destinations.length, 0); }); + it("doesn't allow DAOs with identical base and quote mints", async function () { + const SAME_MINT = await this.createMint(this.payer.publicKey, 6); + + const callbacks = expectError( + "InvalidMint", + "DAO initialized despite base and quote mints being identical", + ); + + await this.futarchy + .initializeDaoIx({ + baseMint: SAME_MINT, + quoteMint: SAME_MINT, + params: { + secondsPerProposal: 60 * 60 * 24 * 3, + twapStartDelaySeconds: 60 * 60 * 24, + twapInitialObservation: THOUSAND_BUCK_PRICE, + twapMaxObservationChangePerUpdate: THOUSAND_BUCK_PRICE.divn(100), + minQuoteFutarchicLiquidity: new BN(1), + minBaseFutarchicLiquidity: new BN(1000), + passThresholdBps: 300, + nonce: new BN(9999), + initialSpendingLimit: null, + }, + }) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("doesn't allow DAOs with proposal duration less than TWAP start delay", async function () { const callbacks = expectError( "ProposalDurationTooShort", From cb2c17bdc952629625a2a82b299ea29a17b2b81e Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 20 Feb 2026 02:29:16 +0100 Subject: [PATCH 3/9] reject performance package init/change that would result in same authority and recipient --- .../src/error.rs | 2 + .../change_performance_package_authority.rs | 10 +++++ .../src/instructions/execute_change.rs | 8 ++++ .../initialize_performance_package.rs | 10 +++++ .../src/lib.rs | 2 + .../types/price_based_performance_package.ts | 10 +++++ .../changePerformancePackageAuthority.test.ts | 17 ++++++++ .../unit/executeChange.test.ts | 41 +++++++++++++++++++ .../unit/initializePerformancePackage.test.ts | 41 +++++++++++++++++++ 9 files changed, 141 insertions(+) diff --git a/programs/price_based_performance_package/src/error.rs b/programs/price_based_performance_package/src/error.rs index 236eaa2a3..da0e0bd26 100644 --- a/programs/price_based_performance_package/src/error.rs +++ b/programs/price_based_performance_package/src/error.rs @@ -32,4 +32,6 @@ pub enum PriceBasedPerformancePackageError { InvalidAdmin, #[msg("Total token amount calculation would overflow")] TotalTokenAmountOverflow, + #[msg("Recipient and performance package authority must be different keys")] + RecipientAuthorityMustDiffer, } diff --git a/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs b/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs index d2477788c..02c620b30 100644 --- a/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs +++ b/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs @@ -20,6 +20,16 @@ pub struct ChangePerformancePackageAuthority<'info> { } impl<'info> ChangePerformancePackageAuthority<'info> { + pub fn validate(&self, params: &ChangePerformancePackageAuthorityParams) -> Result<()> { + require_keys_neq!( + params.new_performance_package_authority, + self.performance_package.recipient, + PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer + ); + + Ok(()) + } + pub fn handle( ctx: Context, params: ChangePerformancePackageAuthorityParams, diff --git a/programs/price_based_performance_package/src/instructions/execute_change.rs b/programs/price_based_performance_package/src/instructions/execute_change.rs index 582da768e..258a65de8 100644 --- a/programs/price_based_performance_package/src/instructions/execute_change.rs +++ b/programs/price_based_performance_package/src/instructions/execute_change.rs @@ -43,6 +43,14 @@ impl<'info> ExecuteChange<'info> { return Err(PriceBasedPerformancePackageError::UnauthorizedChangeRequest.into()); } + if let ChangeType::Recipient { new_recipient } = &self.change_request.change_type { + require_keys_neq!( + *new_recipient, + self.performance_package.performance_package_authority, + PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer + ); + } + Ok(()) } diff --git a/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs b/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs index 4aee933ef..1f52ba6f2 100644 --- a/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs +++ b/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs @@ -56,6 +56,16 @@ pub struct InitializePerformancePackage<'info> { } impl InitializePerformancePackage<'_> { + pub fn validate(&self, params: &InitializePerformancePackageParams) -> Result<()> { + require_keys_neq!( + params.grantee, + params.performance_package_authority, + PriceBasedPerformancePackageError::RecipientAuthorityMustDiffer + ); + + Ok(()) + } + pub fn handle(ctx: Context, params: InitializePerformancePackageParams) -> Result<()> { let Self { performance_package, diff --git a/programs/price_based_performance_package/src/lib.rs b/programs/price_based_performance_package/src/lib.rs index d161b4c1c..bc4c938d6 100644 --- a/programs/price_based_performance_package/src/lib.rs +++ b/programs/price_based_performance_package/src/lib.rs @@ -39,6 +39,7 @@ declare_id!("pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS"); pub mod price_based_performance_package { use super::*; + #[access_control(ctx.accounts.validate(¶ms))] pub fn initialize_performance_package( ctx: Context, params: InitializePerformancePackageParams, @@ -66,6 +67,7 @@ pub mod price_based_performance_package { ExecuteChange::handle(ctx) } + #[access_control(ctx.accounts.validate(¶ms))] pub fn change_performance_package_authority( ctx: Context, params: ChangePerformancePackageAuthorityParams, diff --git a/sdk/src/v0.7/types/price_based_performance_package.ts b/sdk/src/v0.7/types/price_based_performance_package.ts index fe69a3267..70ffba3f9 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -971,6 +971,11 @@ export type PriceBasedPerformancePackage = { name: "TotalTokenAmountOverflow"; msg: "Total token amount calculation would overflow"; }, + { + code: 6015; + name: "RecipientAuthorityMustDiffer"; + msg: "Recipient and performance package authority must be different keys"; + }, ]; }; @@ -1947,5 +1952,10 @@ export const IDL: PriceBasedPerformancePackage = { name: "TotalTokenAmountOverflow", msg: "Total token amount calculation would overflow", }, + { + code: 6015, + name: "RecipientAuthorityMustDiffer", + msg: "Recipient and performance package authority must be different keys", + }, ], }; diff --git a/tests/priceBasedPerformancePackage/unit/changePerformancePackageAuthority.test.ts b/tests/priceBasedPerformancePackage/unit/changePerformancePackageAuthority.test.ts index aa7d15ec2..db1168a19 100644 --- a/tests/priceBasedPerformancePackage/unit/changePerformancePackageAuthority.test.ts +++ b/tests/priceBasedPerformancePackage/unit/changePerformancePackageAuthority.test.ts @@ -6,6 +6,7 @@ import { } from "@solana/web3.js"; import { assert } from "chai"; import BN from "bn.js"; +import { expectError } from "../../utils.js"; export default function () { let createKey: Keypair; @@ -90,6 +91,22 @@ export default function () { .rpc(); }); + it("should fail if new authority equals current recipient", async function () { + const callbacks = expectError( + "RecipientAuthorityMustDiffer", + "Recipient and performance package authority must be different keys", + ); + + await this.priceBasedPerformancePackage + .changePerformancePackageAuthorityIx({ + performancePackage, + currentAuthority: this.payer.publicKey, + newPerformancePackageAuthority: recipient.publicKey, + }) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("should fail if unauthorized party tries to change authority", async function () { const unauthorizedWallet = Keypair.generate(); diff --git a/tests/priceBasedPerformancePackage/unit/executeChange.test.ts b/tests/priceBasedPerformancePackage/unit/executeChange.test.ts index 5350fc453..42d8f0f03 100644 --- a/tests/priceBasedPerformancePackage/unit/executeChange.test.ts +++ b/tests/priceBasedPerformancePackage/unit/executeChange.test.ts @@ -306,6 +306,47 @@ export default function () { .rpc(); }); + it("should fail if new recipient equals current authority", async function () { + const pdaNonce = Math.floor(Math.random() * 1000000); + + // Recipient proposes changing recipient to authority's key (this.payer.publicKey) + await this.priceBasedPerformancePackage + .proposeChangeIx({ + params: { + changeType: { + recipient: { newRecipient: this.payer.publicKey }, + }, + pdaNonce, + }, + performancePackage, + proposer: recipient.publicKey, + }) + .signers([recipient]) + .rpc(); + + const changeRequestAddr = + this.priceBasedPerformancePackage.getChangeRequestAddress( + performancePackage, + recipient.publicKey, + pdaNonce, + ); + + const callbacks = expectError( + "RecipientAuthorityMustDiffer", + "Recipient and performance package authority must be different keys", + ); + + // Authority executes - should fail because new recipient == authority + await this.priceBasedPerformancePackage + .executeChangeIx({ + performancePackage, + changeRequest: changeRequestAddr, + executor: this.payer.publicKey, + }) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("should fail if wrong vault tries to execute", async function () { // Recipient proposes change (correct pattern) const pdaNonce = Math.floor(Math.random() * 1000000); diff --git a/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts b/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts index 1edc0ec0c..e162fecfc 100644 --- a/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts +++ b/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts @@ -244,6 +244,47 @@ export default function () { } }); + it("should fail if recipient equals authority", async function () { + const sameKeyCreateKey = Keypair.generate(); + + const params = { + tranches: [ + { + priceThreshold: new BN(1000000), + tokenAmount: new BN(100000), + }, + ], + grantee: this.payer.publicKey, + performancePackageAuthority: this.payer.publicKey, + minUnlockTimestamp: new BN( + Number((await this.context.banksClient.getClock()).unixTimestamp) + + 3600, + ), + oracleConfig: { + oracleAccount: oracleAccount.publicKey, + byteOffset: 0, + }, + twapLengthSeconds: new BN(86_400), + tokenRecipient: this.payer.publicKey, + }; + + const callbacks = expectError( + "RecipientAuthorityMustDiffer", + "Recipient and performance package authority must be different keys", + ); + + await this.priceBasedPerformancePackage + .initializePerformancePackageIx({ + params, + createKey: sameKeyCreateKey.publicKey, + tokenMint, + grantor: tokenAuthority.publicKey, + }) + .signers([sameKeyCreateKey, tokenAuthority]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("should fail if token amount is zero", async function () { const zeroCreateKey = Keypair.generate(); From 824570a6fa4cb6c1f0a00fb3c249e0a0cd8e5c7f Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 24 Feb 2026 16:56:43 +0100 Subject: [PATCH 4/9] ppv2 - check Futarchy oracle start delay --- .../src/state/performance_package.rs | 35 ++++++++++--------- .../unit/completeUnlock.test.ts | 12 ------- .../unit/startUnlock.test.ts | 12 +------ 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/programs/performance_package_v2/src/state/performance_package.rs b/programs/performance_package_v2/src/state/performance_package.rs index aa12dcf50..449f3c103 100644 --- a/programs/performance_package_v2/src/state/performance_package.rs +++ b/programs/performance_package_v2/src/state/performance_package.rs @@ -59,26 +59,27 @@ fn read_futarchy_aggregator( let dao = Dao::try_deserialize(&mut &dao_data[..]) .map_err(|_| PerformancePackageError::OracleParseError)?; - // Read the oracle data from the spot pool - let (aggregator, last_updated_timestamp, last_observation) = match &dao.amm.state { - PoolState::Spot { spot } => ( - spot.oracle.aggregator, - spot.oracle.last_updated_timestamp, - spot.oracle.last_observation, - ), - PoolState::Futarchy { spot, .. } => ( - spot.oracle.aggregator, - spot.oracle.last_updated_timestamp, - spot.oracle.last_observation, - ), + // Read the spot oracle + let oracle = match &dao.amm.state { + PoolState::Spot { spot } | PoolState::Futarchy { spot, .. } => &spot.oracle, }; - // Compute effective aggregator at current time by extrapolating - // from the last update using the last observation value + // Ensure the oracle's start delay has passed before reading the aggregator. + // During the delay period, the aggregator stays at zero while the observation + // tracks price changes. Reading it during or right after the delay would + // produce an artificially low effective aggregator, distorting the TWAP. let clock = Clock::get()?; - let time_since_update = clock.unix_timestamp.saturating_sub(last_updated_timestamp) as u128; - let effective_aggregator = - aggregator.wrapping_add(last_observation.saturating_mul(time_since_update)); + let twap_start_timestamp = oracle.created_at_timestamp + oracle.start_delay_seconds as i64; + require!( + clock.unix_timestamp >= twap_start_timestamp, + PerformancePackageError::OracleInvalidState + ); + let time_since_update = clock + .unix_timestamp + .saturating_sub(oracle.last_updated_timestamp) as u128; + let effective_aggregator = oracle + .aggregator + .wrapping_add(oracle.last_observation.saturating_mul(time_since_update)); Ok((effective_aggregator, clock.unix_timestamp)) } diff --git a/tests/performancePackageV2/unit/completeUnlock.test.ts b/tests/performancePackageV2/unit/completeUnlock.test.ts index a8edca147..a64a4adbc 100644 --- a/tests/performancePackageV2/unit/completeUnlock.test.ts +++ b/tests/performancePackageV2/unit/completeUnlock.test.ts @@ -599,9 +599,6 @@ export default function suite() { await createRecipientAta(this, mint, recipient.publicKey); - // Advance time so effective aggregator will be non-zero - await this.advanceBySeconds(10); - // === FIRST UNLOCK CYCLE === await ppClient .startUnlockIx({ @@ -1005,9 +1002,6 @@ export default function suite() { // Create recipient ATA await createRecipientAta(this, mint, recipient.publicKey); - // Advance time so the effective aggregator will be non-zero - await this.advanceBySeconds(10); - // Start unlock await ppClient .startUnlockIx({ @@ -1020,10 +1014,7 @@ export default function suite() { // Verify start snapshot was recorded let ppAccount = await ppClient.fetchPerformancePackage(performancePackage); - const startValue = - ppAccount.oracleReader.futarchyTwap.startValue.toString(); const startTime = ppAccount.oracleReader.futarchyTwap.startTime.toString(); - assert.notEqual(startValue, "0"); assert.notEqual(startTime, "0"); assert.equal(ppAccount.oracleReader.futarchyTwap.endValue.toString(), "0"); assert.equal(ppAccount.oracleReader.futarchyTwap.endTime.toString(), "0"); @@ -1104,9 +1095,6 @@ export default function suite() { // Create recipient ATA await createRecipientAta(this, mint, recipient.publicKey); - // Advance time so the effective aggregator will be non-zero - await this.advanceBySeconds(10); - // Start unlock await ppClient .startUnlockIx({ diff --git a/tests/performancePackageV2/unit/startUnlock.test.ts b/tests/performancePackageV2/unit/startUnlock.test.ts index fe9334559..1bd80882d 100644 --- a/tests/performancePackageV2/unit/startUnlock.test.ts +++ b/tests/performancePackageV2/unit/startUnlock.test.ts @@ -278,12 +278,6 @@ export default function suite() { ); assert.equal(ppAccount.oracleReader.futarchyTwap.startTime.toString(), "0"); - // Advance time so the effective aggregator will be non-zero - // The effective_aggregator = aggregator + last_observation * time_since_update - // After DAO init, aggregator is 0 but last_observation = twapInitialObservation - // We need time_since_update > 0 for effective_aggregator to be non-zero - await this.advanceBySeconds(10); - // Get current time before starting unlock const currentClock = await this.banksClient.getClock(); const currentTimestamp = Number(currentClock.unixTimestamp); @@ -304,11 +298,7 @@ export default function suite() { assert.isDefined(ppAccount.oracleReader.futarchyTwap); const futarchyTwap = ppAccount.oracleReader.futarchyTwap; - // start_value should be non-zero (the aggregator value from the DAO) - assert.notEqual(futarchyTwap.startValue.toString(), "0"); - // start_time should be close to current timestamp - const startTime = Number(futarchyTwap.startTime.toString()); - assert.isAtLeast(startTime, currentTimestamp); + assert.equal(Number(futarchyTwap.startTime.toString()), currentTimestamp); // end values should still be 0 (not recorded yet) assert.equal(futarchyTwap.endValue.toString(), "0"); assert.equal(futarchyTwap.endTime.toString(), "0"); From 8f4a2aa70e374d328863d35bf6e0488daee8c02f Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 24 Feb 2026 18:21:12 +0100 Subject: [PATCH 5/9] validate max min_duration size --- .../performance_package_v2/src/constants.rs | 3 + programs/performance_package_v2/src/error.rs | 17 +--- .../src/state/performance_package.rs | 7 +- sdk/src/v0.7/types/performance_package_v2.ts | 20 +++++ .../unit/initializePerformancePackage.test.ts | 89 +++++++++++++++++++ .../unit/proposeChange.test.ts | 45 ++++++++++ 6 files changed, 165 insertions(+), 16 deletions(-) diff --git a/programs/performance_package_v2/src/constants.rs b/programs/performance_package_v2/src/constants.rs index 1146d282e..871f1a05e 100644 --- a/programs/performance_package_v2/src/constants.rs +++ b/programs/performance_package_v2/src/constants.rs @@ -5,3 +5,6 @@ pub const CHANGE_REQUEST_SEED: &[u8] = b"change_request"; #[constant] pub const MAX_TRANCHES: usize = 10; + +#[constant] +pub const MAX_MIN_DURATION: u32 = 60 * 60 * 24 * 365; // 365 days in seconds diff --git a/programs/performance_package_v2/src/error.rs b/programs/performance_package_v2/src/error.rs index e6d170ca7..87075943e 100644 --- a/programs/performance_package_v2/src/error.rs +++ b/programs/performance_package_v2/src/error.rs @@ -2,7 +2,6 @@ use anchor_lang::prelude::*; #[error_code] pub enum PerformancePackageError { - // Authorization #[msg("Signer is neither authority nor recipient")] Unauthorized, #[msg("Executor is not the opposite party from proposer")] @@ -11,20 +10,14 @@ pub enum PerformancePackageError { InvalidAuthority, #[msg("Signer is not the admin")] InvalidAdmin, - - // Account validation #[msg("Mint governor does not match the provided mint")] InvalidMintGovernor, #[msg("Mint authority does not match expected configuration")] InvalidMintAuthority, - - // State #[msg("Expected Locked status")] NotLocked, #[msg("Expected Unlocking status")] NotUnlocking, - - // Oracle #[msg("Expected remaining_accounts not provided")] OracleMissingAccount, #[msg("Account pubkey doesn't match expected")] @@ -35,24 +28,18 @@ pub enum PerformancePackageError { OracleInvalidState, #[msg("Minimum duration hasn't passed yet")] OracleMinDurationNotReached, - - // Time #[msg("Minimum unlock timestamp not yet reached")] UnlockTimestampNotReached, - - // Rewards #[msg("Math overflow in reward function")] RewardCalculationOverflow, - - // Configuration #[msg("Tranches should be sorted and non-empty")] InvalidTranches, #[msg("Invalid vesting schedule configuration")] InvalidVestingSchedule, - - // Change Requests #[msg("Missing proposal for execute")] ChangeRequestNotFound, #[msg("All optional change fields are None")] NoChangesProposed, + #[msg("min_duration exceeds maximum allowed (365 days)")] + MinDurationTooLarge, } diff --git a/programs/performance_package_v2/src/state/performance_package.rs b/programs/performance_package_v2/src/state/performance_package.rs index 449f3c103..170f005ae 100644 --- a/programs/performance_package_v2/src/state/performance_package.rs +++ b/programs/performance_package_v2/src/state/performance_package.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use futarchy::state::{Dao, PoolState}; -use crate::{PerformancePackageError, MAX_TRANCHES}; +use crate::{PerformancePackageError, MAX_MIN_DURATION, MAX_TRANCHES}; /// Lifecycle state for the performance package. #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq, InitSpace)] @@ -98,6 +98,11 @@ impl OracleReader { min_duration > 0, PerformancePackageError::InvalidVestingSchedule ); + require_gte!( + MAX_MIN_DURATION, + min_duration, + PerformancePackageError::MinDurationTooLarge + ); Ok(()) } } diff --git a/sdk/src/v0.7/types/performance_package_v2.ts b/sdk/src/v0.7/types/performance_package_v2.ts index 9f9bc7fd2..70f7415ac 100644 --- a/sdk/src/v0.7/types/performance_package_v2.ts +++ b/sdk/src/v0.7/types/performance_package_v2.ts @@ -9,6 +9,11 @@ export type PerformancePackageV2 = { }; value: "10"; }, + { + name: "MAX_MIN_DURATION"; + type: "u32"; + value: "60 * 60 * 24 * 365"; + }, ]; instructions: [ { @@ -989,6 +994,11 @@ export type PerformancePackageV2 = { name: "NoChangesProposed"; msg: "All optional change fields are None"; }, + { + code: 6019; + name: "MinDurationTooLarge"; + msg: "min_duration exceeds maximum allowed (365 days)"; + }, ]; }; @@ -1003,6 +1013,11 @@ export const IDL: PerformancePackageV2 = { }, value: "10", }, + { + name: "MAX_MIN_DURATION", + type: "u32", + value: "60 * 60 * 24 * 365", + }, ], instructions: [ { @@ -1983,5 +1998,10 @@ export const IDL: PerformancePackageV2 = { name: "NoChangesProposed", msg: "All optional change fields are None", }, + { + code: 6019, + name: "MinDurationTooLarge", + msg: "min_duration exceeds maximum allowed (365 days)", + }, ], }; diff --git a/tests/performancePackageV2/unit/initializePerformancePackage.test.ts b/tests/performancePackageV2/unit/initializePerformancePackage.test.ts index 9307f13ee..29ae7714e 100644 --- a/tests/performancePackageV2/unit/initializePerformancePackage.test.ts +++ b/tests/performancePackageV2/unit/initializePerformancePackage.test.ts @@ -703,6 +703,95 @@ export default function suite() { .then(callbacks[0], callbacks[1]); }); + it("fails with excessive min_duration", async function () { + const dao = await setupDaoForTwapTests(this); + + const createKey = Keypair.generate(); + const [performancePackage] = getPerformancePackageV2Addr({ + createKey: createKey.publicKey, + }); + + const { mint, mintGovernor, mintAuthority } = + await setupMintGovernorWithAuthority( + this.banksClient, + mintGovernorClient, + this.payer, + performancePackage, + ); + + // 365 days + 1 second - exceeds maximum + const minDuration = 365 * 24 * 60 * 60 + 1; + const oracleReader = createFutarchyTwapOracle({ amm: dao, minDuration }); + const rewardFunction = createCliffLinearReward(); + + const callbacks = expectError( + "MinDurationTooLarge", + "Should have failed because min_duration exceeds maximum allowed", + ); + + await ppClient + .initializePerformancePackageIx({ + createKey: createKey.publicKey, + mint, + mintGovernor, + mintAuthority, + authority: this.payer.publicKey, + recipient: this.payer.publicKey, + payer: this.payer.publicKey, + oracleReader, + rewardFunction, + minUnlockTimestamp: new BN(0), + }) + .signers([createKey]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + + it("succeeds with max allowed min_duration", async function () { + const dao = await setupDaoForTwapTests(this); + + const createKey = Keypair.generate(); + const [performancePackage] = getPerformancePackageV2Addr({ + createKey: createKey.publicKey, + }); + + const { mint, mintGovernor, mintAuthority } = + await setupMintGovernorWithAuthority( + this.banksClient, + mintGovernorClient, + this.payer, + performancePackage, + ); + + // Exactly 365 days - should succeed + const minDuration = 365 * 24 * 60 * 60; + const oracleReader = createFutarchyTwapOracle({ amm: dao, minDuration }); + const rewardFunction = createCliffLinearReward(); + + await ppClient + .initializePerformancePackageIx({ + createKey: createKey.publicKey, + mint, + mintGovernor, + mintAuthority, + authority: this.payer.publicKey, + recipient: this.payer.publicKey, + payer: this.payer.publicKey, + oracleReader, + rewardFunction, + minUnlockTimestamp: new BN(0), + }) + .signers([createKey]) + .rpc(); + + const ppAccount = + await ppClient.fetchPerformancePackage(performancePackage); + + assert.isNotNull(ppAccount); + assert.isDefined(ppAccount.oracleReader.futarchyTwap); + assert.equal(ppAccount.oracleReader.futarchyTwap.minDuration, minDuration); + }); + it("fails with invalid CliffLinear config - start_value > cliff_value", async function () { const createKey = Keypair.generate(); const [performancePackage] = getPerformancePackageV2Addr({ diff --git a/tests/performancePackageV2/unit/proposeChange.test.ts b/tests/performancePackageV2/unit/proposeChange.test.ts index 9f178a521..09ef04aae 100644 --- a/tests/performancePackageV2/unit/proposeChange.test.ts +++ b/tests/performancePackageV2/unit/proposeChange.test.ts @@ -9,6 +9,8 @@ import { setupPerformancePackageV2, createCliffLinearReward, createThresholdReward, + createFutarchyTwapOracle, + setupDaoForTwapTests, } from "../utils.js"; import { expectError } from "../../utils.js"; @@ -434,6 +436,49 @@ export default function suite() { .then(callbacks[0], callbacks[1]); }); + it("fails when proposing oracle change with excessive min_duration", async function () { + const authority = Keypair.generate(); + const recipient = Keypair.generate(); + + const { performancePackage } = await setupPerformancePackageV2( + this.banksClient, + mintGovernorClient, + ppClient, + this.payer, + { + authority: authority.publicKey, + recipient: recipient.publicKey, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }, + ); + + const dao = await setupDaoForTwapTests(this); + + // 365 days + 1 second - exceeds maximum + const minDuration = 365 * 24 * 60 * 60 + 1; + const newOracleReader = createFutarchyTwapOracle({ amm: dao, minDuration }); + + const pdaNonce = 1; + + const callbacks = expectError( + "MinDurationTooLarge", + "Should have failed because proposed oracle min_duration exceeds maximum allowed", + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: authority.publicKey, + payer: this.payer.publicKey, + pdaNonce, + newOracleReader, + }) + .signers([authority]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("fails when signer is neither authority nor recipient", async function () { const authority = Keypair.generate(); const recipient = Keypair.generate(); From a35dfe107165ae76c058e65cf9cf7bc9186342be Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 24 Feb 2026 18:27:33 +0100 Subject: [PATCH 6/9] use emit_cpi instead of emit --- .../src/instructions/add_mint_authority.rs | 3 +- .../instructions/initialize_mint_governor.rs | 3 +- .../src/instructions/mint_tokens.rs | 3 +- .../src/instructions/reclaim_authority.rs | 3 +- .../src/instructions/remove_mint_authority.rs | 3 +- .../transfer_authority_to_governor.rs | 3 +- .../src/instructions/update_mint_authority.rs | 3 +- .../update_mint_governor_admin.rs | 3 +- .../src/instructions/change_authority.rs | 3 +- .../instructions/close_performance_package.rs | 3 +- .../src/instructions/complete_unlock.rs | 8 +- .../src/instructions/execute_change.rs | 3 +- .../initialize_performance_package.rs | 3 +- .../src/instructions/propose_change.rs | 3 +- .../src/instructions/start_unlock.rs | 3 +- .../src/instructions/propose_change.rs | 2 +- sdk/src/v0.7/PerformancePackageV2Client.ts | 6 + sdk/src/v0.7/types/mint_governor.ts | 160 ++++++++++++++++++ sdk/src/v0.7/types/performance_package_v2.ts | 150 ++++++++++++++++ 19 files changed, 352 insertions(+), 16 deletions(-) diff --git a/programs/mint_governor/src/instructions/add_mint_authority.rs b/programs/mint_governor/src/instructions/add_mint_authority.rs index 91e08e964..24eeb52a4 100644 --- a/programs/mint_governor/src/instructions/add_mint_authority.rs +++ b/programs/mint_governor/src/instructions/add_mint_authority.rs @@ -10,6 +10,7 @@ pub struct AddMintAuthorityArgs { pub max_total: Option, } +#[event_cpi] #[derive(Accounts)] pub struct AddMintAuthority<'info> { #[account(mut)] @@ -57,7 +58,7 @@ impl AddMintAuthority<'_> { let clock = Clock::get()?; - emit!(MintAuthorityAddedEvent { + emit_cpi!(MintAuthorityAddedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/initialize_mint_governor.rs b/programs/mint_governor/src/instructions/initialize_mint_governor.rs index 63461dd8c..c2f49cdd8 100644 --- a/programs/mint_governor/src/instructions/initialize_mint_governor.rs +++ b/programs/mint_governor/src/instructions/initialize_mint_governor.rs @@ -3,6 +3,7 @@ use anchor_spl::token::Mint; use crate::{CommonFields, MintGovernor, MintGovernorInitializedEvent, MINT_GOVERNOR_SEED}; +#[event_cpi] #[derive(Accounts)] pub struct InitializeMintGovernor<'info> { pub mint: Account<'info, Mint>, @@ -44,7 +45,7 @@ impl InitializeMintGovernor<'_> { let clock = Clock::get()?; let mint_governor = &ctx.accounts.mint_governor; - emit!(MintGovernorInitializedEvent { + emit_cpi!(MintGovernorInitializedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/mint_tokens.rs b/programs/mint_governor/src/instructions/mint_tokens.rs index 29c882c61..01cdf0d96 100644 --- a/programs/mint_governor/src/instructions/mint_tokens.rs +++ b/programs/mint_governor/src/instructions/mint_tokens.rs @@ -11,6 +11,7 @@ pub struct MintTokensArgs { pub amount: u64, } +#[event_cpi] #[derive(Accounts)] pub struct MintTokens<'info> { #[account(mut)] @@ -93,7 +94,7 @@ impl MintTokens<'_> { // Reload mint to get post-mint supply ctx.accounts.mint.reload()?; - emit!(TokensMintedEvent { + emit_cpi!(TokensMintedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/reclaim_authority.rs b/programs/mint_governor/src/instructions/reclaim_authority.rs index db0c18a00..e8f81430f 100644 --- a/programs/mint_governor/src/instructions/reclaim_authority.rs +++ b/programs/mint_governor/src/instructions/reclaim_authority.rs @@ -6,6 +6,7 @@ use crate::{ CommonFields, MintAuthorityReclaimedEvent, MintGovernor, MintGovernorError, MINT_GOVERNOR_SEED, }; +#[event_cpi] #[derive(Accounts)] pub struct ReclaimAuthority<'info> { #[account(mut)] @@ -68,7 +69,7 @@ impl ReclaimAuthority<'_> { // Emit event let clock = Clock::get()?; - emit!(MintAuthorityReclaimedEvent { + emit_cpi!(MintAuthorityReclaimedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/remove_mint_authority.rs b/programs/mint_governor/src/instructions/remove_mint_authority.rs index cde94789b..bcb0cebe5 100644 --- a/programs/mint_governor/src/instructions/remove_mint_authority.rs +++ b/programs/mint_governor/src/instructions/remove_mint_authority.rs @@ -4,6 +4,7 @@ use crate::{ CommonFields, MintAuthority, MintAuthorityRemovedEvent, MintGovernor, MintGovernorError, }; +#[event_cpi] #[derive(Accounts)] pub struct RemoveMintAuthority<'info> { #[account(mut)] @@ -39,7 +40,7 @@ impl RemoveMintAuthority<'_> { // Emit event let clock = Clock::get()?; - emit!(MintAuthorityRemovedEvent { + emit_cpi!(MintAuthorityRemovedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/transfer_authority_to_governor.rs b/programs/mint_governor/src/instructions/transfer_authority_to_governor.rs index f62719f6b..35ce81449 100644 --- a/programs/mint_governor/src/instructions/transfer_authority_to_governor.rs +++ b/programs/mint_governor/src/instructions/transfer_authority_to_governor.rs @@ -4,6 +4,7 @@ use anchor_spl::token::{self, Mint, SetAuthority, Token}; use crate::{CommonFields, MintAuthorityTransferredEvent, MintGovernor, MintGovernorError}; +#[event_cpi] #[derive(Accounts)] pub struct TransferAuthorityToGovernor<'info> { #[account(mut)] @@ -47,7 +48,7 @@ impl TransferAuthorityToGovernor<'_> { // Emit event let clock = Clock::get()?; - emit!(MintAuthorityTransferredEvent { + emit_cpi!(MintAuthorityTransferredEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/update_mint_authority.rs b/programs/mint_governor/src/instructions/update_mint_authority.rs index 72960a28c..df1d1ca5f 100644 --- a/programs/mint_governor/src/instructions/update_mint_authority.rs +++ b/programs/mint_governor/src/instructions/update_mint_authority.rs @@ -9,6 +9,7 @@ pub struct UpdateMintAuthorityArgs { pub max_total: Option, } +#[event_cpi] #[derive(Accounts)] pub struct UpdateMintAuthority<'info> { #[account(mut)] @@ -42,7 +43,7 @@ impl UpdateMintAuthority<'_> { // Emit event let clock = Clock::get()?; - emit!(MintAuthorityUpdatedEvent { + emit_cpi!(MintAuthorityUpdatedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/mint_governor/src/instructions/update_mint_governor_admin.rs b/programs/mint_governor/src/instructions/update_mint_governor_admin.rs index 561525aaf..2d16288c4 100644 --- a/programs/mint_governor/src/instructions/update_mint_governor_admin.rs +++ b/programs/mint_governor/src/instructions/update_mint_governor_admin.rs @@ -2,6 +2,7 @@ use anchor_lang::prelude::*; use crate::{CommonFields, MintGovernor, MintGovernorAdminUpdatedEvent, MintGovernorError}; +#[event_cpi] #[derive(Accounts)] pub struct UpdateMintGovernorAdmin<'info> { #[account(mut)] @@ -27,7 +28,7 @@ impl UpdateMintGovernorAdmin<'_> { let clock = Clock::get()?; - emit!(MintGovernorAdminUpdatedEvent { + emit_cpi!(MintGovernorAdminUpdatedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/change_authority.rs b/programs/performance_package_v2/src/instructions/change_authority.rs index a7265b68c..80c558f39 100644 --- a/programs/performance_package_v2/src/instructions/change_authority.rs +++ b/programs/performance_package_v2/src/instructions/change_authority.rs @@ -5,6 +5,7 @@ use crate::{ PERFORMANCE_PACKAGE_SEED, }; +#[event_cpi] #[derive(Accounts)] pub struct ChangeAuthority<'info> { #[account( @@ -37,7 +38,7 @@ impl ChangeAuthority<'_> { let clock = Clock::get()?; - emit!(AuthorityChangedEvent { + emit_cpi!(AuthorityChangedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/close_performance_package.rs b/programs/performance_package_v2/src/instructions/close_performance_package.rs index 98e645f05..9c885f44d 100644 --- a/programs/performance_package_v2/src/instructions/close_performance_package.rs +++ b/programs/performance_package_v2/src/instructions/close_performance_package.rs @@ -12,6 +12,7 @@ pub mod admin { declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf"); } +#[event_cpi] #[derive(Accounts)] pub struct ClosePerformancePackage<'info> { #[account( @@ -51,7 +52,7 @@ impl ClosePerformancePackage<'_> { let pp = &ctx.accounts.performance_package; let clock = Clock::get()?; - emit!(PerformancePackageClosedEvent { + emit_cpi!(PerformancePackageClosedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/complete_unlock.rs b/programs/performance_package_v2/src/instructions/complete_unlock.rs index 41ca42aec..209cea06c 100644 --- a/programs/performance_package_v2/src/instructions/complete_unlock.rs +++ b/programs/performance_package_v2/src/instructions/complete_unlock.rs @@ -15,6 +15,7 @@ use crate::{ PERFORMANCE_PACKAGE_SEED, }; +#[event_cpi] #[derive(Accounts)] pub struct CompleteUnlock<'info> { #[account( @@ -56,6 +57,9 @@ pub struct CompleteUnlock<'info> { pub associated_token_program: Program<'info, AssociatedToken>, pub mint_governor_program: Program<'info, MintGovernorProgram>, + + /// CHECK: checked by mint_governor program + pub mint_governor_event_authority: UncheckedAccount<'info>, } impl CompleteUnlock<'_> { @@ -120,6 +124,8 @@ impl CompleteUnlock<'_> { destination_ata: ctx.accounts.recipient_ata.to_account_info(), authorized_minter: ctx.accounts.performance_package.to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), + event_authority: ctx.accounts.mint_governor_event_authority.to_account_info(), + program: ctx.accounts.mint_governor_program.to_account_info(), }, signer_seeds, ); @@ -151,7 +157,7 @@ impl CompleteUnlock<'_> { let clock = Clock::get()?; - emit!(UnlockCompletedEvent { + emit_cpi!(UnlockCompletedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/execute_change.rs b/programs/performance_package_v2/src/instructions/execute_change.rs index 44b5b5944..7163ef79f 100644 --- a/programs/performance_package_v2/src/instructions/execute_change.rs +++ b/programs/performance_package_v2/src/instructions/execute_change.rs @@ -5,6 +5,7 @@ use crate::{ PerformancePackageError, ProposerType, }; +#[event_cpi] #[derive(Accounts)] pub struct ExecuteChange<'info> { #[account(mut)] @@ -92,7 +93,7 @@ impl ExecuteChange<'_> { let clock = Clock::get()?; - emit!(ChangeExecutedEvent { + emit_cpi!(ChangeExecutedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/initialize_performance_package.rs b/programs/performance_package_v2/src/instructions/initialize_performance_package.rs index 21544dc98..5b24451f7 100644 --- a/programs/performance_package_v2/src/instructions/initialize_performance_package.rs +++ b/programs/performance_package_v2/src/instructions/initialize_performance_package.rs @@ -14,6 +14,7 @@ pub struct InitializePerformancePackageArgs { pub min_unlock_timestamp: i64, } +#[event_cpi] #[derive(Accounts)] pub struct InitializePerformancePackage<'info> { #[account( @@ -81,7 +82,7 @@ impl InitializePerformancePackage<'_> { let clock = Clock::get()?; let pp = &ctx.accounts.performance_package; - emit!(PerformancePackageCreatedEvent { + emit_cpi!(PerformancePackageCreatedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/propose_change.rs b/programs/performance_package_v2/src/instructions/propose_change.rs index f6158eef5..8a6f80caf 100644 --- a/programs/performance_package_v2/src/instructions/propose_change.rs +++ b/programs/performance_package_v2/src/instructions/propose_change.rs @@ -13,6 +13,7 @@ pub struct ProposeChangeArgs { pub new_reward_function: Option, } +#[event_cpi] #[derive(Accounts)] #[instruction(args: ProposeChangeArgs)] pub struct ProposeChange<'info> { @@ -102,7 +103,7 @@ impl ProposeChange<'_> { // Increment seq_num for event tracking pp.seq_num += 1; - emit!(ChangeProposedEvent { + emit_cpi!(ChangeProposedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/performance_package_v2/src/instructions/start_unlock.rs b/programs/performance_package_v2/src/instructions/start_unlock.rs index 288e86f06..d8813eb6c 100644 --- a/programs/performance_package_v2/src/instructions/start_unlock.rs +++ b/programs/performance_package_v2/src/instructions/start_unlock.rs @@ -5,6 +5,7 @@ use crate::{ PERFORMANCE_PACKAGE_SEED, }; +#[event_cpi] #[derive(Accounts)] pub struct StartUnlock<'info> { #[account( @@ -58,7 +59,7 @@ impl StartUnlock<'_> { let clock = Clock::get()?; - emit!(UnlockStartedEvent { + emit_cpi!(UnlockStartedEvent { common: CommonFields { slot: clock.slot, unix_timestamp: clock.unix_timestamp, diff --git a/programs/price_based_performance_package/src/instructions/propose_change.rs b/programs/price_based_performance_package/src/instructions/propose_change.rs index 1c4a5f0b2..2430dad21 100644 --- a/programs/price_based_performance_package/src/instructions/propose_change.rs +++ b/programs/price_based_performance_package/src/instructions/propose_change.rs @@ -95,7 +95,7 @@ impl<'info> ProposeChange<'info> { performance_package.seq_num += 1; // Emit event - emit!(ChangeProposed { + emit_cpi!(ChangeProposed { common: CommonFields::new(&clock, performance_package.seq_num), locker: performance_package.key(), change_request: change_request.key(), diff --git a/sdk/src/v0.7/PerformancePackageV2Client.ts b/sdk/src/v0.7/PerformancePackageV2Client.ts index 0e807c702..9eec8f7a7 100644 --- a/sdk/src/v0.7/PerformancePackageV2Client.ts +++ b/sdk/src/v0.7/PerformancePackageV2Client.ts @@ -13,6 +13,7 @@ import { import { getPerformancePackageV2Addr, getChangeRequestV2Addr, + getEventAuthorityAddr, } from "./utils/pda.js"; import { PerformancePackageV2 as PerformancePackageV2Program, @@ -193,6 +194,10 @@ export class PerformancePackageV2Client { }) { const recipientAta = getAssociatedTokenAddressSync(mint, recipient, true); + const [mintGovernorEventAuthority] = getEventAuthorityAddr( + MINT_GOVERNOR_PROGRAM_ID, + ); + const builder = this.program.methods.completeUnlock().accounts({ performancePackage, mintGovernor, @@ -203,6 +208,7 @@ export class PerformancePackageV2Client { tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, mintGovernorProgram: MINT_GOVERNOR_PROGRAM_ID, + mintGovernorEventAuthority, }); if (dao) { diff --git a/sdk/src/v0.7/types/mint_governor.ts b/sdk/src/v0.7/types/mint_governor.ts index b6107843a..b2d9b02c2 100644 --- a/sdk/src/v0.7/types/mint_governor.ts +++ b/sdk/src/v0.7/types/mint_governor.ts @@ -35,6 +35,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -61,6 +71,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -97,6 +117,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -140,6 +170,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -168,6 +208,16 @@ export type MintGovernor = { isMut: false; isSigner: true; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -201,6 +251,16 @@ export type MintGovernor = { isMut: true; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -222,6 +282,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -253,6 +323,16 @@ export type MintGovernor = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -692,6 +772,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -718,6 +808,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -754,6 +854,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -797,6 +907,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -825,6 +945,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: true, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -858,6 +988,16 @@ export const IDL: MintGovernor = { isMut: true, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -879,6 +1019,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -910,6 +1060,16 @@ export const IDL: MintGovernor = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, diff --git a/sdk/src/v0.7/types/performance_package_v2.ts b/sdk/src/v0.7/types/performance_package_v2.ts index 70f7415ac..e246f6498 100644 --- a/sdk/src/v0.7/types/performance_package_v2.ts +++ b/sdk/src/v0.7/types/performance_package_v2.ts @@ -64,6 +64,16 @@ export type PerformancePackageV2 = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -87,6 +97,16 @@ export type PerformancePackageV2 = { isMut: false; isSigner: true; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -138,6 +158,21 @@ export type PerformancePackageV2 = { isMut: false; isSigner: false; }, + { + name: "mintGovernorEventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -161,6 +196,16 @@ export type PerformancePackageV2 = { isSigner: false; docs: ["The new authority address"]; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -192,6 +237,16 @@ export type PerformancePackageV2 = { isMut: false; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -225,6 +280,16 @@ export type PerformancePackageV2 = { isMut: true; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -246,6 +311,16 @@ export type PerformancePackageV2 = { isMut: true; isSigner: false; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -1068,6 +1143,16 @@ export const IDL: PerformancePackageV2 = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -1091,6 +1176,16 @@ export const IDL: PerformancePackageV2 = { isMut: false, isSigner: true, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -1142,6 +1237,21 @@ export const IDL: PerformancePackageV2 = { isMut: false, isSigner: false, }, + { + name: "mintGovernorEventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -1165,6 +1275,16 @@ export const IDL: PerformancePackageV2 = { isSigner: false, docs: ["The new authority address"], }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -1196,6 +1316,16 @@ export const IDL: PerformancePackageV2 = { isMut: false, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -1229,6 +1359,16 @@ export const IDL: PerformancePackageV2 = { isMut: true, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -1250,6 +1390,16 @@ export const IDL: PerformancePackageV2 = { isMut: true, isSigner: false, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, From 5962fcefd726d8c563a0a0ac01183847b267b239 Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 24 Feb 2026 18:41:44 +0100 Subject: [PATCH 7/9] can't end before record_start has been called at least once --- .../performance_package_v2/src/state/performance_package.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/performance_package_v2/src/state/performance_package.rs b/programs/performance_package_v2/src/state/performance_package.rs index 170f005ae..df8a0aaa1 100644 --- a/programs/performance_package_v2/src/state/performance_package.rs +++ b/programs/performance_package_v2/src/state/performance_package.rs @@ -168,6 +168,10 @@ impl OracleReader { start_time, .. } => { + // record_start() has not been called yet + if *start_time == 0 { + return false; + } let clock = Clock::get(); match clock { Ok(clock) => { From 6d1b7d664713349ee6aeaccff32f58128e6a8554 Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 27 Feb 2026 16:52:30 +0100 Subject: [PATCH 8/9] add 50k CUs to v6 finalize launch --- sdk/src/v0.6/LaunchpadClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/v0.6/LaunchpadClient.ts b/sdk/src/v0.6/LaunchpadClient.ts index c1e8def39..3a7423820 100644 --- a/sdk/src/v0.6/LaunchpadClient.ts +++ b/sdk/src/v0.6/LaunchpadClient.ts @@ -480,7 +480,7 @@ export class LaunchpadClient { // poolCreatorAuthority, }) .preInstructions([ - ComputeBudgetProgram.setComputeUnitLimit({ units: 800_000 }), + ComputeBudgetProgram.setComputeUnitLimit({ units: 850_000 }), ComputeBudgetProgram.requestHeapFrame({ bytes: 255 * 1024 }), ]); } From f6b21d219c4987bbc9a4801c5a4be0ca81a4f3c6 Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 27 Feb 2026 22:47:33 +0100 Subject: [PATCH 9/9] allow only recipient to start unlocks --- .../src/instructions/start_unlock.rs | 7 +- .../unit/completeUnlock.test.ts | 84 +++++++++---------- .../unit/executeChange.test.ts | 8 +- .../unit/startUnlock.test.ts | 41 ++++----- 4 files changed, 71 insertions(+), 69 deletions(-) diff --git a/programs/performance_package_v2/src/instructions/start_unlock.rs b/programs/performance_package_v2/src/instructions/start_unlock.rs index d8813eb6c..f20db405d 100644 --- a/programs/performance_package_v2/src/instructions/start_unlock.rs +++ b/programs/performance_package_v2/src/instructions/start_unlock.rs @@ -22,9 +22,10 @@ impl StartUnlock<'_> { pub fn validate(&self) -> Result<()> { let pp = &self.performance_package; - // Signer must be authority or recipient - require!( - self.signer.key() == pp.authority || self.signer.key() == pp.recipient, + // Only the recipient can start an unlock + require_keys_eq!( + self.signer.key(), + pp.recipient, PerformancePackageError::Unauthorized ); diff --git a/tests/performancePackageV2/unit/completeUnlock.test.ts b/tests/performancePackageV2/unit/completeUnlock.test.ts index a64a4adbc..b22e461ea 100644 --- a/tests/performancePackageV2/unit/completeUnlock.test.ts +++ b/tests/performancePackageV2/unit/completeUnlock.test.ts @@ -94,9 +94,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Advance time by 500 seconds (halfway between cliff and end) @@ -179,9 +179,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Advance time by 150 seconds (should hit second threshold) @@ -254,9 +254,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -284,9 +284,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -346,9 +346,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -404,9 +404,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -472,9 +472,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -521,9 +521,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -603,10 +603,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Record the start snapshot for later manipulation @@ -647,10 +647,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Get the start snapshot value for second cycle @@ -763,9 +763,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -791,9 +791,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Advance just a tiny bit (not enough to hit second threshold) @@ -866,9 +866,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -900,9 +900,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); ppAccount = await ppClient.fetchPerformancePackage(performancePackage); @@ -934,9 +934,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); await ppClient @@ -1006,10 +1006,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify start snapshot was recorded @@ -1099,10 +1099,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Advance time past min_duration @@ -1187,10 +1187,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Advance time, but NOT past min_duration (only 10 seconds instead of 3600) @@ -1311,9 +1311,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Try to complete unlock with an unauthorized signer @@ -1416,9 +1416,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Try to complete unlock with wrong mint - should fail @@ -1489,9 +1489,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Try to complete unlock with wrong mint governor - should fail diff --git a/tests/performancePackageV2/unit/executeChange.test.ts b/tests/performancePackageV2/unit/executeChange.test.ts index faaabb7dd..2745fba09 100644 --- a/tests/performancePackageV2/unit/executeChange.test.ts +++ b/tests/performancePackageV2/unit/executeChange.test.ts @@ -578,12 +578,12 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) .postInstructions([ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), ]) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify status is Unlocking @@ -653,12 +653,12 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) .postInstructions([ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), ]) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify status is Unlocking diff --git a/tests/performancePackageV2/unit/startUnlock.test.ts b/tests/performancePackageV2/unit/startUnlock.test.ts index 1bd80882d..753ad53bd 100644 --- a/tests/performancePackageV2/unit/startUnlock.test.ts +++ b/tests/performancePackageV2/unit/startUnlock.test.ts @@ -24,7 +24,7 @@ export default function suite() { ppClient = this.performancePackageV2; }); - it("successfully starts when called by authority", async function () { + it("fails when called by authority", async function () { const authority = Keypair.generate(); const recipient = Keypair.generate(); @@ -45,19 +45,20 @@ export default function suite() { let ppAccount = await ppClient.fetchPerformancePackage(performancePackage); assert.isDefined(ppAccount.status.locked); - // Call start_unlock as authority + // Call start_unlock as authority - should fail + const callbacks = expectError( + "Unauthorized", + "Should have failed because only recipient can start unlock", + ); + await ppClient .startUnlockIx({ performancePackage, signer: authority.publicKey, }) .signers([authority]) - .rpc(); - - // Verify status is now Unlocking - ppAccount = await ppClient.fetchPerformancePackage(performancePackage); - assert.isDefined(ppAccount.status.unlocking); - assert.equal(ppAccount.seqNum.toString(), "1"); + .rpc() + .then(callbacks[0], callbacks[1]); }); it("successfully starts when called by recipient", async function () { @@ -117,9 +118,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify status is now Unlocking @@ -137,12 +138,12 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) .postInstructions([ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), ]) - .signers([authority]) + .signers([recipient]) .rpc() .then(callbacks[0], callbacks[1]); }); @@ -180,9 +181,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc() .then(callbacks[0], callbacks[1]); }); @@ -218,9 +219,9 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify status is now Unlocking @@ -286,10 +287,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao, }) - .signers([authority]) + .signers([recipient]) .rpc(); // Verify start snapshot was recorded @@ -357,10 +358,10 @@ export default function suite() { await ppClient .startUnlockIx({ performancePackage, - signer: authority.publicKey, + signer: recipient.publicKey, dao: wrongDao, // Wrong DAO! }) - .signers([authority]) + .signers([recipient]) .rpc() .then(callbacks[0], callbacks[1]); });