Skip to content

Commit aa0f7d5

Browse files
authored
add ix for lp position fix (#423)
* add ix for lp position fix * add scripts for liquidity fix
1 parent ff0a79e commit aa0f7d5

File tree

11 files changed

+895
-0
lines changed

11 files changed

+895
-0
lines changed

Anchor.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ v07-close-launch = "yarn run tsx scripts/v0.7/closeLaunch.ts"
6262
v07-initialize-performance-package = "yarn run tsx scripts/v0.7/initializePerformancePackage.ts"
6363
v07-claim-launch-additional-tokens = "yarn run tsx scripts/v0.7/claimLaunchAdditionalTokens.ts"
6464
v07-remove-proposal = "yarn run tsx scripts/v0.7/removeProposal.ts"
65+
v07-audit-liquidity-position-authorities = "yarn run tsx scripts/v0.7/auditLiquidityPositionAuthorities.ts"
66+
v07-fix-position-authorities = "yarn run tsx scripts/v0.7/fixPositionAuthorities.ts"
6567

6668
[test]
6769
startup_wait = 5000

programs/futarchy/src/events.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,13 @@ pub struct CollectMeteoraDammFeesEvent {
220220
pub quote_fees_collected: u64,
221221
pub base_fees_collected: u64,
222222
}
223+
224+
#[event]
225+
pub struct AdminFixPositionAuthorityEvent {
226+
pub common: CommonFields,
227+
pub dao: Pubkey,
228+
pub admin: Pubkey,
229+
pub amm_position: Pubkey,
230+
pub old_authority: Pubkey,
231+
pub new_authority: Pubkey,
232+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use super::*;
2+
3+
pub mod admin {
4+
use anchor_lang::prelude::declare_id;
5+
// MetaDAO multisig vault
6+
declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf");
7+
}
8+
9+
pub mod v07_launchpad {
10+
use anchor_lang::prelude::declare_id;
11+
declare_id!("moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM");
12+
}
13+
14+
pub mod v06_launchpad {
15+
use anchor_lang::prelude::declare_id;
16+
declare_id!("MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV");
17+
}
18+
19+
#[derive(Accounts)]
20+
#[event_cpi]
21+
pub struct AdminFixPositionAuthority<'info> {
22+
#[account(mut)]
23+
pub dao: Box<Account<'info, Dao>>,
24+
#[account(
25+
mut,
26+
seeds = [b"amm_position", dao.key().as_ref(), dao.squads_multisig_vault.as_ref()],
27+
bump,
28+
has_one = dao,
29+
)]
30+
pub amm_position: Box<Account<'info, AmmPosition>>,
31+
#[account(mut)]
32+
pub admin: Signer<'info>,
33+
}
34+
35+
impl AdminFixPositionAuthority<'_> {
36+
pub fn validate(&self) -> Result<()> {
37+
#[cfg(feature = "production")]
38+
require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin);
39+
40+
// Derive v0.7 launch signer
41+
let (v07_launch, _) = Pubkey::find_program_address(
42+
&[b"launch", self.dao.base_mint.as_ref()],
43+
&v07_launchpad::ID,
44+
);
45+
let (v07_launch_signer, _) = Pubkey::find_program_address(
46+
&[b"launch_signer", v07_launch.as_ref()],
47+
&v07_launchpad::ID,
48+
);
49+
50+
// Derive v0.6 launch signer
51+
let (v06_launch, _) = Pubkey::find_program_address(
52+
&[b"launch", self.dao.base_mint.as_ref()],
53+
&v06_launchpad::ID,
54+
);
55+
let (v06_launch_signer, _) = Pubkey::find_program_address(
56+
&[b"launch_signer", v06_launch.as_ref()],
57+
&v06_launchpad::ID,
58+
);
59+
60+
// Verify current authority is a known launch signer (confirms bug-affected position)
61+
require!(
62+
self.amm_position.position_authority == v07_launch_signer
63+
|| self.amm_position.position_authority == v06_launch_signer,
64+
FutarchyError::AssertFailed
65+
);
66+
67+
Ok(())
68+
}
69+
70+
pub fn handle(ctx: Context<Self>) -> Result<()> {
71+
let dao = &mut ctx.accounts.dao;
72+
let amm_position = &mut ctx.accounts.amm_position;
73+
74+
let old_authority = amm_position.position_authority;
75+
amm_position.position_authority = dao.squads_multisig_vault;
76+
77+
dao.seq_num += 1;
78+
let clock = Clock::get()?;
79+
80+
emit_cpi!(AdminFixPositionAuthorityEvent {
81+
common: CommonFields::new(&clock, dao.seq_num),
82+
dao: dao.key(),
83+
admin: ctx.accounts.admin.key(),
84+
amm_position: ctx.accounts.amm_position.key(),
85+
old_authority,
86+
new_authority: dao.squads_multisig_vault,
87+
});
88+
89+
Ok(())
90+
}
91+
}

