CPI Cost Model and Data Sync

Summary

Each CPI costs ~1,000 CUs base plus serialization costs. Account data is synced before and after callee execution. PDA signing uses the caller's program ID. Return data is limited to 1,024 bytes.

CPI cost model

CPI costs are drawn from the same transaction compute budget (shared meter). The full cost formula for each invoke / invoke_signed call:

total_cpi_cost = invocation_cost
+ instruction_data_cost
+ account_meta_cost (SIMD-0339 only)
+ account_info_cost (SIMD-0339 only)
+ per_account_data_cost (for each non-executable account)
+ callee_execution_cost

Cost breakdown

Cost componentFormulaSource
Invocation (fixed)invoke_units = 1,000 CUs (946 with SIMD-0339)Charged at CPI entry
Instruction data serializationinstruction_data_len / cpi_bytes_per_unitcpi_bytes_per_unit = 250. Charged in translate_instruction.
Account meta serialization (SIMD-0339)(num_account_metas * 34) / cpi_bytes_per_unitEach AccountMeta is 34 bytes (32 pubkey + 1 is_signer + 1 is_writable). Charged in translate_instruction.
Account info translation (SIMD-0339)(num_account_infos * 80) / cpi_bytes_per_unitACCOUNT_INFO_BYTE_SIZE = 80 bytes (32 key + 32 owner + 8 lamports + 8 data_len). Charged in translate_account_infos.
Per-account dataaccount_data_len / cpi_bytes_per_unitCharged per account in CallerAccount::from_account_info and for executable accounts.
Callee executionWhatever CUs the callee program consumesDeducted from the shared meter during callee execution.

Example cost calculation

A CPI with 100 bytes of instruction data, 5 account metas, 5 account infos (each with 1,000 bytes of data), SIMD-0339 active:

invocation_cost = 946
instruction_data_cost = 100 / 250 = 0 (integer division)
account_meta_cost = (5 * 34) / 250 = 0
account_info_cost = (5 * 80) / 250 = 1
per_account_data_cost = 5 * (1000 / 250) = 20
total (before callee) = 967 CUs

Account data synchronization

Account state is synchronized between caller and callee at two points during a CPI. This ensures both sides see a consistent view of account data.

Pre-CPI sync (caller to callee)

Before the callee executes, update_callee_account copies the caller's in-flight modifications to the callee's account view:

FieldDirectionWhen
LamportsCaller -> CalleeIf the value differs from the callee's current view
Data lengthCaller -> CalleeIf the caller has resized the account. Must not exceed original_data_len + MAX_PERMITTED_DATA_INCREASE (10 KiB).
Data contentCaller -> CalleeIf the account's data is modifiable (can_data_be_changed passes)
OwnerCaller -> CalleeSet last, so data/lamport changes are permitted under the old owner

Post-CPI sync (callee to caller)

After the callee returns, update_caller_account copies the callee's modifications back to the caller's view. This only runs for accounts marked as writable:

FieldDirectionWhen
LamportsCallee -> CallerAlways copied back
OwnerCallee -> CallerAlways copied back
Data lengthCallee -> CallerIf changed. The VM data slice pointer and serialized length field are updated. If the account was shrunk, freed memory is zeroed. If the new length exceeds original_data_len + MAX_PERMITTED_DATA_INCREASE, returns InvalidRealloc.
Data contentCallee -> CallerCopied from callee's data buffer to caller's serialized data region

Realloc limits

Account data can be resized during CPI up to MAX_PERMITTED_DATA_INCREASE = 10,240 bytes (10 KiB) beyond the account's data length at the start of the current top-level instruction. This limit is enforced in both update_callee_account (pre-CPI) and update_caller_account (post-CPI). Exceeding it returns InvalidRealloc.

PDA signing

When invoke_signed is called, the runtime derives PDA addresses from the provided seeds and the caller's program ID. This happens in translate_signers_rust:

  1. The signer seeds array is validated: at most MAX_SIGNERS (16) signer seed sets.
  2. Each seed set is validated: at most MAX_SEEDS (16) seeds per set, each seed at most MAX_SEED_LEN (32) bytes.
  3. Pubkey::create_program_address is called with the seeds and the caller's program ID (not the callee's).
  4. If the seeds don't produce a valid PDA (i.e., the resulting point is on the ed25519 curve), the CPI fails with BadSeeds.
  5. The derived PDA pubkeys are collected and passed to prepare_next_instruction as valid signers. During privilege checking, if a callee account is marked as a signer and its pubkey matches one of these derived PDAs, the signer check passes.

The PDA is derived using the caller's program ID, not the callee's. This means only the program that owns the PDA (the one whose ID was used to derive it) can sign on its behalf. A program cannot sign for PDAs derived from other programs.

Return data

Programs can pass data back to callers using the return data mechanism. This uses two syscalls:

  • sol_set_return_data: Sets up to MAX_RETURN_DATA (1,024) bytes of return data for the current instruction. The cost is data_len / cpi_bytes_per_unit + syscall_base_cost CUs.
  • sol_get_return_data: Reads the return data set by the most recently executed instruction. Returns the data along with the program ID that set it. The cost is (data_len + 32) / cpi_bytes_per_unit + syscall_base_cost CUs (32 bytes for the program ID).

Return data is stored per-transaction and is overwritten by each instruction that calls sol_set_return_data. At the start of each program invocation, the runtime resets return data to empty. After a CPI returns, the caller can read whatever return data the callee (or any program the callee called) last set.

Return data is limited to 1,024 bytes. Only the last program to call sol_set_return_data in the call chain determines what the caller sees. If a callee makes further CPIs that set return data, the callee's own return data is overwritten.

Is this page helpful?

सामग्री तालिका

पृष्ठ संपादित करें

द्वारा प्रबंधित

© 2026 सोलाना फाउंडेशन। सर्वाधिकार सुरक्षित।
जुड़े रहें