Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions programs/bid_wall/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ pub enum BidWallError {
InvalidInputAmount,
#[msg("Invalid crank address")]
InvalidCrankAddress,
#[msg("Invalid fee decay duration. Must be greater than 0")]
InvalidFeeDecayDuration,
}
3 changes: 3 additions & 0 deletions programs/bid_wall/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub struct BidWallInitializedEvent {
pub base_mint: Pubkey,
pub fee_recipient: Pubkey,
pub duration_seconds: u32,
pub fee_decay_duration_seconds: u32,
pub max_fee_bps: u16,
pub min_fee_bps: u16,
pub pda_bump: u8,
}

Expand Down
19 changes: 18 additions & 1 deletion programs/bid_wall/src/instructions/initialize_bid_wall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anchor_spl::{
};

use crate::{
error::BidWallError,
events::{BidWallInitializedEvent, CommonFields},
state::BidWall,
usdc_mint,
Expand All @@ -16,6 +17,9 @@ pub struct InitializeBidWallArgs {
pub nonce: u64,
pub initial_amm_quote_reserves: u64,
pub duration_seconds: u32,
pub fee_decay_duration_seconds: u32,
pub max_fee_bps: u16,
pub min_fee_bps: u16,
}

#[event_cpi]
Expand Down Expand Up @@ -63,7 +67,14 @@ pub struct InitializeBidWall<'info> {
}

impl InitializeBidWall<'_> {
pub fn validate(&self, _args: &InitializeBidWallArgs) -> Result<()> {
pub fn validate(&self, args: &InitializeBidWallArgs) -> Result<()> {
// Prevents division by zero in the sell_tokens instruction
require_gt!(
args.fee_decay_duration_seconds,
0,
BidWallError::InvalidFeeDecayDuration
);

Ok(())
}

Expand Down Expand Up @@ -101,6 +112,9 @@ impl InitializeBidWall<'_> {
base_mint: ctx.accounts.base_mint.key(),
fee_recipient: ctx.accounts.fee_recipient.key(),
duration_seconds: args.duration_seconds,
fee_decay_duration_seconds: args.fee_decay_duration_seconds,
max_fee_bps: args.max_fee_bps,
min_fee_bps: args.min_fee_bps,
pda_bump: ctx.bumps.bid_wall,
});

Expand All @@ -116,6 +130,9 @@ impl InitializeBidWall<'_> {
base_mint: ctx.accounts.base_mint.key(),
fee_recipient: ctx.accounts.fee_recipient.key(),
duration_seconds: args.duration_seconds,
fee_decay_duration_seconds: ctx.accounts.bid_wall.fee_decay_duration_seconds,
max_fee_bps: ctx.accounts.bid_wall.max_fee_bps,
min_fee_bps: ctx.accounts.bid_wall.min_fee_bps,
pda_bump: ctx.bumps.bid_wall,
});

Expand Down
22 changes: 19 additions & 3 deletions programs/bid_wall/src/instructions/sell_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
error::BidWallError,
events::{BidWallTokensSoldEvent, CommonFields},
state::BidWall,
usdc_mint, FEE_BPS, TOKENS_TO_PARTICIPANTS,
usdc_mint, TOKENS_TO_PARTICIPANTS,
};

use anchor_lang::prelude::*;
Expand Down Expand Up @@ -87,6 +87,8 @@ impl SellTokens<'_> {
pub fn handle(ctx: Context<Self>, args: SellTokensArgs) -> Result<()> {
let SellTokensArgs { amount_in } = args;

let clock = Clock::get()?;

// We calculate the total NAV as as sum of:
// - The initial quote reserves of the Futarchy AMM
// - The quote tokens in the DAO treasury (which can be spent by the DAO)
Expand All @@ -109,8 +111,22 @@ impl SellTokens<'_> {
BidWallError::InsufficientQuoteReserves
);

let bid_wall_age_seconds = clock.unix_timestamp - ctx.accounts.bid_wall.created_timestamp;

let min_fee_bps = ctx.accounts.bid_wall.min_fee_bps as u128;
let max_fee_bps = ctx.accounts.bid_wall.max_fee_bps as u128;

// Calculate the fee in basis points based on the bid wall age.
// Formula is simple linear decay based on the fee decay duration.
// max_fee - (max_fee - min_fee) * bid_wall_age / fee_decay_duration_seconds
let fee_bps = min_fee_bps.max(
max_fee_bps
- (max_fee_bps - min_fee_bps) * bid_wall_age_seconds as u128
/ ctx.accounts.bid_wall.fee_decay_duration_seconds as u128,
);

