Skip to content

Commit a0c5813

Browse files
committed
fix: cpi context for accounts without data, fix: address assignment check
1 parent 41eb6df commit a0c5813

File tree

6 files changed

+157
-27
lines changed

6 files changed

+157
-27
lines changed

programs/system/CHANGELOG.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [Unreleased]
6+
7+
### Added
8+
- **New `cpi_context` module**: Comprehensive 1500+ line CPI (Cross-Program Invocation) context management system enabling advanced multi-program operations with single zero-knowledge proof (d6d0fb5)
9+
- **Address assignment capability**: Support for spending accounts created within the same instruction via `CpiContextNewAddressParamsAssignedPacked` with `assigned_to_account` and `assigned_account_index` fields (d6d0fb5)
10+
- **Zero-copy CPI context structure**: `ZCpiContextAccount` with separate fields for different account types:
11+
- `new_addresses`: `ZeroCopyVecU8<CpiContextNewAddressParamsAssignedPacked>` for newly created addresses
12+
- `readonly_addresses`: `ZeroCopyVecU8<ZPackedReadOnlyAddress>` for read-only address references
13+
- `readonly_accounts`: `ZeroCopyVecU8<ZPackedReadOnlyCompressedAccount>` for read-only account references
14+
- `in_accounts`: `ZeroCopyVecU8<CpiContextInAccount>` for input compressed accounts
15+
- `out_accounts`: `ZeroCopyVecU8<CpiContextOutAccount>` for output compressed accounts (d6d0fb5)
16+
- **CPI-specific account structures** distinct from regular instruction data types:
17+
- `CpiContextInAccount`: Compressed input account format for CPI context (owner, discriminator, data_hash, merkle_context, root_index, lamports, optional address - 80 bytes) vs `ZPackedCompressedAccountWithMerkleContext` used in regular instruction data (includes full compressed account data, variable size)
18+
- `CpiContextOutAccount`: Compressed output account format for CPI context (owner, discriminator, data_hash, output_merkle_tree_index, lamports, optional address - 82 bytes) vs `ZOutputCompressedAccountWithPackedContext` used in regular instruction data (includes full compressed account data, variable size)
19+
- **Key difference**: CPI context accounts store only essential fields in fixed-size format for efficient cross-program caching, while regular instruction account types include full variable-length compressed account data (d6d0fb5)
20+
- **Pinocchio framework integration**: Added `pinocchio-pubkey` dependency for performance optimizations in account operations (d6d0fb5)
21+
- **Address validation traits**: New `NewAddress` trait implementation for `CpiContextNewAddressParamsAssignedPacked` with seed, queue indices, and assignment validation (d6d0fb5)
22+
23+
### Changed
24+
- **Breaking: Complete CPI context data structure redesign**
25+
- **Old**: `CpiContextAccount { fee_payer: Pubkey, associated_merkle_tree: Pubkey, context: Vec<InstructionDataInvokeCpi> }`
26+
- **New**: `ZCpiContextAccount` with separate typed vectors for different data types and zero-copy memory layout
27+
- **Migration**: Replace `context` vector access with specific field access (`in_accounts`, `out_accounts`, `new_addresses`, etc.) (d6d0fb5)
28+
- **Breaking: `LightTransactionContext::set_cpi_context()` method signature change**
29+
- **Removed parameters**: `outputs_start_offset: usize`, `outputs_end_offset: usize`
30+
- **Reason**: Offsets now calculated internally from CPI context structure
31+
- **Migration**: Remove offset parameters from method calls (d6d0fb5)
32+
- **Breaking: CPI context account layout and serialization format**
33+
- Account data layout changed from Borsh-serialized struct to zero-copy binary format
34+
- Clients must update deserialization logic to use new `deserialize_cpi_context_account()` function (d6d0fb5)
35+
- **CPI context processing workflow**:
36+
- Enhanced two-phase execution: first invocation caches validated data in CPI context, second invocation combines and executes
37+
- Improved validation: CPI context association with Merkle tree verified only during execution phase
38+
- Better fee payer management: fee payer zeroed out when CPI context consumed to prevent reuse (d6d0fb5)
39+
40+
### Performance
41+
- **Memory optimization**: Zero-copy patterns eliminate memory allocation overhead during CPI context operations, reducing heap usage (d6d0fb5)
42+
- **Transaction efficiency**: Single zero-knowledge proof for multi-program transactions instead of multiple proofs:
43+
- **Before**: Each program requires separate proof (128 bytes + ~100,000 CU each)
44+
- **After**: Single combined proof for all programs (128 bytes + ~100,000 CU total) (d6d0fb5)
45+
- **Instruction size reduction**: Consolidated instruction data reduces transaction size for complex multi-program operations (d6d0fb5)
46+
- **Address assignment optimization**: Created accounts can be immediately used as inputs in same instruction, eliminating need for separate transactions (d6d0fb5)
47+
48+
### Internal
49+
- **Module restructuring**: Moved `process_cpi_context.rs` from `invoke_cpi` to dedicated `cpi_context` module for better organization (d6d0fb5)
50+
- **Comprehensive instruction data trait**: Implemented `InstructionData` trait for `ZCpiContextAccount` with proper owner, address, and account management (d6d0fb5)
51+
- **Enhanced validation pipeline**:
52+
- CPI context association validation with Merkle tree accounts
53+
- Fee payer consistency checks across invocations
54+
- Empty context detection and error handling (d6d0fb5)
55+
- **Improved error context**: Added detailed error messages for CPI context mismatches and validation failures (d6d0fb5)
56+
- **TODO cleanup**: Identified and documented areas requiring future implementation (clear_cpi_context_account, offset standardization) (41eb6df)
57+
- **Code documentation**: Added comprehensive comments explaining CPI context usage patterns and examples (51690d7)
58+
59+
### Security
60+
- **Enhanced signer validation**: CPI context operations validate signers during both caching and execution phases (d6d0fb5)
61+
- **Context isolation**: CPI context accounts properly isolated and cleared between transactions to prevent state leakage (d6d0fb5)
62+
- **Fee payer verification**: Strict fee payer matching required between CPI context setup and execution phases (d6d0fb5)
63+
- **Bounds checking**: Comprehensive validation of account indices, output limits, and address assignment boundaries (d6d0fb5)
64+
- **State transition atomicity**: Combined instruction data execution ensures all-or-nothing state transitions across programs (d6d0fb5)
65+
66+
### Fixed
67+
- **CPI context account clearing**: Temporarily disabled problematic account clearing logic requiring reimplementation for new structure (d6d0fb5)
68+
- **Offset calculation**: Removed manual offset tracking in favor of automated calculation from CPI context structure (41eb6df)
69+
70+
## Migration Guide
71+
72+
### For developers using CPI context:
73+
74+
1. **Update CPI context account access**:
75+
```rust
76+
// Old approach
77+
let context_data = cpi_context_account.context[0].input_compressed_accounts;
78+
79+
// New approach
80+
let input_accounts = cpi_context_account.in_accounts;
81+
let output_accounts = cpi_context_account.out_accounts;
82+
```
83+
84+
2. **Update set_cpi_context calls**:
85+
```rust
86+
// Old signature
87+
context.set_cpi_context(cpi_context_account, start_offset, end_offset)?;
88+
89+
// New signature
90+
context.set_cpi_context(cpi_context_account)?;
91+
```
92+
93+
3. **Update account deserialization**:
94+
- Replace manual Borsh deserialization with `deserialize_cpi_context_account()`
95+
- Handle new zero-copy data structures appropriately
96+
97+
4. **Review address assignment usage**:
98+
- Leverage new `assigned_to_account` capability for same-instruction account creation and usage
99+
- Update address handling to use new structured address parameters

