diff --git a/Anchor.toml b/Anchor.toml index 10a9ab039..1ade3f213 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -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" diff --git a/programs/futarchy/src/instructions/mod.rs b/programs/futarchy/src/instructions/mod.rs index 85b8023e3..90e20456b 100644 --- a/programs/futarchy/src/instructions/mod.rs +++ b/programs/futarchy/src/instructions/mod.rs @@ -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; @@ -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::*; diff --git a/programs/futarchy/src/instructions/resize_dao.rs b/programs/futarchy/src/instructions/resize_dao.rs new file mode 100644 index 000000000..6352ac116 --- /dev/null +++ b/programs/futarchy/src/instructions/resize_dao.rs @@ -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) -> 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(()) + } +} diff --git a/programs/futarchy/src/lib.rs b/programs/futarchy/src/lib.rs index 1c0f4ebab..0eaaa4c7a 100644 --- a/programs/futarchy/src/lib.rs +++ b/programs/futarchy/src/lib.rs @@ -100,6 +100,10 @@ pub mod futarchy { UpdateDao::handle(ctx, dao_params) } + pub fn resize_dao(ctx: Context) -> Result<()> { + ResizeDao::handle(ctx) + } + // AMM instructions pub fn spot_swap(ctx: Context, params: SpotSwapParams) -> Result<()> { diff --git a/programs/futarchy/src/state/dao.rs b/programs/futarchy/src/state/dao.rs index dfed8ac6b..0d898f636 100644 --- a/programs/futarchy/src/state/dao.rs +++ b/programs/futarchy/src/state/dao.rs @@ -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, + /// 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, +} diff --git a/scripts/v0.6/dumpDaos.ts b/scripts/v0.6/dumpDaos.ts new file mode 100644 index 000000000..33bb5d55c --- /dev/null +++ b/scripts/v0.6/dumpDaos.ts @@ -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); +}); diff --git a/scripts/v0.6/migrateDaos.ts b/scripts/v0.6/migrateDaos.ts new file mode 100644 index 000000000..427b3f7a1 --- /dev/null +++ b/scripts/v0.6/migrateDaos.ts @@ -0,0 +1,133 @@ +import { + ComputeBudgetProgram, + Keypair, + TransactionInstruction, + VersionedTransaction, + TransactionMessage, +} from "@solana/web3.js"; +import * as anchor from "@coral-xyz/anchor"; +import { FutarchyClient } from "@metadaoproject/futarchy/v0.6"; +import dotenv from "dotenv"; +import bs58 from "bs58"; + +dotenv.config(); + +const provider = anchor.AnchorProvider.env(); +const payer = provider.wallet["payer"]; + +function getDiscriminator(accountName: string): Buffer { + return Buffer.from( + anchor.BorshAccountsCoder.accountDiscriminator(accountName), + ); +} + +async function sendAndConfirmTransaction( + ixs: TransactionInstruction[], + label: string, + signers: Keypair[] = [], +) { + const { blockhash } = await provider.connection.getLatestBlockhash(); + + const messageV0 = new TransactionMessage({ + instructions: ixs, + payerKey: payer.publicKey, + recentBlockhash: blockhash, + }).compileToV0Message(); + const simulationTx = new VersionedTransaction(messageV0); + simulationTx.sign([payer, ...signers]); + + const simulationResult = + await provider.connection.simulateTransaction(simulationTx); + + const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: Math.ceil(simulationResult.value.unitsConsumed! * 1.15), + }); + + const finalMessageV0 = new TransactionMessage({ + instructions: [computeBudgetIx, ...ixs], + payerKey: payer.publicKey, + recentBlockhash: blockhash, + }).compileToV0Message(); + const tx = new VersionedTransaction(finalMessageV0); + tx.sign([payer, ...signers]); + + const txHash = await provider.connection.sendRawTransaction(tx.serialize()); + console.log(`${label} transaction sent:`, txHash); + + await provider.connection.confirmTransaction(txHash, "confirmed"); + const txStatus = await provider.connection.getTransaction(txHash, { + maxSupportedTransactionVersion: 0, + commitment: "confirmed", + }); + if (txStatus?.meta?.err) { + throw new Error( + `Transaction failed: ${txHash}\nError: ${JSON.stringify( + txStatus?.meta?.err, + )}\n\n${txStatus?.meta?.logMessages?.join("\n")}`, + ); + } + console.log(`${label} transaction confirmed`); + return txHash; +} + +async function main() { + const futarchy = FutarchyClient.createClient({ provider }); + + const daoDiscriminator = getDiscriminator("Dao"); + const daoBatchSize = 15; + + 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 (let i = 0; i < daoAccounts.length; i += daoBatchSize) { + const batch = daoAccounts.slice( + i, + Math.min(i + daoBatchSize, daoAccounts.length), + ); + console.log( + `Processing batch ${Math.floor(i / daoBatchSize) + 1} with ${batch.length} DAOs`, + ); + + const ixs = await Promise.all( + batch.map(async ({ pubkey }) => { + return await futarchy.futarchy.methods + .resizeDao() + .accounts({ + dao: pubkey, + payer: payer.publicKey, + }) + .instruction(); + }), + ); + + await sendAndConfirmTransaction( + ixs, + `Resize DAOs batch ${Math.floor(i / daoBatchSize) + 1}`, + ); + } + + console.log("Confirming DAOs can be loaded through SDK..."); + const daos = await futarchy.futarchy.account.dao.all(); + console.log(`Confirmed ${daos.length} DAOs`); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/sdk/src/v0.6/types/futarchy.ts b/sdk/src/v0.6/types/futarchy.ts index d9d95cdc1..99270d126 100644 --- a/sdk/src/v0.6/types/futarchy.ts +++ b/sdk/src/v0.6/types/futarchy.ts @@ -118,6 +118,11 @@ export type Futarchy = { isMut: false; isSigner: false; }, + { + name: "squadsMultisig"; + isMut: false; + isSigner: false; + }, { name: "dao"; isMut: true; @@ -370,6 +375,16 @@ export type Futarchy = { isMut: true; isSigner: false; }, + { + name: "squadsMultisig"; + isMut: false; + isSigner: false; + }, + { + name: "squadsProposal"; + isMut: false; + isSigner: false; + }, { name: "systemProgram"; isMut: false; @@ -562,6 +577,27 @@ export type Futarchy = { }, ]; }, + { + name: "resizeDao"; + accounts: [ + { + name: "dao"; + isMut: true; + isSigner: false; + }, + { + name: "payer"; + isMut: true; + isSigner: true; + }, + { + name: "systemProgram"; + isMut: false; + isSigner: false; + }, + ]; + args: []; + }, { name: "spotSwap"; accounts: [ @@ -1304,6 +1340,52 @@ export type Futarchy = { ]; args: []; }, + { + name: "adminApproveExecuteMultisigProposal"; + accounts: [ + { + name: "dao"; + isMut: true; + isSigner: false; + }, + { + name: "admin"; + isMut: true; + isSigner: true; + }, + { + name: "squadsMultisig"; + isMut: true; + isSigner: false; + }, + { + name: "squadsMultisigProposal"; + isMut: true; + isSigner: false; + }, + { + name: "squadsMultisigVaultTransaction"; + isMut: true; + isSigner: false; + }, + { + name: "squadsMultisigProgram"; + isMut: false; + isSigner: false; + }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, + ]; + args: []; + }, ]; accounts: [ { @@ -1474,6 +1556,142 @@ export type Futarchy = { ]; }; }, + { + name: "oldDao"; + type: { + kind: "struct"; + fields: [ + { + name: "amm"; + docs: ["Embedded FutarchyAmm - 1:1 relationship"]; + type: { + defined: "FutarchyAmm"; + }; + }, + { + name: "nonce"; + docs: ["`nonce` + `dao_creator` are PDA seeds"]; + type: "u64"; + }, + { + name: "daoCreator"; + type: "publicKey"; + }, + { + name: "pdaBump"; + type: "u8"; + }, + { + name: "squadsMultisig"; + type: "publicKey"; + }, + { + name: "squadsMultisigVault"; + type: "publicKey"; + }, + { + name: "baseMint"; + type: "publicKey"; + }, + { + name: "quoteMint"; + type: "publicKey"; + }, + { + name: "proposalCount"; + type: "u32"; + }, + { + name: "passThresholdBps"; + type: "u16"; + }, + { + name: "secondsPerProposal"; + type: "u32"; + }, + { + name: "twapInitialObservation"; + docs: [ + "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.", + ]; + type: "u128"; + }, + { + name: "twapMaxObservationChangePerUpdate"; + type: "u128"; + }, + { + name: "twapStartDelaySeconds"; + docs: [ + "Forces TWAP calculation to start after `twap_start_delay_seconds` seconds", + ]; + type: "u32"; + }, + { + name: "minQuoteFutarchicLiquidity"; + docs: [ + "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).", + ]; + type: "u64"; + }, + { + name: "minBaseFutarchicLiquidity"; + type: "u64"; + }, + { + name: "baseToStake"; + docs: [ + "Minimum amount of base tokens that must be staked to launch a proposal", + ]; + type: "u64"; + }, + { + name: "seqNum"; + type: "u64"; + }, + { + name: "initialSpendingLimit"; + type: { + option: { + defined: "InitialSpendingLimit"; + }; + }; + }, + { + name: "teamSponsoredPassThresholdBps"; + docs: [ + "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.", + ]; + type: "i16"; + }, + { + name: "teamAddress"; + type: "publicKey"; + }, + ]; + }; + }, { name: "proposal"; type: { @@ -3219,6 +3437,21 @@ export type Futarchy = { name: "NoActiveOptimisticProposal"; msg: "No active optimistic proposal"; }, + { + code: 6040; + name: "OptimisticProposalAlreadyPassed"; + msg: "Optimistic proposal has already passed"; + }, + { + code: 6041; + name: "CannotSponsorOptimisticProposalChallenge"; + msg: "Team cannot sponsor a challenge to an optimistic proposal"; + }, + { + code: 6042; + name: "InvalidSpendingLimitMint"; + msg: "Invalid spending limit mint. Must be the same as the DAO's quote mint"; + }, ]; }; @@ -3342,6 +3575,11 @@ export const IDL: Futarchy = { isMut: false, isSigner: false, }, + { + name: "squadsMultisig", + isMut: false, + isSigner: false, + }, { name: "dao", isMut: true, @@ -3594,6 +3832,16 @@ export const IDL: Futarchy = { isMut: true, isSigner: false, }, + { + name: "squadsMultisig", + isMut: false, + isSigner: false, + }, + { + name: "squadsProposal", + isMut: false, + isSigner: false, + }, { name: "systemProgram", isMut: false, @@ -3786,6 +4034,27 @@ export const IDL: Futarchy = { }, ], }, + { + name: "resizeDao", + accounts: [ + { + name: "dao", + isMut: true, + isSigner: false, + }, + { + name: "payer", + isMut: true, + isSigner: true, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, { name: "spotSwap", accounts: [ @@ -4528,6 +4797,52 @@ export const IDL: Futarchy = { ], args: [], }, + { + name: "adminApproveExecuteMultisigProposal", + accounts: [ + { + name: "dao", + isMut: true, + isSigner: false, + }, + { + name: "admin", + isMut: true, + isSigner: true, + }, + { + name: "squadsMultisig", + isMut: true, + isSigner: false, + }, + { + name: "squadsMultisigProposal", + isMut: true, + isSigner: false, + }, + { + name: "squadsMultisigVaultTransaction", + isMut: true, + isSigner: false, + }, + { + name: "squadsMultisigProgram", + isMut: false, + isSigner: false, + }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, ], accounts: [ { @@ -4698,6 +5013,142 @@ export const IDL: Futarchy = { ], }, }, + { + name: "oldDao", + type: { + kind: "struct", + fields: [ + { + name: "amm", + docs: ["Embedded FutarchyAmm - 1:1 relationship"], + type: { + defined: "FutarchyAmm", + }, + }, + { + name: "nonce", + docs: ["`nonce` + `dao_creator` are PDA seeds"], + type: "u64", + }, + { + name: "daoCreator", + type: "publicKey", + }, + { + name: "pdaBump", + type: "u8", + }, + { + name: "squadsMultisig", + type: "publicKey", + }, + { + name: "squadsMultisigVault", + type: "publicKey", + }, + { + name: "baseMint", + type: "publicKey", + }, + { + name: "quoteMint", + type: "publicKey", + }, + { + name: "proposalCount", + type: "u32", + }, + { + name: "passThresholdBps", + type: "u16", + }, + { + name: "secondsPerProposal", + type: "u32", + }, + { + name: "twapInitialObservation", + docs: [ + "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.", + ], + type: "u128", + }, + { + name: "twapMaxObservationChangePerUpdate", + type: "u128", + }, + { + name: "twapStartDelaySeconds", + docs: [ + "Forces TWAP calculation to start after `twap_start_delay_seconds` seconds", + ], + type: "u32", + }, + { + name: "minQuoteFutarchicLiquidity", + docs: [ + "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).", + ], + type: "u64", + }, + { + name: "minBaseFutarchicLiquidity", + type: "u64", + }, + { + name: "baseToStake", + docs: [ + "Minimum amount of base tokens that must be staked to launch a proposal", + ], + type: "u64", + }, + { + name: "seqNum", + type: "u64", + }, + { + name: "initialSpendingLimit", + type: { + option: { + defined: "InitialSpendingLimit", + }, + }, + }, + { + name: "teamSponsoredPassThresholdBps", + docs: [ + "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.", + ], + type: "i16", + }, + { + name: "teamAddress", + type: "publicKey", + }, + ], + }, + }, { name: "proposal", type: { @@ -6443,5 +6894,20 @@ export const IDL: Futarchy = { name: "NoActiveOptimisticProposal", msg: "No active optimistic proposal", }, + { + code: 6040, + name: "OptimisticProposalAlreadyPassed", + msg: "Optimistic proposal has already passed", + }, + { + code: 6041, + name: "CannotSponsorOptimisticProposalChallenge", + msg: "Team cannot sponsor a challenge to an optimistic proposal", + }, + { + code: 6042, + name: "InvalidSpendingLimitMint", + msg: "Invalid spending limit mint. Must be the same as the DAO's quote mint", + }, ], }; diff --git a/sdk/src/v0.7/types/futarchy.ts b/sdk/src/v0.7/types/futarchy.ts index b9540ec56..99270d126 100644 --- a/sdk/src/v0.7/types/futarchy.ts +++ b/sdk/src/v0.7/types/futarchy.ts @@ -577,6 +577,27 @@ export type Futarchy = { }, ]; }, + { + name: "resizeDao"; + accounts: [ + { + name: "dao"; + isMut: true; + isSigner: false; + }, + { + name: "payer"; + isMut: true; + isSigner: true; + }, + { + name: "systemProgram"; + isMut: false; + isSigner: false; + }, + ]; + args: []; + }, { name: "spotSwap"; accounts: [ @@ -1535,6 +1556,142 @@ export type Futarchy = { ]; }; }, + { + name: "oldDao"; + type: { + kind: "struct"; + fields: [ + { + name: "amm"; + docs: ["Embedded FutarchyAmm - 1:1 relationship"]; + type: { + defined: "FutarchyAmm"; + }; + }, + { + name: "nonce"; + docs: ["`nonce` + `dao_creator` are PDA seeds"]; + type: "u64"; + }, + { + name: "daoCreator"; + type: "publicKey"; + }, + { + name: "pdaBump"; + type: "u8"; + }, + { + name: "squadsMultisig"; + type: "publicKey"; + }, + { + name: "squadsMultisigVault"; + type: "publicKey"; + }, + { + name: "baseMint"; + type: "publicKey"; + }, + { + name: "quoteMint"; + type: "publicKey"; + }, + { + name: "proposalCount"; + type: "u32"; + }, + { + name: "passThresholdBps"; + type: "u16"; + }, + { + name: "secondsPerProposal"; + type: "u32"; + }, + { + name: "twapInitialObservation"; + docs: [ + "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.", + ]; + type: "u128"; + }, + { + name: "twapMaxObservationChangePerUpdate"; + type: "u128"; + }, + { + name: "twapStartDelaySeconds"; + docs: [ + "Forces TWAP calculation to start after `twap_start_delay_seconds` seconds", + ]; + type: "u32"; + }, + { + name: "minQuoteFutarchicLiquidity"; + docs: [ + "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).", + ]; + type: "u64"; + }, + { + name: "minBaseFutarchicLiquidity"; + type: "u64"; + }, + { + name: "baseToStake"; + docs: [ + "Minimum amount of base tokens that must be staked to launch a proposal", + ]; + type: "u64"; + }, + { + name: "seqNum"; + type: "u64"; + }, + { + name: "initialSpendingLimit"; + type: { + option: { + defined: "InitialSpendingLimit"; + }; + }; + }, + { + name: "teamSponsoredPassThresholdBps"; + docs: [ + "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.", + ]; + type: "i16"; + }, + { + name: "teamAddress"; + type: "publicKey"; + }, + ]; + }; + }, { name: "proposal"; type: { @@ -3877,6 +4034,27 @@ export const IDL: Futarchy = { }, ], }, + { + name: "resizeDao", + accounts: [ + { + name: "dao", + isMut: true, + isSigner: false, + }, + { + name: "payer", + isMut: true, + isSigner: true, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, { name: "spotSwap", accounts: [ @@ -4835,6 +5013,142 @@ export const IDL: Futarchy = { ], }, }, + { + name: "oldDao", + type: { + kind: "struct", + fields: [ + { + name: "amm", + docs: ["Embedded FutarchyAmm - 1:1 relationship"], + type: { + defined: "FutarchyAmm", + }, + }, + { + name: "nonce", + docs: ["`nonce` + `dao_creator` are PDA seeds"], + type: "u64", + }, + { + name: "daoCreator", + type: "publicKey", + }, + { + name: "pdaBump", + type: "u8", + }, + { + name: "squadsMultisig", + type: "publicKey", + }, + { + name: "squadsMultisigVault", + type: "publicKey", + }, + { + name: "baseMint", + type: "publicKey", + }, + { + name: "quoteMint", + type: "publicKey", + }, + { + name: "proposalCount", + type: "u32", + }, + { + name: "passThresholdBps", + type: "u16", + }, + { + name: "secondsPerProposal", + type: "u32", + }, + { + name: "twapInitialObservation", + docs: [ + "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.", + ], + type: "u128", + }, + { + name: "twapMaxObservationChangePerUpdate", + type: "u128", + }, + { + name: "twapStartDelaySeconds", + docs: [ + "Forces TWAP calculation to start after `twap_start_delay_seconds` seconds", + ], + type: "u32", + }, + { + name: "minQuoteFutarchicLiquidity", + docs: [ + "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).", + ], + type: "u64", + }, + { + name: "minBaseFutarchicLiquidity", + type: "u64", + }, + { + name: "baseToStake", + docs: [ + "Minimum amount of base tokens that must be staked to launch a proposal", + ], + type: "u64", + }, + { + name: "seqNum", + type: "u64", + }, + { + name: "initialSpendingLimit", + type: { + option: { + defined: "InitialSpendingLimit", + }, + }, + }, + { + name: "teamSponsoredPassThresholdBps", + docs: [ + "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.", + ], + type: "i16", + }, + { + name: "teamAddress", + type: "publicKey", + }, + ], + }, + }, { name: "proposal", type: {