Skip to content

Commit 3538990

Browse files
authored
Add funding record accumulator (#431)
* funding record accumulator with configurable delay * add funding record and launch resize ixs/scripts * update SDK
1 parent 31a6985 commit 3538990

19 files changed

Lines changed: 1531 additions & 7 deletions

Anchor.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ v07-claim-launch-additional-tokens = "yarn run tsx scripts/v0.7/claimLaunchAddit
6464
v07-remove-proposal = "yarn run tsx scripts/v0.7/removeProposal.ts"
6565
v07-audit-liquidity-position-authorities = "yarn run tsx scripts/v0.7/auditLiquidityPositionAuthorities.ts"
6666
v07-fix-position-authorities = "yarn run tsx scripts/v0.7/fixPositionAuthorities.ts"
67+
v07-dump-launches-funding-records = "yarn run tsx scripts/v0.7/dumpLaunchesAndFundingRecords.ts"
68+
v07-resize-launches-funding-records = "yarn run tsx scripts/v0.7/resizeLaunchesAndFundingRecords.ts"
6769

6870
[test]
6971
startup_wait = 5000

programs/v07_launchpad/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,6 @@ pub enum LaunchpadError {
6262
PerformancePackageAlreadyInitialized,
6363
#[msg("Invalid DAO")]
6464
InvalidDao,
65+
#[msg("Accumulator activation delay must be less than the launch duration")]
66+
InvalidAccumulatorActivationDelaySeconds,
6567
}

programs/v07_launchpad/src/events.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub struct LaunchInitializedEvent {
3939
pub seconds_for_launch: u32,
4040
pub additional_tokens_amount: u64,
4141
pub additional_tokens_recipient: Option<Pubkey>,
42+
pub accumulator_activation_delay_seconds: u32,
4243
}
4344

4445
#[event]
@@ -58,6 +59,7 @@ pub struct LaunchFundedEvent {
5859
pub amount: u64,
5960
pub total_committed_by_funder: u64,
6061
pub total_committed: u64,
62+
pub committed_amount_accumulator: u128,
6163
}
6264

6365
#[event]

programs/v07_launchpad/src/instructions/fund.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,23 @@ impl Fund<'_> {
8686
)?;
8787

8888
let funding_record = &mut ctx.accounts.funding_record;
89+
let clock = Clock::get()?;
8990

9091
if funding_record.funder == ctx.accounts.funder.key() {
92+
// Existing funding record — flush accumulator before changing committed_amount
93+
let activation_timestamp = ctx.accounts.launch.unix_timestamp_started.unwrap()
94+
+ ctx.accounts.launch.accumulator_activation_delay_seconds as i64;
95+
let now = clock.unix_timestamp;
96+
97+
if funding_record.last_accumulator_update > 0 && now > activation_timestamp {
98+
let period_start =
99+
std::cmp::max(funding_record.last_accumulator_update, activation_timestamp);
100+
let elapsed = now - period_start;
101+
funding_record.committed_amount_accumulator +=
102+
(funding_record.committed_amount as u128) * (elapsed as u128);
103+
}
104+
105+
funding_record.last_accumulator_update = now;
91106
funding_record.committed_amount += amount;
92107
} else {
93108
funding_record.set_inner(FundingRecord {
@@ -98,6 +113,8 @@ impl Fund<'_> {
98113
is_tokens_claimed: false,
99114
is_usdc_refunded: false,
100115
approved_amount: 0,
116+
committed_amount_accumulator: 0,
117+
last_accumulator_update: clock.unix_timestamp,
101118
});
102119
}
103120

@@ -106,7 +123,6 @@ impl Fund<'_> {
106123

107124
ctx.accounts.launch.seq_num += 1;
108125