programs/futarchy/src/instructions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::*;
22

33
pub mod admin_approve_execute_multisig_proposal;
44
pub mod admin_cancel_proposal;
5+
pub mod admin_fix_position_authority;
56
pub mod admin_remove_proposal;
67
pub mod collect_fees;
78
pub mod collect_meteora_damm_fees;
@@ -21,6 +22,7 @@ pub mod withdraw_liquidity;
2122

2223
pub use admin_approve_execute_multisig_proposal::*;
2324
pub use admin_cancel_proposal::*;
25+
pub use admin_fix_position_authority::*;
2426
pub use admin_remove_proposal::*;
2527
pub use collect_fees::*;
2628
pub use collect_meteora_damm_fees::*;

programs/futarchy/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ pub mod futarchy {
161161
AdminCancelProposal::handle(ctx)
162162
}
163163

164+
#[access_control(ctx.accounts.validate())]
165+
pub fn admin_fix_position_authority(ctx: Context<AdminFixPositionAuthority>) -> Result<()> {
166+
AdminFixPositionAuthority::handle(ctx)
167+
}
168+
164169
#[access_control(ctx.accounts.validate())]
165170
pub fn admin_remove_proposal(ctx: Context<AdminRemoveProposal>) -> Result<()> {
166171
AdminRemoveProposal::handle(ctx)
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import * as anchor from "@coral-xyz/anchor";
2+
import {
3+
FUTARCHY_PROGRAM_ID,
4+
CONDITIONAL_VAULT_PROGRAM_ID,
5+
LAUNCHPAD_PROGRAM_ID,
6+
FutarchyClient,
7+
getLaunchAddr,
8+
getLaunchSignerAddr,
9+
} from "@metadaoproject/futarchy/v0.7";
10+
import { LAUNCHPAD_PROGRAM_ID as V06_LAUNCHPAD_PROGRAM_ID } from "@metadaoproject/futarchy/v0.6";
11+
import { PublicKey } from "@solana/web3.js";
12+
import bs58 from "bs58";
13+
14+
const provider = anchor.AnchorProvider.env();
15+
16+
const futarchy: FutarchyClient = new FutarchyClient(
17+
provider,
18+
FUTARCHY_PROGRAM_ID,
19+
CONDITIONAL_VAULT_PROGRAM_ID,
20+
[],
21+
);
22+
23+
function getDiscriminator(accountName: string): Buffer {
24+
return Buffer.from(
25+
anchor.BorshAccountsCoder.accountDiscriminator(accountName),
26+
);
27+
}
28+
29+
async function main() {
30+
// 1. Fetch all DAO accounts
31+
console.log("Fetching all DAO accounts...");
32+
const daoDiscriminator = getDiscriminator("Dao");
33+
const daoAccounts = await provider.connection.getProgramAccounts(
34+
futarchy.autocrat.programId,
35+
{
36+
filters: [
37+
{
38+
memcmp: {
39+
offset: 0,
40+
bytes: bs58.encode(daoDiscriminator),
41+
},
42+
},
43+
],
44+
},
45+
);
46+
console.log(`Found ${daoAccounts.length} DAOs`);
47+
48+
// Build map: DAO pubkey -> { squadsMultisigVault, v07LaunchSigner, v06LaunchSigner }
49+
const daoMap = new Map<
50+
string,
51+
{
52+
squadsMultisigVault: PublicKey;
53+
v07LaunchSigner: PublicKey;
54+
v06LaunchSigner: PublicKey;
55+
}
56+
>();
57+
58+
for (const { pubkey, account } of daoAccounts) {
59+
const dao = futarchy.autocrat.coder.accounts.decode("dao", account.data);
60+
const [v07Launch] = getLaunchAddr(LAUNCHPAD_PROGRAM_ID, dao.baseMint);
61+
const [v07LaunchSigner] = getLaunchSignerAddr(
62+
LAUNCHPAD_PROGRAM_ID,
63+
v07Launch,
64+
);
65+
const [v06Launch] = getLaunchAddr(V06_LAUNCHPAD_PROGRAM_ID, dao.baseMint);
66+
const [v06LaunchSigner] = getLaunchSignerAddr(
67+
V06_LAUNCHPAD_PROGRAM_ID,
68+
v06Launch,
69+
);
70+
daoMap.set(pubkey.toBase58(), {
71+
squadsMultisigVault: dao.squadsMultisigVault,
72+
v07LaunchSigner,
73+
v06LaunchSigner,
74+
});
75+
}
76+
77+
// 2. Fetch all AmmPosition accounts
78+
console.log("Fetching all AmmPosition accounts...");
79+
const positionDiscriminator = getDiscriminator("AmmPosition");
80+
const positionAccounts = await provider.connection.getProgramAccounts(
81+
futarchy.autocrat.programId,
82+
{
83+
filters: [
84+
{
85+
memcmp: {
86+
offset: 0,
87+
bytes: bs58.encode(positionDiscriminator),
88+
},
89+
},
90+
],
91+
},
92+
);
93+
console.log(`Found ${positionAccounts.length} AmmPositions\n`);
94+
95+
// 3. Compare each position's authority against its DAO's squadsMultisigVault
96+
// AND verify whether the position's PDA was derived from squadsMultisigVault
97+
let matchCount = 0;
98+
let v07LaunchSignerCount = 0;
99+
let v06LaunchSignerCount = 0;
100+
let unknownCount = 0;
101+
let derivedFromVaultCount = 0;
102+
let notDerivedFromVaultCount = 0;
103+
104+
for (const { pubkey, account } of positionAccounts) {
105+
const position = futarchy.autocrat.coder.accounts.decode(
106+
"ammPosition",
107+
account.data,
108+
);
109+
110+
const daoPubkey = (position.dao as PublicKey).toBase58();
111+
const daoInfo = daoMap.get(daoPubkey);
112+
113+
const positionAuthority = (
114+
position.positionAuthority as PublicKey
115+
).toBase58();
116+
const expectedAuthority = daoInfo
117+
? daoInfo.squadsMultisigVault.toBase58()
118+
: "DAO NOT FOUND";
119+
const v07LaunchSigner = daoInfo
120+
? daoInfo.v07LaunchSigner.toBase58()
121+
: "DAO NOT FOUND";
122+
const v06LaunchSigner = daoInfo
123+
? daoInfo.v06LaunchSigner.toBase58()
124+
: "DAO NOT FOUND";
125+
126+
// Derive the expected PDA using dao.squadsMultisigVault as position authority
127+
let derivedFromVault = false;
128+
let expectedPda = "DAO NOT FOUND";
129+
if (daoInfo) {
130+
const [pda] = PublicKey.findProgramAddressSync(
131+
[
132+
Buffer.from("amm_position"),
133+
new PublicKey(daoPubkey).toBuffer(),
134+
daoInfo.squadsMultisigVault.toBuffer(),
135+
],
136+
FUTARCHY_PROGRAM_ID,
137+
);
138+
expectedPda = pda.toBase58();
139+
derivedFromVault = pubkey.toBase58() === expectedPda;
140+
if (derivedFromVault) {
141+
derivedFromVaultCount++;
142+
} else {
143+
notDerivedFromVaultCount++;
144+
}
145+
}
146+
147+
let status: string;
148+
if (positionAuthority === expectedAuthority) {
149+
status = "OK (squads multisig vault)";
150+
matchCount++;
151+
} else if (positionAuthority === v07LaunchSigner) {
152+
status =
153+
"V0.7 LAUNCH SIGNER (current authority is the v0.7 launch signer)";
154+
v07LaunchSignerCount++;
155+
} else if (positionAuthority === v06LaunchSigner) {
156+
status =
157+
"V0.6 LAUNCH SIGNER (current authority is the v0.6 launch signer)";
158+
v06LaunchSignerCount++;
159+
} else {
160+
status = "UNKNOWN *** MISMATCH ***";
161+
unknownCount++;
162+
}
163+
164+
console.log(`Position: ${pubkey.toBase58()}`);
165+
console.log(` DAO: ${daoPubkey}`);
166+
console.log(` Position Authority: ${positionAuthority}`);
167+
console.log(` Expected Authority: ${expectedAuthority}`);
168+
console.log(` v0.7 Launch Signer: ${v07LaunchSigner}`);
169+
console.log(` v0.6 Launch Signer: ${v06LaunchSigner}`);
170+
console.log(
171+
` Derived from vault: ${derivedFromVault ? "YES" : "NO"} (expected PDA: ${expectedPda})`,
172+
);
173+
console.log(` Belongs to: ${status}`);
174+
console.log();
175+
}
176+
177+
// 4. Summary
178+
console.log("=== Summary ===");
179+
console.log(`Total positions: ${positionAccounts.length}`);
180+
console.log(`Squads vault (OK): ${matchCount}`);
181+
console.log(`v0.7 launch signer: ${v07LaunchSignerCount}`);
182+
console.log(`v0.6 launch signer: ${v06LaunchSignerCount}`);
183+
console.log(`Unknown mismatch: ${unknownCount}`);
184+
console.log();
185+
console.log("=== PDA Derivation Check ===");
186+
console.log(`Derived from vault: ${derivedFromVaultCount}`);
187+
console.log(`NOT derived from vault: ${notDerivedFromVaultCount}`);
188+
}
189+
190+
main().catch((error) => {
191+
console.error("Fatal error:", error);
192+
process.exit(1);
193+
});

0 commit comments

Comments
 (0)