/** * @brief Example C-based BPF program that tests cross-program invocations */ #include "../invoked/instruction.h" #include static const uint8_t TEST_SUCCESS = 1; static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2; static const uint8_t TEST_PRIVILEGE_ESCALATION_WRITABLE = 3; static const uint8_t TEST_PPROGRAM_NOT_EXECUTABLE = 4; static const int MINT_INDEX = 0; static const int ARGUMENT_INDEX = 1; static const int INVOKED_PROGRAM_INDEX = 2; static const int INVOKED_ARGUMENT_INDEX = 3; static const int INVOKED_PROGRAM_DUP_INDEX = 4; static const int ARGUMENT_DUP_INDEX = 5; static const int DERIVED_KEY1_INDEX = 6; static const int DERIVED_KEY2_INDEX = 7; static const int DERIVED_KEY3_INDEX = 8; static const int SYSTEM_PROGRAM_INDEX = 9; static const int FROM_INDEX = 10; extern uint64_t entrypoint(const uint8_t *input) { sol_log("Invoke C program"); SolAccountInfo accounts[11]; SolParameters params = (SolParameters){.ka = accounts}; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(accounts))) { return ERROR_INVALID_ARGUMENT; } uint8_t nonce1 = params.data[1]; uint8_t nonce2 = params.data[2]; uint8_t nonce3 = params.data[3]; switch (params.data[0]) { case TEST_SUCCESS: { sol_log("Call system program create account"); { uint64_t from_lamports = *accounts[FROM_INDEX].lamports; uint64_t to_lamports = *accounts[DERIVED_KEY1_INDEX].lamports; SolAccountMeta arguments[] = { {accounts[FROM_INDEX].key, true, true}, {accounts[DERIVED_KEY1_INDEX].key, true, true}}; uint8_t data[4 + 8 + 8 + 32]; *(uint64_t *)(data + 4) = 42; *(uint64_t *)(data + 4 + 8) = MAX_PERMITTED_DATA_INCREASE; sol_memcpy(data + 4 + 8 + 8, params.program_id, SIZE_PUBKEY); const SolInstruction instruction = {accounts[SYSTEM_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', ' ', 'b', 'u', 't', 't', 'e', 'r'}; const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, {&nonce1, 1}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}}; sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, SOL_ARRAY_SIZE(accounts), signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); sol_assert(*accounts[FROM_INDEX].lamports == from_lamports - 42); sol_assert(*accounts[DERIVED_KEY1_INDEX].lamports == to_lamports + 42); sol_assert(SolPubkey_same(accounts[DERIVED_KEY1_INDEX].owner, params.program_id)); sol_assert(accounts[DERIVED_KEY1_INDEX].data_len == MAX_PERMITTED_DATA_INCREASE); sol_assert( accounts[DERIVED_KEY1_INDEX].data[MAX_PERMITTED_DATA_INCREASE - 1] == 0); accounts[DERIVED_KEY1_INDEX].data[MAX_PERMITTED_DATA_INCREASE - 1] = 0x0f; sol_assert( accounts[DERIVED_KEY1_INDEX].data[MAX_PERMITTED_DATA_INCREASE - 1] == 0x0f); for (uint8_t i = 0; i < 20; i++) { accounts[DERIVED_KEY1_INDEX].data[i] = i; } } sol_log("Call system program transfer"); { uint64_t from_lamports = *accounts[FROM_INDEX].lamports; uint64_t to_lamports = *accounts[DERIVED_KEY1_INDEX].lamports; SolAccountMeta arguments[] = { {accounts[FROM_INDEX].key, true, true}, {accounts[DERIVED_KEY1_INDEX].key, true, false}}; uint8_t data[] = {2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}; const SolInstruction instruction = {accounts[SYSTEM_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); sol_assert(*accounts[FROM_INDEX].lamports == from_lamports - 1); sol_assert(*accounts[DERIVED_KEY1_INDEX].lamports == to_lamports + 1); } sol_log("Test data translation"); { for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) { accounts[ARGUMENT_INDEX].data[i] = i; } SolAccountMeta arguments[] = { {accounts[ARGUMENT_INDEX].key, true, true}, {accounts[INVOKED_ARGUMENT_INDEX].key, true, true}, {accounts[INVOKED_PROGRAM_INDEX].key, false, false}, {accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}}; uint8_t data[] = {TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); } sol_log("Test no instruction data"); { SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}}; uint8_t data[] = {}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); } sol_log("Test return error"); { SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}}; uint8_t data[] = {TEST_RETURN_ERROR}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(42 == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); } sol_log("Test create_program_address"); { uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', ' ', 'b', 'u', 't', 't', 'e', 'r'}; const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, {&nonce1, 1}}; SolPubkey address; sol_assert(SUCCESS == sol_create_program_address(seeds1, SOL_ARRAY_SIZE(seeds1), params.program_id, &address)); sol_assert(SolPubkey_same(&address, accounts[DERIVED_KEY1_INDEX].key)); } sol_log("Test derived signers"); { sol_assert(!accounts[DERIVED_KEY1_INDEX].is_signer); sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer); sol_assert(!accounts[DERIVED_KEY3_INDEX].is_signer); SolAccountMeta arguments[] = { {accounts[INVOKED_PROGRAM_INDEX].key, false, false}, {accounts[DERIVED_KEY1_INDEX].key, true, true}, {accounts[DERIVED_KEY2_INDEX].key, true, false}, {accounts[DERIVED_KEY3_INDEX].key, false, false}}; uint8_t data[] = {TEST_DERIVED_SIGNERS, nonce2, nonce3}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', ' ', 'b', 'u', 't', 't', 'e', 'r'}; const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, {&nonce1, 1}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}}; sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, SOL_ARRAY_SIZE(accounts), signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); } sol_log("Test multiple derived signers"); { SolAccountMeta arguments[] = { {accounts[DERIVED_KEY1_INDEX].key, true, false}, {accounts[DERIVED_KEY2_INDEX].key, true, true}, {accounts[DERIVED_KEY3_INDEX].key, false, true}}; uint8_t data[] = {TEST_VERIFY_NESTED_SIGNERS}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; uint8_t seed1[] = {'L', 'i', 'l', '\''}; uint8_t seed2[] = {'B', 'i', 't', 's'}; const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, {seed2, SOL_ARRAY_SIZE(seed2)}, {&nonce2, 1}}; const SolSignerSeed seeds2[] = { {(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY}, {&nonce3, 1}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, {seeds2, SOL_ARRAY_SIZE(seeds2)}}; sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, SOL_ARRAY_SIZE(accounts), signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); } sol_log("Test readonly with writable account"); { SolAccountMeta arguments[] = { {accounts[INVOKED_ARGUMENT_INDEX].key, true, false}}; uint8_t data[] = {TEST_VERIFY_WRITER}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); } sol_log("Test invoke"); { sol_assert(accounts[ARGUMENT_INDEX].is_signer); *accounts[ARGUMENT_INDEX].lamports -= 5; *accounts[INVOKED_ARGUMENT_INDEX].lamports += 5; SolAccountMeta arguments[] = { {accounts[INVOKED_ARGUMENT_INDEX].key, true, true}, {accounts[ARGUMENT_INDEX].key, true, true}}; uint8_t data[] = {TEST_NESTED_INVOKE}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_log("First invoke"); sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); sol_log("2nd invoke from first program"); sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42 - 5 + 1 + 1); sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10 + 5 - 1 - 1); } sol_log("Verify data values are retained and updated"); for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) { sol_assert(accounts[ARGUMENT_INDEX].data[i] == i); } for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) { sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data[i] == i); } break; } case TEST_PRIVILEGE_ESCALATION_SIGNER: { sol_log("Test privilege escalation signer"); SolAccountMeta arguments[] = { {accounts[DERIVED_KEY3_INDEX].key, false, false}}; uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); // Signer privilege escalation will always fail the whole transaction instruction.accounts[0].is_signer = true; sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)); break; } case TEST_PRIVILEGE_ESCALATION_WRITABLE: { sol_log("Test privilege escalation writable"); SolAccountMeta arguments[] = { {accounts[DERIVED_KEY3_INDEX].key, false, false}}; uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; sol_assert(SUCCESS == sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); // Writable privilege escalation will always fail the whole transaction instruction.accounts[0].is_writable = true; sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)); break; } case TEST_PPROGRAM_NOT_EXECUTABLE: { sol_log("Test program not executable"); SolAccountMeta arguments[] = { {accounts[DERIVED_KEY3_INDEX].key, false, false}}; uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION}; const SolInstruction instruction = {accounts[ARGUMENT_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; return sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)); } default: sol_panic(); } return SUCCESS; }