109-
let clock = Clock::get()?;
110126
emit_cpi!(LaunchFundedEvent {
111127
common: CommonFields::new(&clock, ctx.accounts.launch.seq_num),
112128
launch: ctx.accounts.launch.key(),
@@ -115,6 +131,7 @@ impl Fund<'_> {
115131
total_committed: ctx.accounts.launch.total_committed_amount,
116132
funding_record: funding_record.key(),
117133
total_committed_by_funder: funding_record.committed_amount,
134+
committed_amount_accumulator: funding_record.committed_amount_accumulator,
118135
});
119136

120137
Ok(())

programs/v07_launchpad/src/instructions/initialize_launch.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct InitializeLaunchArgs {
3030
pub months_until_insiders_can_unlock: u8,
3131
pub team_address: Pubkey,
3232
pub additional_tokens_amount: u64,
33+
pub accumulator_activation_delay_seconds: u32,
3334
}
3435

3536
#[event_cpi]
@@ -125,6 +126,12 @@ impl InitializeLaunch<'_> {
125126
LaunchpadError::InvalidSecondsForLaunch
126127
);
127128

129+
require_gt!(
130+
args.seconds_for_launch,
131+
args.accumulator_activation_delay_seconds,
132+
LaunchpadError::InvalidAccumulatorActivationDelaySeconds
133+
);
134+
128135
require!(
129136
self.base_mint.freeze_authority.is_none(),
130137
LaunchpadError::FreezeAuthoritySet
@@ -239,6 +246,7 @@ impl InitializeLaunch<'_> {
239246
additional_tokens_claimed: false,
240247
unix_timestamp_completed: None,
241248
is_performance_package_initialized: false,
249+
accumulator_activation_delay_seconds: args.accumulator_activation_delay_seconds,
242250
});
243251

244252
let clock = Clock::get()?;
@@ -266,6 +274,7 @@ impl InitializeLaunch<'_> {
266274
.additional_tokens_recipient
267275
.as_ref()
268276
.map(|a| a.key()),
277+
accumulator_activation_delay_seconds: args.accumulator_activation_delay_seconds,
269278
});
270279

271280
let launch_key = ctx.accounts.launch.key();

programs/v07_launchpad/src/instructions/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pub mod fund;
66
pub mod initialize_launch;
77
pub mod initialize_performance_package;
88
pub mod refund;
9+
pub mod resize_funding_record;
10+
pub mod resize_launch;
911
pub mod set_funding_record_approval;
1012
pub mod start_launch;
1113

