Add return data implementation
This consists of: - syscalls - passing return data from invoked to invoker - printing to stable log - rust and C SDK changes
This commit is contained in:
1
programs/bpf/Cargo.lock
generated
1
programs/bpf/Cargo.lock
generated
@@ -3260,6 +3260,7 @@ name = "solana-sdk"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64 0.13.0",
|
||||
"bincode",
|
||||
"borsh",
|
||||
"borsh-derive",
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include <sol/log.h>
|
||||
#include <sol/assert.h>
|
||||
#include <sol/deserialize.h>
|
||||
#include <sol/return_data.h>
|
||||
|
||||
static const uint8_t TEST_SUCCESS = 1;
|
||||
static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2;
|
||||
@@ -26,6 +27,7 @@ static const uint8_t TEST_WRITABLE_DEESCALATION_WRITABLE = 14;
|
||||
static const uint8_t TEST_NESTED_INVOKE_TOO_DEEP = 15;
|
||||
static const uint8_t TEST_EXECUTABLE_LAMPORTS = 16;
|
||||
static const uint8_t ADD_LAMPORTS = 17;
|
||||
static const uint8_t TEST_RETURN_DATA_TOO_LARGE = 18;
|
||||
|
||||
static const int MINT_INDEX = 0;
|
||||
static const int ARGUMENT_INDEX = 1;
|
||||
@@ -174,6 +176,32 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
|
||||
}
|
||||
|
||||
sol_log("Test return data");
|
||||
{
|
||||
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
|
||||
uint8_t data[] = { SET_RETURN_DATA };
|
||||
uint8_t buf[100];
|
||||
|
||||
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
arguments, SOL_ARRAY_SIZE(arguments),
|
||||
data, SOL_ARRAY_SIZE(data)};
|
||||
|
||||
// set some return data, so that the callee can check it is cleared
|
||||
sol_set_return_data((uint8_t[]){1, 2, 3, 4}, 4);
|
||||
|
||||
sol_assert(SUCCESS ==
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
|
||||
|
||||
SolPubkey setter;
|
||||
|
||||
uint64_t ret = sol_get_return_data(data, sizeof(data), &setter);
|
||||
|
||||
sol_assert(ret == sizeof(RETURN_DATA_VAL));
|
||||
|
||||
sol_assert(sol_memcmp(data, RETURN_DATA_VAL, sizeof(RETURN_DATA_VAL)));
|
||||
sol_assert(SolPubkey_same(&setter, accounts[INVOKED_PROGRAM_INDEX].key));
|
||||
}
|
||||
|
||||
sol_log("Test create_program_address");
|
||||
{
|
||||
uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's',
|
||||
@@ -542,27 +570,33 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||
break;
|
||||
}
|
||||
case TEST_EXECUTABLE_LAMPORTS: {
|
||||
sol_log("Test executable lamports");
|
||||
accounts[ARGUMENT_INDEX].executable = true;
|
||||
*accounts[ARGUMENT_INDEX].lamports -= 1;
|
||||
*accounts[DERIVED_KEY1_INDEX].lamports +=1;
|
||||
SolAccountMeta arguments[] = {
|
||||
{accounts[ARGUMENT_INDEX].key, true, false},
|
||||
{accounts[DERIVED_KEY1_INDEX].key, true, false},
|
||||
};
|
||||
uint8_t data[] = {ADD_LAMPORTS, 0, 0, 0};
|
||||
SolPubkey program_id;
|
||||
sol_memcpy(&program_id, params.program_id, sizeof(SolPubkey));
|
||||
const SolInstruction instruction = {&program_id,
|
||||
arguments, SOL_ARRAY_SIZE(arguments),
|
||||
data, SOL_ARRAY_SIZE(data)};
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
|
||||
*accounts[ARGUMENT_INDEX].lamports += 1;
|
||||
break;
|
||||
sol_log("Test executable lamports");
|
||||
accounts[ARGUMENT_INDEX].executable = true;
|
||||
*accounts[ARGUMENT_INDEX].lamports -= 1;
|
||||
*accounts[DERIVED_KEY1_INDEX].lamports +=1;
|
||||
SolAccountMeta arguments[] = {
|
||||
{accounts[ARGUMENT_INDEX].key, true, false},
|
||||
{accounts[DERIVED_KEY1_INDEX].key, true, false},
|
||||
};
|
||||
uint8_t data[] = {ADD_LAMPORTS, 0, 0, 0};
|
||||
SolPubkey program_id;
|
||||
sol_memcpy(&program_id, params.program_id, sizeof(SolPubkey));
|
||||
const SolInstruction instruction = {&program_id,
|
||||
arguments, SOL_ARRAY_SIZE(arguments),
|
||||
data, SOL_ARRAY_SIZE(data)};
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
|
||||
*accounts[ARGUMENT_INDEX].lamports += 1;
|
||||
break;
|
||||
}
|
||||
case ADD_LAMPORTS: {
|
||||
*accounts[0].lamports += 1;
|
||||
break;
|
||||
*accounts[0].lamports += 1;
|
||||
break;
|
||||
}
|
||||
case TEST_RETURN_DATA_TOO_LARGE: {
|
||||
sol_log("Test setting return data too long");
|
||||
// The actual buffer doesn't matter, just pass null
|
||||
sol_set_return_data(NULL, 1027);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
|
@@ -16,3 +16,6 @@ const uint8_t VERIFY_PRIVILEGE_DEESCALATION = 8;
|
||||
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER = 9;
|
||||
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 10;
|
||||
const uint8_t WRITE_ACCOUNT = 11;
|
||||
const uint8_t SET_RETURN_DATA = 12;
|
||||
|
||||
#define RETURN_DATA_VAL "return data test"
|
@@ -14,6 +14,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
// on entry, return data must not be set
|
||||
sol_assert(sol_get_return_data(NULL, 0, NULL) == 0);
|
||||
|
||||
if (params.data_len == 0) {
|
||||
return SUCCESS;
|
||||
}
|
||||
@@ -91,6 +94,12 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||
sol_log("return Ok");
|
||||
return SUCCESS;
|
||||
}
|
||||
case SET_RETURN_DATA: {
|
||||
sol_set_return_data((const uint8_t*)RETURN_DATA_VAL, sizeof(RETURN_DATA_VAL));
|
||||
sol_log("set return data");
|
||||
sol_assert(sol_get_return_data(NULL, 0, NULL) == sizeof(RETURN_DATA_VAL));
|
||||
return SUCCESS;
|
||||
}
|
||||
case RETURN_ERROR: {
|
||||
sol_log("return error");
|
||||
return 42;
|
||||
|
40
programs/bpf/c/src/return_data/return_data.c
Normal file
40
programs/bpf/c/src/return_data/return_data.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @brief return data Syscall test
|
||||
*/
|
||||
#include <solana_sdk.h>
|
||||
|
||||
#define DATA "the quick brown fox jumps over the lazy dog"
|
||||
|
||||
extern uint64_t entrypoint(const uint8_t *input) {
|
||||
uint8_t buf[1024];
|
||||
SolPubkey me;
|
||||
|
||||
// There should be no return data on entry
|
||||
uint64_t ret = sol_get_return_data(NULL, 0, NULL);
|
||||
|
||||
sol_assert(ret == 0);
|
||||
|
||||
// set some return data
|
||||
sol_set_return_data((const uint8_t*)DATA, sizeof(DATA));
|
||||
|
||||
// ensure the length is correct
|
||||
ret = sol_get_return_data(NULL, 0, &me);
|
||||
sol_assert(ret == sizeof(DATA));
|
||||
|
||||
// try getting a subset
|
||||
ret = sol_get_return_data(buf, 4, &me);
|
||||
|
||||
sol_assert(ret == sizeof(DATA));
|
||||
|
||||
sol_assert(!sol_memcmp(buf, "the ", 4));
|
||||
|
||||
// try getting the whole thing
|
||||
ret = sol_get_return_data(buf, sizeof(buf), &me);
|
||||
|
||||
sol_assert(ret == sizeof(DATA));
|
||||
|
||||
sol_assert(!sol_memcmp(buf, (const uint8_t*)DATA, sizeof(DATA)));
|
||||
|
||||
// done
|
||||
return SUCCESS;
|
||||
}
|
@@ -10,7 +10,7 @@ use solana_program::{
|
||||
entrypoint,
|
||||
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
||||
msg,
|
||||
program::{invoke, invoke_signed},
|
||||
program::{get_return_data, invoke, invoke_signed, set_return_data},
|
||||
program_error::ProgramError,
|
||||
pubkey::{Pubkey, PubkeyError},
|
||||
system_instruction,
|
||||
@@ -394,6 +394,27 @@ fn process_instruction(
|
||||
assert_eq!(data[i], i as u8);
|
||||
}
|
||||
}
|
||||
|
||||
msg!("Test return data via invoked");
|
||||
{
|
||||
// this should be cleared on entry, the invoked tests for this
|
||||
set_return_data(b"x");
|
||||
|
||||
let instruction = create_instruction(
|
||||
*accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
&[(accounts[ARGUMENT_INDEX].key, false, true)],
|
||||
vec![SET_RETURN_DATA],
|
||||
);
|
||||
let _ = invoke(&instruction, accounts);
|
||||
|
||||
assert_eq!(
|
||||
get_return_data(),
|
||||
Some((
|
||||
*accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
b"Set by invoked".to_vec()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
TEST_PRIVILEGE_ESCALATION_SIGNER => {
|
||||
msg!("Test privilege escalation signer");
|
||||
|
@@ -18,6 +18,7 @@ pub const VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 9;
|
||||
pub const VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 10;
|
||||
pub const WRITE_ACCOUNT: u8 = 11;
|
||||
pub const CREATE_AND_INIT: u8 = 12;
|
||||
pub const SET_RETURN_DATA: u8 = 13;
|
||||
|
||||
pub fn create_instruction(
|
||||
program_id: Pubkey,
|
||||
|
@@ -8,7 +8,7 @@ use solana_program::{
|
||||
bpf_loader, entrypoint,
|
||||
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
||||
msg,
|
||||
program::{invoke, invoke_signed},
|
||||
program::{get_return_data, invoke, invoke_signed, set_return_data},
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
system_instruction,
|
||||
@@ -27,6 +27,8 @@ fn process_instruction(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
assert_eq!(get_return_data(), None);
|
||||
|
||||
match instruction_data[0] {
|
||||
VERIFY_TRANSLATIONS => {
|
||||
msg!("verify data translations");
|
||||
@@ -286,6 +288,11 @@ fn process_instruction(
|
||||
}
|
||||
}
|
||||
}
|
||||
SET_RETURN_DATA => {
|
||||
msg!("Set return data");
|
||||
|
||||
set_return_data(b"Set by invoked");
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
|
@@ -227,6 +227,8 @@ fn run_program(
|
||||
for i in 0..2 {
|
||||
let mut parameter_bytes = parameter_bytes.clone();
|
||||
{
|
||||
invoke_context.set_return_data(None);
|
||||
|
||||
let mut vm = create_vm(
|
||||
&loader_id,
|
||||
executable.as_ref(),
|
||||
@@ -432,6 +434,7 @@ fn test_program_bpf_sanity() {
|
||||
("noop++", true),
|
||||
("panic", false),
|
||||
("relative_call", true),
|
||||
("return_data", true),
|
||||
("sanity", true),
|
||||
("sanity++", true),
|
||||
("secp256k1_recover", true),
|
||||
@@ -756,6 +759,7 @@ fn test_program_bpf_invoke_sanity() {
|
||||
const TEST_WRITABLE_DEESCALATION_WRITABLE: u8 = 14;
|
||||
const TEST_NESTED_INVOKE_TOO_DEEP: u8 = 15;
|
||||
const TEST_EXECUTABLE_LAMPORTS: u8 = 16;
|
||||
const TEST_RETURN_DATA_TOO_LARGE: u8 = 18;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
@@ -878,6 +882,7 @@ fn test_program_bpf_invoke_sanity() {
|
||||
invoked_program_id.clone(),
|
||||
invoked_program_id.clone(),
|
||||
invoked_program_id.clone(),
|
||||
invoked_program_id.clone(),
|
||||
],
|
||||
Languages::Rust => vec![
|
||||
system_program::id(),
|
||||
@@ -902,6 +907,7 @@ fn test_program_bpf_invoke_sanity() {
|
||||
invoked_program_id.clone(),
|
||||
invoked_program_id.clone(),
|
||||
system_program::id(),
|
||||
invoked_program_id.clone(),
|
||||
],
|
||||
};
|
||||
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
|
||||
@@ -1030,6 +1036,12 @@ fn test_program_bpf_invoke_sanity() {
|
||||
&[invoke_program_id.clone()],
|
||||
);
|
||||
|
||||
do_invoke_failure_test_local(
|
||||
TEST_RETURN_DATA_TOO_LARGE,
|
||||
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
||||
&[],
|
||||
);
|
||||
|
||||
// Check resulting state
|
||||
|
||||
assert_eq!(43, bank.get_balance(&derived_key1));
|
||||
@@ -1312,6 +1324,7 @@ fn assert_instruction_count() {
|
||||
("noop", 5),
|
||||
("noop++", 5),
|
||||
("relative_call", 10),
|
||||
("return_data", 480),
|
||||
("sanity", 169),
|
||||
("sanity++", 168),
|
||||
("secp256k1_recover", 359),
|
||||
|
Reference in New Issue
Block a user