Skip to content

Cross-Program Invocation

View as Markdown

Use this page when you need to call another program from a C program and want the smallest correct model for account auth, deauth, and error handling.

ulong
tsys_invoke( void const * instr_data,
ulong instr_data_sz,
ushort program_account_idx,
tsdk_invoke_auth_t const * auth,
ulong * invoke_err_code );
ArgumentMeaning
instr_dataPointer to the callee instruction payload.
instr_data_szPayload size in bytes.
program_account_idxTransaction account index of the program being invoked.
authOptional authorization descriptor for CPI account auth and deauth rules.
invoke_err_codeOptional output for the callee’s own error code.

The public auth descriptor is a header plus a flexible array:

FieldMeaning
magicMust equal TSDK_INVOKE_AUTH_MAGIC.
auth_cntNumber of authorized account indices at the start of acc_idxs.
deauth_cntNumber of deauthorized account indices after the auth entries.
acc_idxs[]First auth_cnt authorized indices, then deauth_cnt deauthorized indices.

Validation the wrapper performs before CPI

Section titled “Validation the wrapper performs before CPI”

If auth is non-null, the wrapper checks:

  • the magic value is correct
  • every auth entry references a valid account index
  • every auth entry is owned by the current program
  • every deauth entry references a valid account index

If any of those checks fail, the wrapper reverts before issuing the syscall.

ulong invoke_err = 0UL;
ulong invoke_result = tsys_invoke( payload,
payload_sz,
target_program_idx,
NULL,
&invoke_err );
if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result );
if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err );
struct my_invoke_auth {
ulong magic;
ushort auth_cnt;
ushort deauth_cnt;
ushort acc_idxs[1];
};
struct my_invoke_auth auth = {
.magic = TSDK_INVOKE_AUTH_MAGIC,
.auth_cnt = 1U,
.deauth_cnt = 0U,
.acc_idxs = { writable_account_idx },
};

Pass &auth as the auth argument to tsys_invoke.

The current SDK’s authorization helpers walk the shadow stack from the most recent parent frame downward:

  • deeper frames override shallower ones
  • deauth entries are checked before auth entries
  • auth entries are only accepted if the invoking frame’s program owns the account
  • fee payer and current program remain implicitly authorized

Before calling tsys_invoke, verify:

  • program_account_idx is valid
  • the callee payload layout matches what the target program expects
  • every account index embedded in the payload matches the transaction ordering
  • any auth entries refer only to accounts owned by the current program
  • you handle both invoke_result and invoke_err
  • For most first-pass CPI work, start with auth = NULL; only add explicit auth when the callee needs it.
  • Do not collapse the two error channels from tsys_invoke into one check.
  • The easiest CPI bugs come from mismatched account indices, not from the syscall callsite itself.