@@ -17,5 +19,7 @@ pub use fund::*;
1719
pub use initialize_launch::*;
1820
pub use initialize_performance_package::*;
1921
pub use refund::*;
22+
pub use resize_funding_record::*;
23+
pub use resize_launch::*;
2024
pub use set_funding_record_approval::*;
2125
pub use start_launch::*;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use anchor_lang::{prelude::*, system_program, Discriminator};
2+
3+
use crate::state::{FundingRecord, OldFundingRecord};
4+
use crate::ID;
5+
6+
#[derive(Accounts)]
7+
pub struct ResizeFundingRecord<'info> {
8+
/// CHECK: we check the discriminator, owner, and size
9+
#[account(mut)]
10+
pub funding_record: UncheckedAccount<'info>,
11+
#[account(mut)]
12+
pub payer: Signer<'info>,
13+
pub system_program: Program<'info, System>,
14+
}
15+
16+
impl ResizeFundingRecord<'_> {
17+
pub fn handle(ctx: Context<Self>) -> Result<()> {
18+
let funding_record = &mut ctx.accounts.funding_record;
19+
20+
// Owner check
21+
require_eq!(funding_record.owner, &ID);
22+
23+
// Discriminator check
24+
let is_discriminator_correct =
25+
funding_record.data.try_borrow_mut().unwrap()[..8] == FundingRecord::discriminator();
26+
require_eq!(is_discriminator_correct, true);
27+
28+
const AFTER_REALLOC_SIZE: usize = 8 + FundingRecord::INIT_SPACE;
29+
const BEFORE_REALLOC_SIZE: usize = 8 + OldFundingRecord::INIT_SPACE;
30+
31+
// Size check
32+
if funding_record.data_len() != BEFORE_REALLOC_SIZE {
33+
require_eq!(funding_record.data_len(), AFTER_REALLOC_SIZE);
34+
return Ok(());
35+
}
36+
37+
let old_data =
38+
OldFundingRecord::deserialize(&mut &funding_record.try_borrow_data().unwrap()[8..])?;
39+
40+
let new_data = FundingRecord {
41+
pda_bump: old_data.pda_bump,
42+
funder: old_data.funder,
43+
launch: old_data.launch,
44+
committed_amount: old_data.committed_amount,
45+
is_tokens_claimed: old_data.is_tokens_claimed,
46+
is_usdc_refunded: old_data.is_usdc_refunded,
47+
approved_amount: old_data.approved_amount,
48+
committed_amount_accumulator: 0,
49+
last_accumulator_update: 0,
50+
};
51+
52+
funding_record.realloc(AFTER_REALLOC_SIZE, true)?;
53+
54+
let lamports_needed = Rent::get()?.minimum_balance(AFTER_REALLOC_SIZE);
55+
56+
if lamports_needed > funding_record.lamports() {
57+
system_program::transfer(
58+
CpiContext::new(
59+
ctx.accounts.system_program.to_account_info(),
60+
system_program::Transfer {
61+
from: ctx.accounts.payer.to_account_info(),
62+
to: funding_record.to_account_info(),
63+
},
64+
),
65+
lamports_needed - funding_record.lamports(),
66+
)?;
67+
}
68+
69+
new_data.serialize(&mut &mut funding_record.try_borrow_mut_data().unwrap()[8..])?;
70+
71+
Ok(())
72+
}
73+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use anchor_lang::{prelude::*, system_program, Discriminator};
2+
3+
use crate::state::{Launch, OldLaunch};
4+
use crate::ID;
5+
6+
#[derive(Accounts)]
7+
pub struct ResizeLaunch<'info> {
8+
/// CHECK: we check the discriminator, owner, and size
9+
#[account(mut)]
10+
pub launch: UncheckedAccount<'info>,
11+
#[account(mut)]
12+
pub payer: Signer<'info>,
13+
pub system_program: Program<'info, System>,
14+
}
15+
16+
impl ResizeLaunch<'_> {
17+
pub fn handle(ctx: Context<Self>) -> Result<()> {
18+
let launch = &mut ctx.accounts.launch;
19+
20+
// Owner check
21+
require_eq!(launch.owner, &ID);
22+
23+
// Discriminator check
24+
let is_discriminator_correct =
25+
launch.data.try_borrow_mut().unwrap()[..8] == Launch::discriminator();
26+
require_eq!(is_discriminator_correct, true);
27+
28+
const AFTER_REALLOC_SIZE: usize = 8 + Launch::INIT_SPACE;
29+
const BEFORE_REALLOC_SIZE: usize = 8 + OldLaunch::INIT_SPACE;
30+
31+
// Size check
32+
if launch.data_len() != BEFORE_REALLOC_SIZE {
33+
require_eq!(launch.data_len(), AFTER_REALLOC_SIZE);
34+
return Ok(());
35+
}
36+
37+
let old_data = OldLaunch::deserialize(&mut &launch.try_borrow_data().unwrap()[8..])?;
38+
39+
let new_data = Launch {
40+
pda_bump: old_data.pda_bump,
41+
minimum_raise_amount: old_data.minimum_raise_amount,
42+
monthly_spending_limit_amount: old_data.monthly_spending_limit_amount,
43+
monthly_spending_limit_members: old_data.monthly_spending_limit_members,
44+
launch_authority: old_data.launch_authority,
45+
launch_signer: old_data.launch_signer,
46+
launch_signer_pda_bump: old_data.launch_signer_pda_bump,
47+
launch_quote_vault: old_data.launch_quote_vault,
48+
launch_base_vault: old_data.launch_base_vault,
49+
base_mint: old_data.base_mint,
50+
quote_mint: old_data.quote_mint,
51+
unix_timestamp_started: old_data.unix_timestamp_started,
52+
unix_timestamp_closed: old_data.unix_timestamp_closed,
53+
total_committed_amount: old_data.total_committed_amount,
54+
state: old_data.state,
55+
seq_num: old_data.seq_num,
56+
seconds_for_launch: old_data.seconds_for_launch,
57+
dao: old_data.dao,
58+
dao_vault: old_data.dao_vault,
59+
performance_package_grantee: old_data.performance_package_grantee,
60+
performance_package_token_amount: old_data.performance_package_token_amount,
61+
months_until_insiders_can_unlock: old_data.months_until_insiders_can_unlock,
62+
team_address: old_data.team_address,
63+
total_approved_amount: old_data.total_approved_amount,
64+
additional_tokens_amount: old_data.additional_tokens_amount,
65+
additional_tokens_recipient: old_data.additional_tokens_recipient,
66+
additional_tokens_claimed: old_data.additional_tokens_claimed,
67+
unix_timestamp_completed: old_data.unix_timestamp_completed,
68+
is_performance_package_initialized: old_data.is_performance_package_initialized,
69+
accumulator_activation_delay_seconds: 0,
70+
};
71+
72+
launch.realloc(AFTER_REALLOC_SIZE, true)?;
73+
74+
let lamports_needed = Rent::get()?.minimum_balance(AFTER_REALLOC_SIZE);
75+
76+
if lamports_needed > launch.lamports() {
77+
system_program::transfer(
78+
CpiContext::new(
79+
ctx.accounts.system_program.to_account_info(),
80+
system_program::Transfer {
81+
from: ctx.accounts.payer.to_account_info(),
82+
to: launch.to_account_info(),
83+
},
84+
),
85+
lamports_needed - launch.lamports(),
86+
)?;
87+
}
88+
89+
new_data.serialize(&mut &mut launch.try_borrow_mut_data().unwrap()[8..])?;
90+
91+
Ok(())
92+
}
93+
}

