diff --git a/programs/futarchy/src/instructions/admin_fix_position_authority.rs b/programs/futarchy/src/instructions/admin_fix_position_authority.rs deleted file mode 100644 index d0bcab21b..000000000 --- a/programs/futarchy/src/instructions/admin_fix_position_authority.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::*; - -pub mod admin { - use anchor_lang::prelude::declare_id; - // MetaDAO multisig vault - declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf"); -} - -pub mod v07_launchpad { - use anchor_lang::prelude::declare_id; - declare_id!("moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM"); -} - -pub mod v06_launchpad { - use anchor_lang::prelude::declare_id; - declare_id!("MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV"); -} - -#[derive(Accounts)] -#[event_cpi] -pub struct AdminFixPositionAuthority<'info> { - #[account(mut)] - pub dao: Box>, - #[account( - mut, - seeds = [b"amm_position", dao.key().as_ref(), dao.squads_multisig_vault.as_ref()], - bump, - has_one = dao, - )] - pub amm_position: Box>, - #[account(mut)] - pub admin: Signer<'info>, -} - -impl AdminFixPositionAuthority<'_> { - pub fn validate(&self) -> Result<()> { - #[cfg(feature = "production")] - require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin); - - // Derive v0.7 launch signer - let (v07_launch, _) = Pubkey::find_program_address( - &[b"launch", self.dao.base_mint.as_ref()], - &v07_launchpad::ID, - ); - let (v07_launch_signer, _) = Pubkey::find_program_address( - &[b"launch_signer", v07_launch.as_ref()], - &v07_launchpad::ID, - ); - - // Derive v0.6 launch signer - let (v06_launch, _) = Pubkey::find_program_address( - &[b"launch", self.dao.base_mint.as_ref()], - &v06_launchpad::ID, - ); - let (v06_launch_signer, _) = Pubkey::find_program_address( - &[b"launch_signer", v06_launch.as_ref()], - &v06_launchpad::ID, - ); - - // Verify current authority is a known launch signer (confirms bug-affected position) - require!( - self.amm_position.position_authority == v07_launch_signer - || self.amm_position.position_authority == v06_launch_signer, - FutarchyError::AssertFailed - ); - - Ok(()) - } - - pub fn handle(ctx: Context) -> Result<()> { - let dao = &mut ctx.accounts.dao; - let amm_position = &mut ctx.accounts.amm_position; - - let old_authority = amm_position.position_authority; - amm_position.position_authority = dao.squads_multisig_vault; - - dao.seq_num += 1; - let clock = Clock::get()?; - - emit_cpi!(AdminFixPositionAuthorityEvent { - common: CommonFields::new(&clock, dao.seq_num), - dao: dao.key(), - admin: ctx.accounts.admin.key(), - amm_position: ctx.accounts.amm_position.key(), - old_authority, - new_authority: dao.squads_multisig_vault, - }); - - Ok(()) - } -} diff --git a/programs/futarchy/src/instructions/mod.rs b/programs/futarchy/src/instructions/mod.rs index 1be7e9e16..3c32078ba 100644 --- a/programs/futarchy/src/instructions/mod.rs +++ b/programs/futarchy/src/instructions/mod.rs @@ -2,7 +2,6 @@ use super::*; pub mod admin_approve_execute_multisig_proposal; pub mod admin_cancel_proposal; -pub mod admin_fix_position_authority; pub mod admin_remove_proposal; pub mod collect_fees; pub mod collect_meteora_damm_fees; @@ -22,7 +21,6 @@ pub mod withdraw_liquidity; pub use admin_approve_execute_multisig_proposal::*; pub use admin_cancel_proposal::*; -pub use admin_fix_position_authority::*; pub use admin_remove_proposal::*; pub use collect_fees::*; pub use collect_meteora_damm_fees::*; diff --git a/programs/futarchy/src/lib.rs b/programs/futarchy/src/lib.rs index b647f40a1..b060cb11e 100644 --- a/programs/futarchy/src/lib.rs +++ b/programs/futarchy/src/lib.rs @@ -161,11 +161,6 @@ pub mod futarchy { AdminCancelProposal::handle(ctx) } - #[access_control(ctx.accounts.validate())] - pub fn admin_fix_position_authority(ctx: Context) -> Result<()> { - AdminFixPositionAuthority::handle(ctx) - } - #[access_control(ctx.accounts.validate())] pub fn admin_remove_proposal(ctx: Context) -> Result<()> { AdminRemoveProposal::handle(ctx) diff --git a/scripts/v0.7/auditLiquidityPositionAuthorities.ts b/scripts/v0.7/auditLiquidityPositionAuthorities.ts deleted file mode 100644 index 2f3af7de4..000000000 --- a/scripts/v0.7/auditLiquidityPositionAuthorities.ts +++ /dev/null @@ -1,193 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import { - FUTARCHY_PROGRAM_ID, - CONDITIONAL_VAULT_PROGRAM_ID, - LAUNCHPAD_PROGRAM_ID, - FutarchyClient, - getLaunchAddr, - getLaunchSignerAddr, -} from "@metadaoproject/futarchy/v0.7"; -import { LAUNCHPAD_PROGRAM_ID as V06_LAUNCHPAD_PROGRAM_ID } from "@metadaoproject/futarchy/v0.6"; -import { PublicKey } from "@solana/web3.js"; -import bs58 from "bs58"; - -const provider = anchor.AnchorProvider.env(); - -const futarchy: FutarchyClient = new FutarchyClient( - provider, - FUTARCHY_PROGRAM_ID, - CONDITIONAL_VAULT_PROGRAM_ID, - [], -); - -function getDiscriminator(accountName: string): Buffer { - return Buffer.from( - anchor.BorshAccountsCoder.accountDiscriminator(accountName), - ); -} - -async function main() { - // 1. Fetch all DAO accounts - console.log("Fetching all DAO accounts..."); - const daoDiscriminator = getDiscriminator("Dao"); - const daoAccounts = await provider.connection.getProgramAccounts( - futarchy.autocrat.programId, - { - filters: [ - { - memcmp: { - offset: 0, - bytes: bs58.encode(daoDiscriminator), - }, - }, - ], - }, - ); - console.log(`Found ${daoAccounts.length} DAOs`); - - // Build map: DAO pubkey -> { squadsMultisigVault, v07LaunchSigner, v06LaunchSigner } - const daoMap = new Map< - string, - { - squadsMultisigVault: PublicKey; - v07LaunchSigner: PublicKey; - v06LaunchSigner: PublicKey; - } - >(); - - for (const { pubkey, account } of daoAccounts) { - const dao = futarchy.autocrat.coder.accounts.decode("dao", account.data); - const [v07Launch] = getLaunchAddr(LAUNCHPAD_PROGRAM_ID, dao.baseMint); - const [v07LaunchSigner] = getLaunchSignerAddr( - LAUNCHPAD_PROGRAM_ID, - v07Launch, - ); - const [v06Launch] = getLaunchAddr(V06_LAUNCHPAD_PROGRAM_ID, dao.baseMint); - const [v06LaunchSigner] = getLaunchSignerAddr( - V06_LAUNCHPAD_PROGRAM_ID, - v06Launch, - ); - daoMap.set(pubkey.toBase58(), { - squadsMultisigVault: dao.squadsMultisigVault, - v07LaunchSigner, - v06LaunchSigner, - }); - } - - // 2. Fetch all AmmPosition accounts - console.log("Fetching all AmmPosition accounts..."); - const positionDiscriminator = getDiscriminator("AmmPosition"); - const positionAccounts = await provider.connection.getProgramAccounts( - futarchy.autocrat.programId, - { - filters: [ - { - memcmp: { - offset: 0, - bytes: bs58.encode(positionDiscriminator), - }, - }, - ], - }, - ); - console.log(`Found ${positionAccounts.length} AmmPositions\n`); - - // 3. Compare each position's authority against its DAO's squadsMultisigVault - // AND verify whether the position's PDA was derived from squadsMultisigVault - let matchCount = 0; - let v07LaunchSignerCount = 0; - let v06LaunchSignerCount = 0; - let unknownCount = 0; - let derivedFromVaultCount = 0; - let notDerivedFromVaultCount = 0; - - for (const { pubkey, account } of positionAccounts) { - const position = futarchy.autocrat.coder.accounts.decode( - "ammPosition", - account.data, - ); - - const daoPubkey = (position.dao as PublicKey).toBase58(); - const daoInfo = daoMap.get(daoPubkey); - - const positionAuthority = ( - position.positionAuthority as PublicKey - ).toBase58(); - const expectedAuthority = daoInfo - ? daoInfo.squadsMultisigVault.toBase58() - : "DAO NOT FOUND"; - const v07LaunchSigner = daoInfo - ? daoInfo.v07LaunchSigner.toBase58() - : "DAO NOT FOUND"; - const v06LaunchSigner = daoInfo - ? daoInfo.v06LaunchSigner.toBase58() - : "DAO NOT FOUND"; - - // Derive the expected PDA using dao.squadsMultisigVault as position authority - let derivedFromVault = false; - let expectedPda = "DAO NOT FOUND"; - if (daoInfo) { - const [pda] = PublicKey.findProgramAddressSync( - [ - Buffer.from("amm_position"), - new PublicKey(daoPubkey).toBuffer(), - daoInfo.squadsMultisigVault.toBuffer(), - ], - FUTARCHY_PROGRAM_ID, - ); - expectedPda = pda.toBase58(); - derivedFromVault = pubkey.toBase58() === expectedPda; - if (derivedFromVault) { - derivedFromVaultCount++; - } else { - notDerivedFromVaultCount++; - } - } - - let status: string; - if (positionAuthority === expectedAuthority) { - status = "OK (squads multisig vault)"; - matchCount++; - } else if (positionAuthority === v07LaunchSigner) { - status = - "V0.7 LAUNCH SIGNER (current authority is the v0.7 launch signer)"; - v07LaunchSignerCount++; - } else if (positionAuthority === v06LaunchSigner) { - status = - "V0.6 LAUNCH SIGNER (current authority is the v0.6 launch signer)"; - v06LaunchSignerCount++; - } else { - status = "UNKNOWN *** MISMATCH ***"; - unknownCount++; - } - - console.log(`Position: ${pubkey.toBase58()}`); - console.log(` DAO: ${daoPubkey}`); - console.log(` Position Authority: ${positionAuthority}`); - console.log(` Expected Authority: ${expectedAuthority}`); - console.log(` v0.7 Launch Signer: ${v07LaunchSigner}`); - console.log(` v0.6 Launch Signer: ${v06LaunchSigner}`); - console.log( - ` Derived from vault: ${derivedFromVault ? "YES" : "NO"} (expected PDA: ${expectedPda})`, - ); - console.log(` Belongs to: ${status}`); - console.log(); - } - - // 4. Summary - console.log("=== Summary ==="); - console.log(`Total positions: ${positionAccounts.length}`); - console.log(`Squads vault (OK): ${matchCount}`); - console.log(`v0.7 launch signer: ${v07LaunchSignerCount}`); - console.log(`v0.6 launch signer: ${v06LaunchSignerCount}`); - console.log(`Unknown mismatch: ${unknownCount}`); - console.log(); - console.log("=== PDA Derivation Check ==="); - console.log(`Derived from vault: ${derivedFromVaultCount}`); - console.log(`NOT derived from vault: ${notDerivedFromVaultCount}`); -} - -main().catch((error) => { - console.error("Fatal error:", error); - process.exit(1); -}); diff --git a/scripts/v0.7/fixPositionAuthorities.ts b/scripts/v0.7/fixPositionAuthorities.ts deleted file mode 100644 index 13cf16640..000000000 --- a/scripts/v0.7/fixPositionAuthorities.ts +++ /dev/null @@ -1,269 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import * as multisig from "@sqds/multisig"; -import { - FUTARCHY_PROGRAM_ID, - CONDITIONAL_VAULT_PROGRAM_ID, - LAUNCHPAD_PROGRAM_ID, - FutarchyClient, - getLaunchAddr, - getLaunchSignerAddr, - METADAO_MULTISIG_VAULT, -} from "@metadaoproject/futarchy/v0.7"; -import { LAUNCHPAD_PROGRAM_ID as V06_LAUNCHPAD_PROGRAM_ID } from "@metadaoproject/futarchy/v0.6"; -import { PublicKey, TransactionMessage } from "@solana/web3.js"; -import bs58 from "bs58"; - -const provider = anchor.AnchorProvider.env(); - -const payer = provider.wallet["payer"]; - -const futarchy: FutarchyClient = new FutarchyClient( - provider, - FUTARCHY_PROGRAM_ID, - CONDITIONAL_VAULT_PROGRAM_ID, - [], -); - -const metadaoSquadsMultisig = new PublicKey( - "8N3Tvc6B1wEVKVC6iD4s6eyaCNqX2ovj2xze2q3Q9DWH", -); -const metadaoSquadsMultisigVault = METADAO_MULTISIG_VAULT; - -const BATCH_SIZE = 10; - -function getDiscriminator(accountName: string): Buffer { - return Buffer.from( - anchor.BorshAccountsCoder.accountDiscriminator(accountName), - ); -} - -async function main() { - // 1. Fetch all DAO accounts - console.log("Fetching all DAO accounts..."); - const daoDiscriminator = getDiscriminator("Dao"); - const daoAccounts = await provider.connection.getProgramAccounts( - futarchy.autocrat.programId, - { - filters: [ - { - memcmp: { - offset: 0, - bytes: bs58.encode(daoDiscriminator), - }, - }, - ], - }, - ); - console.log(`Found ${daoAccounts.length} DAOs`); - - // Build map: DAO pubkey -> { squadsMultisigVault, v07LaunchSigner, v06LaunchSigner } - const daoMap = new Map< - string, - { - squadsMultisigVault: PublicKey; - v07LaunchSigner: PublicKey; - v06LaunchSigner: PublicKey; - } - >(); - - for (const { pubkey, account } of daoAccounts) { - const dao = futarchy.autocrat.coder.accounts.decode("dao", account.data); - const [v07Launch] = getLaunchAddr(LAUNCHPAD_PROGRAM_ID, dao.baseMint); - const [v07LaunchSigner] = getLaunchSignerAddr( - LAUNCHPAD_PROGRAM_ID, - v07Launch, - ); - const [v06Launch] = getLaunchAddr(V06_LAUNCHPAD_PROGRAM_ID, dao.baseMint); - const [v06LaunchSigner] = getLaunchSignerAddr( - V06_LAUNCHPAD_PROGRAM_ID, - v06Launch, - ); - daoMap.set(pubkey.toBase58(), { - squadsMultisigVault: dao.squadsMultisigVault, - v07LaunchSigner, - v06LaunchSigner, - }); - } - - // 2. Fetch all AmmPosition accounts - console.log("Fetching all AmmPosition accounts..."); - const positionDiscriminator = getDiscriminator("AmmPosition"); - const positionAccounts = await provider.connection.getProgramAccounts( - futarchy.autocrat.programId, - { - filters: [ - { - memcmp: { - offset: 0, - bytes: bs58.encode(positionDiscriminator), - }, - }, - ], - }, - ); - console.log(`Found ${positionAccounts.length} AmmPositions\n`); - - // 3. Filter to affected positions - const affectedDaos: { daoPubkey: PublicKey; positionPubkey: PublicKey }[] = - []; - - for (const { pubkey, account } of positionAccounts) { - const position = futarchy.autocrat.coder.accounts.decode( - "ammPosition", - account.data, - ); - - const daoPubkey = (position.dao as PublicKey).toBase58(); - const daoInfo = daoMap.get(daoPubkey); - if (!daoInfo) continue; - - // Check if this position's address was derived from dao + squadsMultisigVault - const [expectedPda] = PublicKey.findProgramAddressSync( - [ - Buffer.from("amm_position"), - new PublicKey(daoPubkey).toBuffer(), - daoInfo.squadsMultisigVault.toBuffer(), - ], - FUTARCHY_PROGRAM_ID, - ); - const derivedFromVault = pubkey.toBase58() === expectedPda.toBase58(); - if (!derivedFromVault) continue; - - // Check if current authority is a launch signer (i.e. bug-affected) - const positionAuthority = ( - position.positionAuthority as PublicKey - ).toBase58(); - const isV07LaunchSigner = - positionAuthority === daoInfo.v07LaunchSigner.toBase58(); - const isV06LaunchSigner = - positionAuthority === daoInfo.v06LaunchSigner.toBase58(); - - if (!isV07LaunchSigner && !isV06LaunchSigner) continue; - - const version = isV07LaunchSigner ? "v0.7" : "v0.6"; - console.log(`Affected position: ${pubkey.toBase58()}`); - console.log(` DAO: ${daoPubkey}`); - console.log( - ` Current authority: ${positionAuthority} (${version} launch signer)`, - ); - console.log( - ` Expected authority: ${daoInfo.squadsMultisigVault.toBase58()}`, - ); - console.log(); - - affectedDaos.push({ - daoPubkey: new PublicKey(daoPubkey), - positionPubkey: pubkey, - }); - } - - if (affectedDaos.length === 0) { - console.log("No affected positions found. Nothing to fix."); - return; - } - - // 4. Build fix instructions - console.log( - `Building fix instructions for ${affectedDaos.length} affected positions...`, - ); - const instructions = []; - for (const { daoPubkey } of affectedDaos) { - const ix = await futarchy - .adminFixPositionAuthorityIx({ - dao: daoPubkey, - admin: metadaoSquadsMultisigVault, - }) - .instruction(); - instructions.push(ix); - } - - // 5. Batch into groups - const batches = []; - for (let i = 0; i < instructions.length; i += BATCH_SIZE) { - batches.push(instructions.slice(i, i + BATCH_SIZE)); - } - - // 6. Output base64 messages for inspection - console.log( - `\n=== Base64 Transaction Messages (${batches.length} batches) ===\n`, - ); - for (let i = 0; i < batches.length; i++) { - const message = new TransactionMessage({ - payerKey: metadaoSquadsMultisigVault, - recentBlockhash: (await provider.connection.getLatestBlockhash()) - .blockhash, - instructions: batches[i], - }); - const compiled = message.compileToLegacyMessage(); - const base64 = Buffer.from(compiled.serialize()).toString("base64"); - console.log(`Batch ${i + 1} (${batches[i].length} instructions):`); - console.log(base64); - console.log(); - } - - // 7. Summary - console.log("=== Summary ==="); - console.log(`Total affected positions: ${affectedDaos.length}`); - console.log(`Number of batches: ${batches.length}`); - console.log(`DAOs involved:`); - const uniqueDaos = new Set(affectedDaos.map((a) => a.daoPubkey.toBase58())); - for (const dao of uniqueDaos) { - console.log(` - ${dao}`); - } - - // 8. Safety gate - console.log( - "\nReturning early. Uncomment code and remove the return below to create Squads proposals.", - ); - return; - - // // 9. Create Squads vault transactions + proposals - // const squadsMultisigAccount = - // await multisig.accounts.Multisig.fromAccountAddress( - // provider.connection, - // metadaoSquadsMultisig, - // ); - // let txIndex = - // BigInt(squadsMultisigAccount.transactionIndex.toString()) + 1n; - - // for (let i = 0; i < batches.length; i++) { - // const transactionMessage = new TransactionMessage({ - // payerKey: metadaoSquadsMultisigVault, - // recentBlockhash: (await provider.connection.getLatestBlockhash()) - // .blockhash, - // instructions: batches[i], - // }); - - // // Create vault transaction - // const vaultTxSig = await multisig.rpc.vaultTransactionCreate({ - // connection: provider.connection, - // creator: payer.publicKey, - // feePayer: payer.publicKey, - // ephemeralSigners: 0, - // multisigPda: metadaoSquadsMultisig, - // transactionIndex: txIndex, - // vaultIndex: 0, - // transactionMessage, - // }); - // console.log(`Vault tx ${txIndex} (batch ${i + 1}): ${vaultTxSig}`); - - // // Create proposal - // const proposalSig = await multisig.rpc.proposalCreate({ - // connection: provider.connection, - // creator: payer.publicKey, - // feePayer: payer.publicKey, - // multisigPda: metadaoSquadsMultisig, - // transactionIndex: txIndex, - // }); - // console.log(`Proposal ${txIndex} (batch ${i + 1}): ${proposalSig}`); - - // txIndex++; - // } - - // console.log("\nAll Squads proposals created."); -} - -main().catch((error) => { - console.error("Fatal error:", error); - process.exit(1); -}); diff --git a/sdk/src/v0.7/FutarchyClient.ts b/sdk/src/v0.7/FutarchyClient.ts index aa38bcc29..0f22606f6 100644 --- a/sdk/src/v0.7/FutarchyClient.ts +++ b/sdk/src/v0.7/FutarchyClient.ts @@ -1004,35 +1004,6 @@ export class FutarchyClient { }); } - adminFixPositionAuthorityIx({ - dao, - admin = this.provider.publicKey, - }: { - dao: PublicKey; - admin?: PublicKey; - }) { - const multisigPda = multisig.getMultisigPda({ createKey: dao })[0]; - const squadsMultisigVault = multisig.getVaultPda({ - multisigPda, - index: 0, - })[0]; - - const ammPosition = PublicKey.findProgramAddressSync( - [ - Buffer.from("amm_position"), - dao.toBuffer(), - squadsMultisigVault.toBuffer(), - ], - this.getProgramId(), - )[0]; - - return this.autocrat.methods.adminFixPositionAuthority().accounts({ - dao, - ammPosition, - admin, - }); - } - collectMeteoraDammFeesIx({ dao, baseMint, diff --git a/sdk/src/v0.7/types/futarchy.ts b/sdk/src/v0.7/types/futarchy.ts index 659948bd5..92b38e277 100644 --- a/sdk/src/v0.7/types/futarchy.ts +++ b/sdk/src/v0.7/types/futarchy.ts @@ -1367,37 +1367,6 @@ export type Futarchy = { ]; args: []; }, - { - name: "adminFixPositionAuthority"; - accounts: [ - { - name: "dao"; - isMut: true; - isSigner: false; - }, - { - name: "ammPosition"; - isMut: true; - isSigner: false; - }, - { - name: "admin"; - isMut: true; - isSigner: true; - }, - { - name: "eventAuthority"; - isMut: false; - isSigner: false; - }, - { - name: "program"; - isMut: false; - isSigner: false; - }, - ]; - args: []; - }, { name: "adminRemoveProposal"; accounts: [ @@ -4656,37 +4625,6 @@ export const IDL: Futarchy = { ], args: [], }, - { - name: "adminFixPositionAuthority", - accounts: [ - { - name: "dao", - isMut: true, - isSigner: false, - }, - { - name: "ammPosition", - isMut: true, - isSigner: false, - }, - { - name: "admin", - isMut: true, - isSigner: true, - }, - { - name: "eventAuthority", - isMut: false, - isSigner: false, - }, - { - name: "program", - isMut: false, - isSigner: false, - }, - ], - args: [], - }, { name: "adminRemoveProposal", accounts: [ diff --git a/tests/futarchy/main.test.ts b/tests/futarchy/main.test.ts index b64add9fc..1cbf4671c 100644 --- a/tests/futarchy/main.test.ts +++ b/tests/futarchy/main.test.ts @@ -15,7 +15,6 @@ import executeSpendingLimitChange from "./unit/executeSpendingLimitChange.test.j import collectMeteoraDammFees from "./unit/collectMeteoraDammFees.test.js"; import adminApproveProposal from "./unit/adminApproveExecuteMultisigProposal.test.js"; import adminCancelProposal from "./unit/adminCancelProposal.test.js"; -import adminFixPositionAuthority from "./unit/adminFixPositionAuthority.test.js"; import adminRemoveProposal from "./unit/adminRemoveProposal.test.js"; import { PublicKey } from "@solana/web3.js"; @@ -63,7 +62,6 @@ export default function suite() { describe("#admin_approve_proposal", adminApproveProposal); describe("#admin_cancel_proposal", adminCancelProposal); - describe("#admin_fix_position_authority", adminFixPositionAuthority); describe("#admin_remove_proposal", adminRemoveProposal); // describe("full proposal", fullProposal); // describe("proposal with a squads batch tx", proposalBatchTx); diff --git a/tests/futarchy/unit/adminFixPositionAuthority.test.ts b/tests/futarchy/unit/adminFixPositionAuthority.test.ts deleted file mode 100644 index 1ccaf811e..000000000 --- a/tests/futarchy/unit/adminFixPositionAuthority.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js"; -import BN from "bn.js"; -import { assert } from "chai"; -import { - FUTARCHY_PROGRAM_ID, - LAUNCHPAD_PROGRAM_ID, -} from "@metadaoproject/futarchy/v0.7"; -import { LAUNCHPAD_PROGRAM_ID as V06_LAUNCHPAD_PROGRAM_ID } from "@metadaoproject/futarchy/v0.6"; -import { - getAssociatedTokenAddressSync, - TOKEN_PROGRAM_ID, -} from "@solana/spl-token"; -import { expectError } from "../../utils.js"; -import * as multisig from "@sqds/multisig"; - -export default function suite() { - let META: PublicKey, USDC: PublicKey, dao: PublicKey; - - beforeEach(async function () { - META = await this.createMint(this.payer.publicKey, 6); - USDC = await this.createMint(this.payer.publicKey, 6); - - await this.mintTo(USDC, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - await this.mintTo(META, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - - dao = await this.setupBasicDaoWithLiquidity({ - baseMint: META, - quoteMint: USDC, - }); - }); - - it("fixes corrupted position_authority to squads vault", async function () { - const storedDao = await this.futarchy.getDao(dao); - const multisigPda = multisig.getMultisigPda({ createKey: dao })[0]; - const squadsMultisigVault = multisig.getVaultPda({ - multisigPda, - index: 0, - })[0]; - - // Provide liquidity with positionAuthority = squadsMultisigVault - // This creates the AmmPosition PDA derived from squadsMultisigVault - await this.mintTo(META, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - await this.mintTo(USDC, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - - await this.futarchy - .provideLiquidityIx({ - dao, - baseMint: META, - quoteMint: USDC, - quoteAmount: new BN(100 * 10 ** 6), - maxBaseAmount: new BN(200 * 10 ** 6), - minLiquidity: new BN(1), - positionAuthority: squadsMultisigVault, - liquidityProvider: this.payer.publicKey, - }) - .rpc(); - - // Derive the amm_position PDA - const [ammPositionPda] = PublicKey.findProgramAddressSync( - [ - Buffer.from("amm_position"), - dao.toBuffer(), - squadsMultisigVault.toBuffer(), - ], - FUTARCHY_PROGRAM_ID, - ); - - // Verify the position was created correctly (current code is fixed) - const positionBefore = - await this.futarchy.autocrat.account.ammPosition.fetch(ammPositionPda); - assert.isTrue(positionBefore.positionAuthority.equals(squadsMultisigVault)); - - // Now simulate the bug: overwrite position_authority with the v0.7 launch signer - const [v07Launch] = PublicKey.findProgramAddressSync( - [Buffer.from("launch"), META.toBuffer()], - LAUNCHPAD_PROGRAM_ID, - ); - const [v07LaunchSigner] = PublicKey.findProgramAddressSync( - [Buffer.from("launch_signer"), v07Launch.toBuffer()], - LAUNCHPAD_PROGRAM_ID, - ); - - // Read raw account data and corrupt the position_authority field - const rawAccount = await this.banksClient.getAccount(ammPositionPda); - const data = Buffer.from(rawAccount.data); - - // AmmPosition layout: 8 (discriminator) + 32 (dao) + 32 (position_authority) + 16 (liquidity) - // position_authority starts at offset 40 - data.set(v07LaunchSigner.toBuffer(), 40); - - this.context.setAccount(ammPositionPda, { - ...rawAccount, - data, - }); - - // Verify the corruption - const positionCorrupted = - await this.futarchy.autocrat.account.ammPosition.fetch(ammPositionPda); - assert.isTrue(positionCorrupted.positionAuthority.equals(v07LaunchSigner)); - - const daoBefore = await this.futarchy.getDao(dao); - const seqNumBefore = daoBefore.seqNum.toNumber(); - - // Call admin_fix_position_authority to fix it - await this.futarchy.adminFixPositionAuthorityIx({ dao }).rpc(); - - // Verify the fix - const positionAfter = - await this.futarchy.autocrat.account.ammPosition.fetch(ammPositionPda); - assert.isTrue(positionAfter.positionAuthority.equals(squadsMultisigVault)); - - // Verify seq_num incremented - const storedDaoAfter = await this.futarchy.getDao(dao); - assert.equal(storedDaoAfter.seqNum.toNumber(), seqNumBefore + 1); - - // Verify liquidity is untouched - assert.isTrue(positionAfter.liquidity.eq(positionBefore.liquidity)); - }); - - it("rejects when position_authority is not a recognized launch signer", async function () { - const multisigPda = multisig.getMultisigPda({ createKey: dao })[0]; - const squadsMultisigVault = multisig.getVaultPda({ - multisigPda, - index: 0, - })[0]; - - // Provide liquidity with positionAuthority = squadsMultisigVault - await this.mintTo(META, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - await this.mintTo(USDC, this.payer.publicKey, this.payer, 1000 * 10 ** 6); - - await this.futarchy - .provideLiquidityIx({ - dao, - baseMint: META, - quoteMint: USDC, - quoteAmount: new BN(100 * 10 ** 6), - maxBaseAmount: new BN(200 * 10 ** 6), - minLiquidity: new BN(1), - positionAuthority: squadsMultisigVault, - liquidityProvider: this.payer.publicKey, - }) - .rpc(); - - // The position_authority is correctly set to squadsMultisigVault (not a launch signer), - // so the fix instruction should reject it - const callbacks = expectError( - "AssertFailed", - "should reject when position_authority is not a launch signer", - ); - - await this.futarchy - .adminFixPositionAuthorityIx({ dao }) - .rpc() - .then(callbacks[0], callbacks[1]); - }); -}