let amount_out_after_fee =
((10_000_u128 - FEE_BPS as u128) * amount_out_before_fee as u128 / 10_000_u128) as u64;
((10_000_u128 - fee_bps) * amount_out_before_fee as u128 / 10_000_u128) as u64;

let fee = amount_out_before_fee - amount_out_after_fee;

Expand Down Expand Up @@ -157,7 +173,7 @@ impl SellTokens<'_> {
ctx.accounts.bid_wall.seq_num += 1;

emit_cpi!(BidWallTokensSoldEvent {
common: CommonFields::new(&Clock::get()?, ctx.accounts.bid_wall.seq_num),
common: CommonFields::new(&clock, ctx.accounts.bid_wall.seq_num),
bid_wall: ctx.accounts.bid_wall.key(),
amount_in: amount_in,
amount_out: amount_out_after_fee,
Expand Down
2 changes: 0 additions & 2 deletions programs/bid_wall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ pub mod usdc_mint {
declare_id!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
}

pub const FEE_BPS: u16 = 100;

pub const TOKEN_SCALE: u64 = 1_000_000;
/// 10M tokens with 6 decimals - the exact amount of tokens that end up in floating supply at launch
pub const TOKENS_TO_PARTICIPANTS: u64 = 10_000_000 * TOKEN_SCALE;
Expand Down
6 changes: 6 additions & 0 deletions programs/bid_wall/src/state/bid_wall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub struct BidWall {
pub fee_recipient: Pubkey,
/// The minimum duration in seconds before the bid wall can be closed.
pub duration_seconds: u32,
/// The duration in seconds over which the fee linearly decays from the max fee to the min fee.
pub fee_decay_duration_seconds: u32,
/// The maximum fee in basis points.
pub max_fee_bps: u16,
/// The minimum fee in basis points.
pub min_fee_bps: u16,
/// The PDA bump.
pub pda_bump: u8,
}
7 changes: 6 additions & 1 deletion programs/v07_launchpad/src/instructions/complete_launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use futarchy::{InitialSpendingLimit, InitializeDaoParams, ProvideLiquidityParams

use damm_v2_cpi::program::DammV2Cpi;

pub const PASS_THRESHOLD_BPS: u16 = 300; // 3%

/// Static accounts for completing a launch, used to reduce code duplication
/// and conserve stack space.
#[derive(Accounts)]
Expand Down Expand Up @@ -425,7 +427,7 @@ impl CompleteLaunch<'_> {
// We're providing liquidity, so that can be used for proposals
min_quote_futarchic_liquidity: 0,
min_base_futarchic_liquidity: 0,
pass_threshold_bps: 300,
pass_threshold_bps: PASS_THRESHOLD_BPS,
base_to_stake: TOKENS_TO_PARTICIPANTS / 20,
seconds_per_proposal: 3 * 24 * 60 * 60,
twap_start_delay_seconds: 24 * 60 * 60,
Expand Down Expand Up @@ -478,6 +480,9 @@ impl CompleteLaunch<'_> {
nonce: 0,
initial_amm_quote_reserves: usdc_to_lp,
duration_seconds: 3 * 30 * 24 * 60 * 60, // 3 months
fee_decay_duration_seconds: 14 * 24 * 60 * 60, // 14 days
max_fee_bps: PASS_THRESHOLD_BPS + 200, // 2% more than the pass threshold = 5%
min_fee_bps: PASS_THRESHOLD_BPS, // Set min_fee to be the same as the pass threshold
},
)
}
Expand Down
9 changes: 9 additions & 0 deletions sdk/src/v0.7/BidWallClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export class BidWallClient {
initializeBidWallIx({
amount,
durationSeconds,
feeDecayDurationSeconds,
maxFeeBps,
minFeeBps,
initialAmmQuoteReserves,
daoTreasury,
authority,
Expand All @@ -68,6 +71,9 @@ export class BidWallClient {
}: {
amount: number;
durationSeconds: number;
feeDecayDurationSeconds: number;
maxFeeBps: number;
minFeeBps: number;
initialAmmQuoteReserves: number;
daoTreasury: PublicKey;
creator?: PublicKey;
Expand Down Expand Up @@ -98,6 +104,9 @@ export class BidWallClient {
nonce,
durationSeconds,
initialAmmQuoteReserves: new BN(initialAmmQuoteReserves),
feeDecayDurationSeconds,
maxFeeBps,
minFeeBps,
})
.accounts({
bidWall,
Expand Down
98 changes: 98 additions & 0 deletions sdk/src/v0.7/types/bid_wall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,23 @@ export type BidWall = {
];
type: "u32";
},
{
name: "feeDecayDurationSeconds";
docs: [
"The duration in seconds over which the fee linearly decays from the max fee to the min fee.",
];
type: "u32";
},
{
name: "maxFeeBps";
docs: ["The maximum fee in basis points."];
type: "u16";
},
{
name: "minFeeBps";
docs: ["The minimum fee in basis points."];
type: "u16";
},
{
name: "pdaBump";
docs: ["The PDA bump."];
Expand Down Expand Up @@ -490,6 +507,18 @@ export type BidWall = {
name: "durationSeconds";
type: "u32";
},
{
name: "feeDecayDurationSeconds";
type: "u32";
},
{
name: "maxFeeBps";
type: "u16";
},
{
name: "minFeeBps";
type: "u16";
},
];
};
},
Expand Down Expand Up @@ -567,6 +596,21 @@ export type BidWall = {
type: "u32";
index: false;
},
{
name: "feeDecayDurationSeconds";
type: "u32";
index: false;
},
{
name: "maxFeeBps";
type: "u16";
index: false;
},
{
name: "minFeeBps";
type: "u16";
index: false;
},
{
name: "pdaBump";
type: "u8";
Expand Down Expand Up @@ -719,6 +763,11 @@ export type BidWall = {
name: "InvalidCrankAddress";
msg: "Invalid crank address";
},
{
code: 6007;
name: "InvalidFeeDecayDuration";
msg: "Invalid fee decay duration. Must be greater than 0";
},
];
};

Expand Down Expand Up @@ -1163,6 +1212,23 @@ export const IDL: BidWall = {
],
type: "u32",
},
{
name: "feeDecayDurationSeconds",
docs: [
"The duration in seconds over which the fee linearly decays from the max fee to the min fee.",
],
type: "u32",
},
{
name: "maxFeeBps",
docs: ["The maximum fee in basis points."],
type: "u16",
},
{
name: "minFeeBps",
docs: ["The minimum fee in basis points."],
type: "u16",
},
{
name: "pdaBump",
docs: ["The PDA bump."],
Expand Down Expand Up @@ -1214,6 +1280,18 @@ export const IDL: BidWall = {
name: "durationSeconds",
type: "u32",
},
{
name: "feeDecayDurationSeconds",
type: "u32",
},
{
name: "maxFeeBps",
type: "u16",
},
{
name: "minFeeBps",
type: "u16",
},
],
},
},
Expand Down Expand Up @@ -1291,6 +1369,21 @@ export const IDL: BidWall = {
type: "u32",
index: false,
},
{
name: "feeDecayDurationSeconds",
type: "u32",
index: false,
},
{
name: "maxFeeBps",
type: "u16",
index: false,
},
{
name: "minFeeBps",
type: "u16",
index: false,
},
{
name: "pdaBump",
type: "u8",
Expand Down Expand Up @@ -1443,5 +1536,10 @@ export const IDL: BidWall = {
name: "InvalidCrankAddress",
msg: "Invalid crank address",
},
{
code: 6007,
name: "InvalidFeeDecayDuration",
msg: "Invalid fee decay duration. Must be greater than 0",
},
],
};
7 changes: 5 additions & 2 deletions tests/bidWall/unit/cancelBidWall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export default function suite() {
.initializeBidWallIx({
amount: 100_000_000000,
durationSeconds,
feeDecayDurationSeconds: 2,
maxFeeBps: 200,
minFeeBps: 100,
initialAmmQuoteReserves: ammQuoteVaultReserves.toNumber(),
authority: this.payer.publicKey,
creator: this.payer.publicKey,
Expand Down Expand Up @@ -229,10 +232,10 @@ export default function suite() {
authorityUsdcBalanceAfter,
authorityUsdcBalanceBefore + 50_000_000000n,
);
// Fee recipient received 500 USDC in fees
// Fee recipient received 1000 USDC in fees
assert.equal(
feeRecipientUsdcBalanceAfter,
feeRecipientUsdcBalanceBefore + 500_000000n,
feeRecipientUsdcBalanceBefore + 1_000_000000n,
);
});

Expand Down
Loading
Loading