|
| 1 | +# SDK Token Test Debugging Guide |
| 2 | + |
| 3 | +## Error Code Reference |
| 4 | + |
| 5 | +| Error Code | Error Name | Description | Common Fix | |
| 6 | +|------------|------------|-------------|------------| |
| 7 | +| 16031 | `CpiAccountsIndexOutOfBounds` | Missing account in accounts array | Add signer with `add_pre_accounts_signer_mut()` | |
| 8 | +| 6020 | `CpiContextAccountUndefined` | CPI context expected but not provided | Set `cpi_context: None` for simple operations | |
| 9 | + |
| 10 | +### Light System Program Errors (Full Reference) |
| 11 | +| 6017 | `ProofIsNone` | 6018 | `ProofIsSome` | 6019 | `EmptyInputs` | 6020 | `CpiContextAccountUndefined` | |
| 12 | +| 6021 | `CpiContextEmpty` | 6022 | `CpiContextMissing` | 6023 | `DecompressionRecipientDefined` | |
| 13 | + |
| 14 | +## Common Issues and Solutions |
| 15 | + |
| 16 | +### 1. `CpiAccountsIndexOutOfBounds` (Error 16031) |
| 17 | +Missing signer account. **Fix**: `remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey())` |
| 18 | + |
| 19 | +### 2. Privilege Escalation Error |
| 20 | +Manually adding accounts instead of using PackedAccounts. **Fix**: Use `add_pre_accounts_signer_mut()` instead of manual account concatenation. |
| 21 | + |
| 22 | +### 3. Account Structure Mismatch |
| 23 | +Wrong context type. **Fix**: Use `Generic<'info>` for single signer, `GenericWithAuthority<'info>` for signer + authority. |
| 24 | + |
| 25 | +### 4. `CpiContextAccountUndefined` (Error 6020) |
| 26 | +**Root Cause**: Using functions designed for CPI context when you don't need it. |
| 27 | + |
| 28 | +**CPI Context Purpose**: Optimize multi-program transactions by using one proof instead of multiple. Flow: |
| 29 | +1. First program: Cache signer checks in CPI context |
| 30 | +2. Second program: Read context, combine data, execute with single proof |
| 31 | + |
| 32 | +**Solutions**: |
| 33 | +```rust |
| 34 | +// ✅ Simple operations - no CPI context |
| 35 | +let cpi_inputs = CpiInputs { |
| 36 | + proof, |
| 37 | + account_infos: Some(vec![account.to_account_info().unwrap()]), |
| 38 | + new_addresses: Some(vec![new_address_params]), |
| 39 | + cpi_context: None, // ← Key |
| 40 | + ..Default::default() |
| 41 | +}; |
| 42 | + |
| 43 | +// ✅ Complex multi-program operations - use CPI context |
| 44 | +let config = SystemAccountMetaConfig::new_with_cpi_context(program_id, cpi_context_account); |
| 45 | +``` |
| 46 | + |
| 47 | +### 5. Avoid Complex Function Reuse |
| 48 | +**Problem**: Functions like `process_create_compressed_account` expect CPI context setup. |
| 49 | + |
| 50 | +**Fix**: Use direct Light SDK approach: |
| 51 | +```rust |
| 52 | +// ❌ Complex function with CPI context dependency |
| 53 | +process_create_compressed_account(...) |
| 54 | + |
| 55 | +// ✅ Direct approach |
| 56 | +let mut account = LightAccount::<'_, CompressedEscrowPda>::new_init(&crate::ID, Some(address), tree_index); |
| 57 | +account.amount = amount; |
| 58 | +account.owner = *cpi_accounts.fee_payer().key; |
| 59 | +let cpi_inputs = CpiInputs { proof, account_infos: Some(vec![account.to_account_info().unwrap()]), cpi_context: None, ..Default::default() }; |
| 60 | +cpi_inputs.invoke_light_system_program(cpi_accounts) |
| 61 | +``` |
| 62 | + |
| 63 | +### 6. Critical Four Invokes Implementation Learnings |
| 64 | + |
| 65 | +**CompressInputs Structure for CPI Context Operations**: |
| 66 | +```rust |
| 67 | +let compress_inputs = CompressInputs { |
| 68 | + fee_payer: *cpi_accounts.fee_payer().key, |
| 69 | + authority: *cpi_accounts.fee_payer().key, |
| 70 | + mint, |
| 71 | + recipient, |
| 72 | + sender_token_account: *remaining_accounts[0].key, // ← Use remaining_accounts index |
| 73 | + amount, |
| 74 | + output_tree_index, |
| 75 | + // ❌ Wrong: output_queue_pubkey: *cpi_accounts.tree_accounts().unwrap()[0].key, |
| 76 | + token_pool_pda: *remaining_accounts[1].key, // ← From remaining_accounts |
| 77 | + transfer_config: Some(TransferConfig { |
| 78 | + cpi_context: Some(CompressedCpiContext { |
| 79 | + set_context: true, |
| 80 | + first_set_context: true, |
| 81 | + cpi_context_account_index: 0, |
| 82 | + }), |
| 83 | + cpi_context_pubkey: Some(cpi_context_pubkey), |
| 84 | + ..Default::default() |
| 85 | + }), |
| 86 | + spl_token_program: *remaining_accounts[2].key, // ← SPL_TOKEN_PROGRAM_ID |
| 87 | + tree_accounts: cpi_accounts.tree_pubkeys().unwrap(), // ← From CPI accounts |
| 88 | +}; |
| 89 | +``` |
| 90 | + |
| 91 | +**Critical Account Ordering for Four Invokes**: |
| 92 | +```rust |
| 93 | +// Test setup - exact order matters for remaining_accounts indices |
| 94 | +remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey()); |
| 95 | +// Remaining accounts 0 - compression token account |
| 96 | +remaining_accounts.add_pre_accounts_meta(AccountMeta::new(compression_token_account, false)); |
| 97 | +// Remaining accounts 1 - token pool PDA |
| 98 | +remaining_accounts.add_pre_accounts_meta(AccountMeta::new(token_pool_pda1, false)); |
| 99 | +// Remaining accounts 2 - SPL token program |
| 100 | +remaining_accounts.add_pre_accounts_meta(AccountMeta::new(SPL_TOKEN_PROGRAM_ID.into(), false)); |
| 101 | +// Remaining accounts 3 - compressed token program |
| 102 | +remaining_accounts.add_pre_accounts_meta(AccountMeta::new(compressed_token_program, false)); |
| 103 | +// Remaining accounts 4 - CPI authority PDA |
| 104 | +remaining_accounts.add_pre_accounts_meta(AccountMeta::new(cpi_authority_pda, false)); |
| 105 | +``` |
| 106 | + |
| 107 | +**Validity Proof and Tree Info Management**: |
| 108 | +```rust |
| 109 | +// Get escrow account directly by address (more efficient) |
| 110 | +let escrow_account = rpc.get_compressed_account(escrow_address, None).await?.value; |
| 111 | + |
| 112 | +// Pack tree infos BEFORE constructing TokenAccountMeta |
| 113 | +let packed_tree_info = rpc_result.pack_tree_infos(&mut remaining_accounts); |
| 114 | + |
| 115 | +// Use correct tree info indices for each compressed account |
| 116 | +let mint2_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[1]; |
| 117 | +let mint3_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[2]; |
| 118 | +let escrow_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[0]; |
| 119 | +``` |
| 120 | + |
| 121 | +**System Accounts Start Offset**: |
| 122 | +```rust |
| 123 | +// Use the actual offset returned by to_account_metas() |
| 124 | +let (accounts, system_accounts_start_offset, _) = remaining_accounts.to_account_metas(); |
| 125 | +// Pass this offset to the instruction |
| 126 | +system_accounts_start_offset: system_accounts_start_offset as u8, |
| 127 | +``` |
| 128 | + |
| 129 | +## Best Practices |
| 130 | + |
| 131 | +### CPI Context Decision |
| 132 | +- **Use**: Multi-program transactions with compressed accounts (saves proofs) |
| 133 | +- **Avoid**: Simple single-program operations (PDA creation, basic transfers) |
| 134 | + |
| 135 | +### Account Management |
| 136 | +- Use `PackedAccounts` and `add_pre_accounts_signer_mut()` |
| 137 | +- Choose `Generic<'info>` (1 account) vs `GenericWithAuthority<'info>` (2 accounts) |
| 138 | +- Set `cpi_context: None` for simple operations |
| 139 | + |
| 140 | +### Working Patterns |
| 141 | +```rust |
| 142 | +// Compress tokens pattern |
| 143 | +let mut remaining_accounts = PackedAccounts::default(); |
| 144 | +remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey()); |
| 145 | +let metas = get_transfer_instruction_account_metas(config); |
| 146 | +remaining_accounts.add_pre_accounts_metas(metas.as_slice()); |
| 147 | +let output_tree_index = rpc.get_random_state_tree_info().unwrap().pack_output_tree_index(&mut remaining_accounts).unwrap(); |
| 148 | + |
| 149 | +// Test flow: Setup → Compress → Create PDA → Execute |
| 150 | +``` |
| 151 | + |
| 152 | +## Implementation Status |
| 153 | + |
| 154 | +### ✅ Working Features |
| 155 | +1. **Basic PDA Creation**: `create_escrow_pda` instruction works correctly |
| 156 | +2. **Token Compression**: Individual token compression operations work |
| 157 | +3. **Four Invokes Instruction**: Complete CPI context implementation working |
| 158 | + - Account structure: Uses `Generic<'info>` (single signer) |
| 159 | + - CPI context: Proper multi-program proof optimization |
| 160 | + - Token accounts: Correct account ordering and tree info management |
| 161 | + - Compress CPI: Working with proper `CompressInputs` structure |
| 162 | + - Transfer CPI: Custom `transfer_tokens_with_cpi_context` wrapper replaces `transfer_tokens_to_escrow_pda` |
| 163 | +4. **Error Handling**: Comprehensive error code documentation and fixes |
| 164 | + |
| 165 | +### Key Implementation Success |
| 166 | +The `four_invokes` instruction successfully demonstrates the complete CPI context pattern for Light Protocol, enabling: |
| 167 | +- **Single Proof Optimization**: One validity proof for multiple compressed account operations |
| 168 | +- **Cross-Program Integration**: Token program + system program coordination |
| 169 | +- **Production Ready**: Complete account setup and tree info management |
| 170 | +- **Custom Transfer Wrapper**: Purpose-built transfer function for four invokes instruction |
0 commit comments