-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Desc
When an account uses both init and seeds constraints in Anchor, the generated IDL omits the pda field that should describe the seed derivation. This makes it impossible for client developers to correctly derive the PDA using only the IDL, defeating the purpose of IDL-based client generation.
Expected Behavior
When an account has a seeds constraint, the IDL should include a pda field describing the seed derivation, regardless of whether the account also has an init constraint. This allows client developers to derive the correct PDA address using only the IDL.
Actual Behavior
Accounts with both init and seeds constraints are missing the pda field in the generated IDL, showing only "writable": true.
Reproduction Steps
1. Create a program with PDA account using init + seeds
In this file: https://github.com/MeteoraAg/damm-v2/blob/main/programs/cp-amm/src/instructions/initialize_pool/ix_initialize_customizable_pool.rs
The pool account is defined with both init and seeds constraints:
#[account(
init,
seeds = [
CUSTOMIZABLE_POOL_PREFIX.as_ref(),
&max_key(&token_a_mint.key(), &token_b_mint.key()),
&min_key(&token_a_mint.key(), &token_b_mint.key()),
],
bump,
payer = payer,
space = 8 + Pool::INIT_SPACE
)]
pub pool: AccountLoader<'info, Pool>,
#[account(
init,
seeds = [
POSITION_PREFIX.as_ref(),
position_nft_mint.key().as_ref()
],
bump,
payer = payer,
space = 8 + Position::INIT_SPACE
)]
pub position: AccountLoader<'info, Position>,Where: https://github.com/MeteoraAg/damm-v2/blob/main/programs/cp-amm/src/constants.rs#L152
pub const CUSTOMIZABLE_POOL_PREFIX: &[u8] = b"cpool";
pub const POSITION_PREFIX: &[u8] = b"position";2. Build and generate IDL
anchor build3. Check generated IDL
Expected IDL output for pool account:
{
"name": "pool",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [99, 112, 111, 111, 108]
},
{
"kind": "account",
"path": "token_a_mint | token_b_mint"
},
{
"kind": "account",
"path": "token_a_mint | token_b_mint"
}
]
}
}Actual IDL output for pool account:
{
"name": "pool",
"docs": [
"Initialize an account to store the pool state"
],
"writable": true
}4. Observe inconsistency
In the same IDL, the position account (which also has init + seeds) correctly includes the pda field:
{
"name": "position",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [112, 111, 115, 105, 116, 105, 111, 110]
},
{
"kind": "account",
"path": "position_nft_mint"
}
]
}
}This inconsistency is confusing - why does position get the pda field but pool doesn't, when both use the same pattern?
5. Attempt to use IDL from client
Client code attempting to derive PDA using IDL:
// This fails because IDL doesn't specify the pool is a PDA
const pool = PublicKey.findProgramAddressSync(
[
Buffer.from("cpool"),
getMaxKey(tokenAMint, tokenBMint),
getMinKey(tokenAMint, tokenBMint),
],
programId
)[0];
// Passing the incorrectly derived (or not derived) pool address
await program.methods
.initializeCustomizablePool(params)
.accounts({
pool: pool, // This will fail with ConstraintSeeds error
// ... other accounts
})
.rpc();Error received:
Error: AnchorError caused by account: pool. Error Code: ConstraintSeeds. Error Number: 2006.
Error Message: A seeds constraint was violated.
Program log: Left:
Program log: F8DxVitDgiFvXX3tYxtzZPrBnSc1B69sKWmUpixHw8BF
Program log: Right:
Program log: 6zwjucTW2d32jZRC8m7FE6C2aQuhsiGaWBWoge3feWCx
The only way to discover the correct seeds is to read the Rust source code, which defeats the purpose of the IDL!.
This appears to affect Anchor programs using:
AccountLoader<'info, T>account types- Complex seed derivations with helper functions
- The
initconstraint combined withseeds
However, it's inconsistent - some accounts in the same struct show PDA seeds correctly while others don't.
here's the full IDL, but would suggest to check and build on your own too
cp_amm.json