programs/v07_launchpad/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,12 @@ pub mod launchpad_v7 {
121121
) -> Result<()> {
122122
InitializePerformancePackage::handle(ctx)
123123
}
124+
125+
pub fn resize_funding_record(ctx: Context<ResizeFundingRecord>) -> Result<()> {
126+
ResizeFundingRecord::handle(ctx)
127+
}
128+
129+
pub fn resize_launch(ctx: Context<ResizeLaunch>) -> Result<()> {
130+
ResizeLaunch::handle(ctx)
131+
}
124132
}

programs/v07_launchpad/src/state/funding_record.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,28 @@ pub struct FundingRecord {
1818
/// The amount of USDC that the launch authority has approved for the funder.
1919
/// If zero, the funder has not been approved for any amount.
2020
pub approved_amount: u64,
21+
/// Running integral of committed_amount over time (committed_amount * seconds).
22+
pub committed_amount_accumulator: u128,
23+
/// Unix timestamp of the last accumulator update.
24+
pub last_accumulator_update: i64,
25+
}
26+
27+
#[account]
28+
#[derive(InitSpace)]
29+
pub struct OldFundingRecord {
30+
/// The PDA bump.
31+
pub pda_bump: u8,
32+
/// The funder.
33+
pub funder: Pubkey,
34+
/// The launch.
35+
pub launch: Pubkey,
36+
/// The amount of USDC that has been committed by the funder.
37+
pub committed_amount: u64,
38+
/// Whether the tokens have been claimed.
39+
pub is_tokens_claimed: bool,
40+
/// Whether the USDC has been refunded.
41+
pub is_usdc_refunded: bool,
42+
/// The amount of USDC that the launch authority has approved for the funder.
43+
/// If zero, the funder has not been approved for any amount.
44+
pub approved_amount: u64,
2145
}

0 commit comments

Comments
 (0)