programs/system/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pinocchio = { workspace = true }
4141
pinocchio-system = { version = "0.2.3" }
4242
solana-pubkey = { workspace = true, features = ["curve25519", "sha2"] }
4343
pinocchio-pubkey = { workspace = true }
44-
44+
solana-msg = { workspace = true }
4545
[dev-dependencies]
4646
rand = { workspace = true }
4747
light-compressed-account = { workspace = true, features = [

programs/system/src/accounts/account_checks.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::panic::Location;
2+
13
use light_account_checks::{
24
checks::{
35
check_discriminator, check_mut, check_non_mut, check_owner, check_pda_seeds,
@@ -124,14 +126,34 @@ pub fn check_option_decompression_recipient<'a>(
124126
Ok(account)
125127
}
126128

129+
#[track_caller]
127130
pub fn check_option_cpi_context_account<'a>(
128131
account_infos: &mut AccountIterator<'a, AccountInfo>,
129132
account_options: AccountOptions,
130133
) -> Result<Option<&'a AccountInfo>> {
131134
let account = if account_options.cpi_context_account {
132135
let account_info = account_infos.next_account("cpi_context")?;
133-
check_owner(&crate::ID, account_info)?;
134-
check_discriminator::<ZCpiContextAccount>(account_info.try_borrow_data()?.as_ref())?;
136+
check_owner(&crate::ID, account_info).inspect_err(|_| {
137+
let location = Location::caller();
138+
solana_msg::msg!(
139+
"ERROR: check_owner {:?} owner: {:?} for cpi_context failed. {}:{}:{}",
140+
solana_pubkey::Pubkey::new_from_array(*account_info.key()),
141+
solana_pubkey::Pubkey::new_from_array(unsafe { *account_info.owner() }),
142+
location.file(),
143+
location.line(),
144+
location.column()
145+
)
146+
})?;
147+
check_discriminator::<ZCpiContextAccount>(account_info.try_borrow_data()?.as_ref())
148+
.inspect_err(|_| {
149+
let location = Location::caller();
150+
solana_msg::msg!(
151+
"ERROR: check_discriminator for cpi_context failed. {}:{}:{}",
152+
location.file(),
153+
location.line(),
154+
location.column()
155+
)
156+
})?;
135157
Some(account_info)
136158
} else {
137159
None

programs/system/src/cpi_context/process_cpi_context.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use light_compressed_account::{
1212
pubkey::AsPubkey,
1313
};
1414
use light_zero_copy::ZeroCopyNew;
15-
use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey};
15+
use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
16+
use solana_msg::msg;
1617

1718
use super::state::{deserialize_cpi_context_account, ZCpiContextAccount};
1819
use crate::{context::WrappedInstructionData, errors::SystemProgramError, Result};
@@ -187,7 +188,12 @@ pub fn copy_cpi_context_outputs(
187188
.to_le_bytes()
188189
.as_slice(),
189190
);
190-
msg!("here");
191+
msg!(
192+
"here cpi_context
193+
.out_accounts len {} data len {}",
194+
cpi_context.out_accounts.len(),
195+
cpi_context.output_data.len()
196+
);
191197
for (output_account, output_data) in cpi_context
192198
.out_accounts
193199
.iter()

programs/system/src/cpi_context/state.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,24 @@ impl<'a> ZCpiContextAccount<'a> {
153153
};
154154
self.out_accounts.push(out_account)?;
155155
sol_log_compute_units();
156-
if let Some(data) = output.data() {
157-
// 330 CU
156+
// Add output data
157+
{
158158
*self.output_data_len += 1;
159-
// TODO: add unchecked new at this will fail with MemoryNotZeroed
160-
let (mut new_data, remaining_data) = ZeroCopySliceMut::<U16, u8>::new_at(
161-
(data.data.len() as u16).into(),
162-
self.remaining_data,
163-
)?;
164-
new_data.as_mut_slice().copy_from_slice(&data.data);
159+
160+
let data_len = output
161+
.data()
162+
.map_or(Ok(0), |data| data.data.len().try_into())
163+
.map_err(|_| ZeroCopyError::InvalidConversion)?;
164+
let (mut new_data, remaining_data) =
165+
ZeroCopySliceMut::<U16, u8>::new_at(data_len.into(), self.remaining_data)?;
166+
167+
if let Some(data) = output.data() {
168+
new_data.as_mut_slice().copy_from_slice(&data.data);
169+
}
165170
self.output_data.push(new_data);
166171
self.remaining_data = remaining_data;
167172
}
173+
168174
sol_log_compute_units();
169175
}
170176

programs/system/src/processor/create_address_cpi_data.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,17 @@ pub fn derive_new_addresses<'info, 'a, 'b: 'a, const ADDRESS_ASSIGNMENT: bool>(
8585
))
8686
}
8787
};
88-
//if !ADDRESS_ASSIGNMENT {
89-
// We are inserting addresses into two vectors to avoid unwrapping
90-
// the option in following functions.
91-
context.addresses.push(Some(address));
92-
// commented because too strict for usage with cpi context.
93-
// Either keep it commented or create v2 cpi context.
94-
// TODO: create v2 cpi context. We can resize existing ones.
95-
// } else if new_address_params
96-
// .assigned_compressed_account_index()
97-
// .is_some()
98-
// {
99-
// Only addresses assigned to output accounts can be used in output accounts.
100-
// context.addresses.push(Some(address));
101-
// }
88+
if !ADDRESS_ASSIGNMENT {
89+
// We are inserting addresses into two vectors to avoid unwrapping
90+
// the option in following functions.
91+
context.addresses.push(Some(address));
92+
} else if new_address_params
93+
.assigned_compressed_account_index()
94+
.is_some()
95+
{
96+
// Only addresses assigned to output accounts can be used in output accounts.
97+
context.addresses.push(Some(address));
98+
}
10299
cpi_ix_data.addresses[i].address = address;
103100

104101
context.set_rollover_fee(new_address_params.address_queue_index(), rollover_fee);

0 commit comments

Comments
 (0)