Skip to content
Merged
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 Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ v06-create-dao = "yarn run tsx scripts/v0.6/createDao.ts"
v06-provide-liquidity = "yarn run tsx scripts/v0.6/provideLiquidity.ts"
v06-collect-meteora-damm-fees = "yarn run tsx scripts/v0.6/collectMeteoraDammFees.ts"
v06-return-funds = "yarn run tsx scripts/v0.6/returnFunds.ts"
v06-dump-daos = "yarn run tsx scripts/v0.6/dumpDaos.ts"
v06-migrate-daos = "yarn run tsx scripts/v0.6/migrateDaos.ts"
v07-collect-meteora-damm-fees = "yarn run tsx scripts/v0.7/collectMeteoraDammFees.ts"
v07-launch-template = "yarn run tsx scripts/v0.7/launchTemplate.ts"
v07-start-launch = "yarn run tsx scripts/v0.7/startLaunch.ts"
Expand Down
2 changes: 2 additions & 0 deletions programs/futarchy/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod initialize_proposal;
pub mod initiate_vault_spend_optimistic_proposal;
pub mod launch_proposal;
pub mod provide_liquidity;
pub mod resize_dao;
pub mod sponsor_proposal;
pub mod spot_swap;
pub mod stake_to_proposal;
Expand All @@ -31,6 +32,7 @@ pub use initialize_proposal::*;
pub use initiate_vault_spend_optimistic_proposal::*;
pub use launch_proposal::*;
pub use provide_liquidity::*;
pub use resize_dao::*;
pub use sponsor_proposal::*;
pub use spot_swap::*;
pub use stake_to_proposal::*;
Expand Down
83 changes: 83 additions & 0 deletions programs/futarchy/src/instructions/resize_dao.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use anchor_lang::{system_program, Discriminator};

use super::*;

#[derive(Accounts)]
pub struct ResizeDao<'info> {
/// CHECK: we check the discriminator
#[account(mut)]
pub dao: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}

impl ResizeDao<'_> {
pub fn handle(ctx: Context<Self>) -> Result<()> {
let dao = &ctx.accounts.dao;

require_eq!(dao.owner, &crate::ID);
let is_discriminator_correct = dao.try_borrow_data().unwrap()[..8] == Dao::discriminator();
require_eq!(is_discriminator_correct, true);

const AFTER_REALLOC_SIZE: usize = Dao::INIT_SPACE + 8;
// 42 bytes: 1 (Option discriminant) + 32 (Pubkey) + 8 (i64) + 1 (bool)
const BEFORE_REALLOC_SIZE: usize = AFTER_REALLOC_SIZE - 42;

if dao.data_len() != BEFORE_REALLOC_SIZE {
// already realloced
require_eq!(dao.data_len(), AFTER_REALLOC_SIZE);
return Ok(());
}

let old_dao_data = OldDao::deserialize(&mut &dao.try_borrow_data().unwrap()[8..])?;

let new_dao_data = Dao {
amm: old_dao_data.amm,
nonce: old_dao_data.nonce,
dao_creator: old_dao_data.dao_creator,
pda_bump: old_dao_data.pda_bump,
squads_multisig: old_dao_data.squads_multisig,
squads_multisig_vault: old_dao_data.squads_multisig_vault,
base_mint: old_dao_data.base_mint,
quote_mint: old_dao_data.quote_mint,
proposal_count: old_dao_data.proposal_count,
pass_threshold_bps: old_dao_data.pass_threshold_bps,
seconds_per_proposal: old_dao_data.seconds_per_proposal,
twap_initial_observation: old_dao_data.twap_initial_observation,
twap_max_observation_change_per_update: old_dao_data
.twap_max_observation_change_per_update,
twap_start_delay_seconds: old_dao_data.twap_start_delay_seconds,
min_quote_futarchic_liquidity: old_dao_data.min_quote_futarchic_liquidity,
min_base_futarchic_liquidity: old_dao_data.min_base_futarchic_liquidity,
base_to_stake: old_dao_data.base_to_stake,
seq_num: old_dao_data.seq_num,
initial_spending_limit: old_dao_data.initial_spending_limit,
team_sponsored_pass_threshold_bps: old_dao_data.team_sponsored_pass_threshold_bps,
team_address: old_dao_data.team_address,
optimistic_proposal: None,
is_optimistic_governance_enabled: false,
};

dao.realloc(AFTER_REALLOC_SIZE, true)?;

let lamports_needed = Rent::get()?.minimum_balance(AFTER_REALLOC_SIZE);

if lamports_needed > dao.lamports() {
system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: dao.to_account_info(),
},
),
lamports_needed - dao.lamports(),
)?;
}

new_dao_data.serialize(&mut &mut dao.try_borrow_mut_data().unwrap()[8..])?;

Ok(())
}
}
4 changes: 4 additions & 0 deletions programs/futarchy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ pub mod futarchy {
UpdateDao::handle(ctx, dao_params)
}

pub fn resize_dao(ctx: Context<ResizeDao>) -> Result<()> {
ResizeDao::handle(ctx)
}

// AMM instructions

