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 @@ -62,6 +62,8 @@ v07-close-launch = "yarn run tsx scripts/v0.7/closeLaunch.ts"
v07-initialize-performance-package = "yarn run tsx scripts/v0.7/initializePerformancePackage.ts"
v07-claim-launch-additional-tokens = "yarn run tsx scripts/v0.7/claimLaunchAdditionalTokens.ts"
v07-remove-proposal = "yarn run tsx scripts/v0.7/removeProposal.ts"
v07-audit-liquidity-position-authorities = "yarn run tsx scripts/v0.7/auditLiquidityPositionAuthorities.ts"
v07-fix-position-authorities = "yarn run tsx scripts/v0.7/fixPositionAuthorities.ts"

[test]
startup_wait = 5000
Expand Down
10 changes: 10 additions & 0 deletions programs/futarchy/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,13 @@ pub struct CollectMeteoraDammFeesEvent {
pub quote_fees_collected: u64,
pub base_fees_collected: u64,
}

#[event]
pub struct AdminFixPositionAuthorityEvent {
pub common: CommonFields,
pub dao: Pubkey,
pub admin: Pubkey,
pub amm_position: Pubkey,
pub old_authority: Pubkey,
pub new_authority: Pubkey,
}
91 changes: 91 additions & 0 deletions programs/futarchy/src/instructions/admin_fix_position_authority.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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<'info, Dao>>,
#[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<'info, AmmPosition>>,
#[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<Self>) -> 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(())
}
}
2 changes: 2 additions & 0 deletions programs/futarchy/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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;
Expand All @@ -21,6 +22,7 @@ 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::*;
Expand Down
5 changes: 5 additions & 0 deletions programs/futarchy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ pub mod futarchy {
AdminCancelProposal::handle(ctx)
}

#[access_control(ctx.accounts.validate())]
pub fn admin_fix_position_authority(ctx: Context<AdminFixPositionAuthority>) -> Result<()> {
AdminFixPositionAuthority::handle(ctx)
}

#[access_control(ctx.accounts.validate())]
pub fn admin_remove_proposal(ctx: Context<AdminRemoveProposal>) -> Result<()> {
AdminRemoveProposal::handle(ctx)
Expand Down
193 changes: 193 additions & 0 deletions scripts/v0.7/auditLiquidityPositionAuthorities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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);
});
Loading
Loading