pub fn spot_swap(ctx: Context<SpotSwap>, params: SpotSwapParams) -> Result<()> {
Expand Down
56 changes: 56 additions & 0 deletions programs/futarchy/src/state/dao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,59 @@ impl Dao {
Ok(())
}
}

#[account]
#[derive(InitSpace)]
pub struct OldDao {
/// Embedded FutarchyAmm - 1:1 relationship
pub amm: FutarchyAmm,
/// `nonce` + `dao_creator` are PDA seeds
pub nonce: u64,
pub dao_creator: Pubkey,
pub pda_bump: u8,
pub squads_multisig: Pubkey,
pub squads_multisig_vault: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub proposal_count: u32,
// the percentage, in basis points, the pass price needs to be above the
// fail price in order for the proposal to pass
pub pass_threshold_bps: u16,
pub seconds_per_proposal: u32,
/// For manipulation-resistance the TWAP is a time-weighted average observation,
/// where observation tries to approximate price but can only move by
/// `twap_max_observation_change_per_update` per update. Because it can only move
/// a little bit per update, you need to check that it has a good initial observation.
/// Otherwise, an attacker could create a very high initial observation in the pass
/// market and a very low one in the fail market to force the proposal to pass.
///
/// We recommend setting an initial observation around the spot price of the token,
/// and max observation change per update around 2% the spot price of the token.
/// For example, if the spot price of META is $400, we'd recommend setting an initial
/// observation of 400 (converted into the AMM prices) and a max observation change per
/// update of 8 (also converted into the AMM prices). Observations can be updated once
/// a minute, so 2% allows the proposal market to reach double the spot price or 0
/// in 50 minutes.
pub twap_initial_observation: u128,
pub twap_max_observation_change_per_update: u128,
/// Forces TWAP calculation to start after `twap_start_delay_seconds` seconds
pub twap_start_delay_seconds: u32,
/// As an anti-spam measure and to help liquidity, you need to lock up some liquidity
/// in both futarchic markets in order to create a proposal.
///
/// For example, for META, we can use a `min_quote_futarchic_liquidity` of
/// 5000 * 1_000_000 (5000 USDC) and a `min_base_futarchic_liquidity` of
/// 10 * 1_000_000_000 (10 META).
pub min_quote_futarchic_liquidity: u64,
pub min_base_futarchic_liquidity: u64,
/// Minimum amount of base tokens that must be staked to launch a proposal
pub base_to_stake: u64,
pub seq_num: u64,
pub initial_spending_limit: Option<InitialSpendingLimit>,
/// The percentage, in basis points, the pass price needs to be above the
/// fail price in order for the proposal to pass for team-sponsored proposals.
///
/// Can be negative to allow for team-sponsored proposals to pass by default.
pub team_sponsored_pass_threshold_bps: i16,
pub team_address: Pubkey,
}
90 changes: 90 additions & 0 deletions scripts/v0.6/dumpDaos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { PublicKey } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
import { FutarchyClient } from "@metadaoproject/futarchy/v0.6";
import dotenv from "dotenv";
import * as fs from "fs";
import * as path from "path";
import bs58 from "bs58";

dotenv.config();

const provider = anchor.AnchorProvider.env();

function getDiscriminator(accountName: string): Buffer {
return Buffer.from(
anchor.BorshAccountsCoder.accountDiscriminator(accountName),
);
}

async function dumpAccount(
publicKey: PublicKey,
outputDir: string,
accountType: string,
) {
const accountInfo = await provider.connection.getAccountInfo(publicKey);

if (!accountInfo) {
console.error(`Account ${publicKey.toBase58()} not found`);
return;
}

const accountData = {
pubkey: publicKey.toBase58(),
account: {
lamports: accountInfo.lamports,
data: [accountInfo.data.toString("base64"), "base64"],
owner: accountInfo.owner.toBase58(),
executable: accountInfo.executable,
rentEpoch: "U64_MAX_PLACEHOLDER",
},
};

const filename = path.join(outputDir, `${publicKey.toBase58()}.json`);
fs.writeFileSync(
filename,
JSON.stringify(accountData, null, 2).replace(
'"U64_MAX_PLACEHOLDER"',
"18446744073709551615",
),
);

console.log(`Dumped ${accountType}: ${publicKey.toBase58()}`);
}

async function main() {
const futarchy = FutarchyClient.createClient({ provider });

const daosDir = "daos";
if (!fs.existsSync(daosDir)) {
fs.mkdirSync(daosDir);
}

const daoDiscriminator = getDiscriminator("Dao");
console.log(`DAO discriminator (hex): ${daoDiscriminator.toString("hex")}`);
console.log(`DAO discriminator (base58): ${bs58.encode(daoDiscriminator)}`);
console.log(`Program ID: ${futarchy.futarchy.programId.toBase58()}\n`);

const daoAccounts = await provider.connection.getProgramAccounts(
futarchy.futarchy.programId,
{
filters: [
{
memcmp: {
offset: 0,
bytes: bs58.encode(daoDiscriminator),
},
},
],
},
);

console.log(`Found ${daoAccounts.length} DAOs`);
for (const { pubkey } of daoAccounts) {
await dumpAccount(pubkey, daosDir, "DAO");
}
}

main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
Loading
Loading