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:
parent
2dee098b91
commit
098585234d
|
@ -5520,6 +5520,7 @@ name = "solana-sdk"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
|
"base64 0.13.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
"borsh",
|
"borsh",
|
||||||
"borsh-derive",
|
"borsh-derive",
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Return data from BPF programs
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
In the Solidity langauge it is permitted to return any number of values from a function,
|
||||||
|
for example a variable length string can be returned:
|
||||||
|
|
||||||
|
```
|
||||||
|
function foo1() public returns (string) {
|
||||||
|
return "Hello, world!\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple values, arrays and structs are permitted too.
|
||||||
|
|
||||||
|
```
|
||||||
|
struct S {
|
||||||
|
int f1;
|
||||||
|
bool f2
|
||||||
|
};
|
||||||
|
|
||||||
|
function foo2() public returns (string, int[], S) {
|
||||||
|
return (a, b, c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All the return values are eth abi encoded to a variable-length byte array.
|
||||||
|
|
||||||
|
On ethereum errors can be returned too:
|
||||||
|
|
||||||
|
```
|
||||||
|
function withdraw() public {
|
||||||
|
require(msg.sender == owner, "Permission denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
function failure() public {
|
||||||
|
revert("I afraid I can't do that dave");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
These errors help the developer debug any issue they are having, and can
|
||||||
|
also be caught in a Solidity `try` .. `catch` block. Outside of a `try` .. `catch`
|
||||||
|
block, any of these would cause the transaction or rpc to fail.
|
||||||
|
|
||||||
|
## Existing solution
|
||||||
|
|
||||||
|
The existing solution that Solang uses, writes the return data to the callee account data.
|
||||||
|
The caller's account cannot be used, since the callee may not be the same BPF program, so
|
||||||
|
it will not have permission to write to the callee's account data.
|
||||||
|
|
||||||
|
Another solution would be to have a single return data account which is passed
|
||||||
|
around through CPI. Again this does not work for CPI as the callee may not have
|
||||||
|
permission to write to it.
|
||||||
|
|
||||||
|
The problem with this solution is:
|
||||||
|
|
||||||
|
- It does not work for RPC calls
|
||||||
|
- It is very racey; a client has to submit the Tx and then retrieve the account
|
||||||
|
data. This is not atomic so the return data can be overwritten by another transaction.
|
||||||
|
|
||||||
|
## Requirements for Solution
|
||||||
|
|
||||||
|
It must work for:
|
||||||
|
|
||||||
|
- RPC: An RPC should be able to return any number of values without writing to account data
|
||||||
|
- Transaction: An transaction should be able to return any number of values without needing to write them account data
|
||||||
|
- CPI: The callee must "set" return value, and the caller must be able to retrieve it.
|
||||||
|
|
||||||
|
## Review of other chains
|
||||||
|
|
||||||
|
### Ethereum (EVM)
|
||||||
|
|
||||||
|
The `RETURN` opcode allows a contract to set a buffer as a returndata. This opcode takes a pointer to memory and a size. The `REVERT` opcode works similarly but signals that the call failed, and all account data changes must be reverted.
|
||||||
|
|
||||||
|
For CPI, the caller can retrieve the returned data of the callee using the `RETURNDATASIZE` opcode which returns the length, and the `RETURNDATACOPY` opcode, which takes a memory destination pointer, offset into the returndata, and a length argument.
|
||||||
|
|
||||||
|
Ethereum stores the returndata in blocks.
|
||||||
|
|
||||||
|
### Parity Substrate
|
||||||
|
|
||||||
|
The return data can be set using the `seal_return(u32 flags, u32 pointer, u32 size)` syscall.
|
||||||
|
- Flags can be 1 for revert, 0 for success (nothing else defined)
|
||||||
|
- Function does not return
|
||||||
|
|
||||||
|
CPI: The `seal_call()` syscall takes pointer to buffer and pointer to buffer size where return data goes
|
||||||
|
- There is a 32KB limit for return data.
|
||||||
|
|
||||||
|
Parity Substrate does not write the return data to blocks.
|
||||||
|
|
||||||
|
## Rejected Solution
|
||||||
|
|
||||||
|
The concept of ephemeral accounts has been proposed a solution for this. This would
|
||||||
|
certainly work for the CPI case, but this would not work RPC or Transaction case.
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
|
||||||
|
The callee can set the return data using a new system call `sol_set_return_data(buf: *const u8, length: u64)`.
|
||||||
|
There is a limit of 1024 bytes for the returndata. This function can be called multiple times, and
|
||||||
|
will simply overwrite what was written in the last call.
|
||||||
|
|
||||||
|
The return data can be retrieved with `sol_get_return_data(buf: *mut u8, length: u64, program_id: *mut Pubkey) -> u64`.
|
||||||
|
This function copies the return buffer, and the program_id that set the return data, and
|
||||||
|
returns the length of the return data, or `0` if no return data is set. In this case, program_id is not set.
|
||||||
|
|
||||||
|
When an instruction calls `sol_invoke()`, the return data of the callee is copied into the return data
|
||||||
|
of the current instruction. This means that any return data is automatically passed up the call stack,
|
||||||
|
to the callee of the current instruction (or the RPC call).
|
||||||
|
|
||||||
|
Note that `sol_invoke()` clears the returns data before invoking the callee, so that any return data from
|
||||||
|
a previous invoke is not reused if the invoked fails to set a return data. For example:
|
||||||
|
|
||||||
|
- A invokes B
|
||||||
|
- Before entry to B, return data is cleared.0
|
||||||
|
- B sets some return data and returns
|
||||||
|
- A invokes C
|
||||||
|
- Before entry to C, return data is cleared.
|
||||||
|
- C does not set return data and returns
|
||||||
|
- A checks return data and finds it empty
|
||||||
|
|
||||||
|
Another scenario to consider:
|
||||||
|
|
||||||
|
- A invokes B
|
||||||
|
- B invokes C
|
||||||
|
- C sets return data and returns
|
||||||
|
- B does not touch return data and returns
|
||||||
|
- A gets return data from C
|
||||||
|
- A does not touch return data
|
||||||
|
- Return data from transaction is what C set.
|
||||||
|
|
||||||
|
The compute costs are calculated for getting and setting the return data using
|
||||||
|
the syscalls.
|
||||||
|
|
||||||
|
For a normal RPC or Transaction, the returndata is base64-encoded and stored along side the sol_log
|
||||||
|
strings in the [stable log](https://github.com/solana-labs/solana/blob/95292841947763bdd47ef116b40fc34d0585bca8/sdk/src/process_instruction.rs#L275-L281).
|
||||||
|
|
||||||
|
## Note on returning errors
|
||||||
|
|
||||||
|
Solidity on Ethereum allows the contract to return an error in the return data. In this case, all
|
||||||
|
the account data changes for the account should be reverted. On Solana, any non-zero exit code
|
||||||
|
for a BPF prorgram means the entire transaction fails. We do not wish to support an error return
|
||||||
|
by returning success and then returning an error in the return data. This would mean we would have
|
||||||
|
to support reverting the account data changes; this too expensive both on the VM side and the BPF
|
||||||
|
contract side.
|
||||||
|
|
||||||
|
Errors will be reported via sol_log.
|
|
@ -631,6 +631,9 @@ impl InstructionProcessor {
|
||||||
// Verify the calling program hasn't misbehaved
|
// Verify the calling program hasn't misbehaved
|
||||||
invoke_context.verify_and_update(instruction, accounts, caller_write_privileges)?;
|
invoke_context.verify_and_update(instruction, accounts, caller_write_privileges)?;
|
||||||
|
|
||||||
|
// clear the return data
|
||||||
|
invoke_context.set_return_data(None);
|
||||||
|
|
||||||
// Invoke callee
|
// Invoke callee
|
||||||
invoke_context.push(program_id, message, instruction, program_indices, accounts)?;
|
invoke_context.push(program_id, message, instruction, program_indices, accounts)?;
|
||||||
|
|
||||||
|
|
|
@ -3260,6 +3260,7 @@ name = "solana-sdk"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
|
"base64 0.13.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
"borsh",
|
"borsh",
|
||||||
"borsh-derive",
|
"borsh-derive",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <sol/log.h>
|
#include <sol/log.h>
|
||||||
#include <sol/assert.h>
|
#include <sol/assert.h>
|
||||||
#include <sol/deserialize.h>
|
#include <sol/deserialize.h>
|
||||||
|
#include <sol/return_data.h>
|
||||||
|
|
||||||
static const uint8_t TEST_SUCCESS = 1;
|
static const uint8_t TEST_SUCCESS = 1;
|
||||||
static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2;
|
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_NESTED_INVOKE_TOO_DEEP = 15;
|
||||||
static const uint8_t TEST_EXECUTABLE_LAMPORTS = 16;
|
static const uint8_t TEST_EXECUTABLE_LAMPORTS = 16;
|
||||||
static const uint8_t ADD_LAMPORTS = 17;
|
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 MINT_INDEX = 0;
|
||||||
static const int ARGUMENT_INDEX = 1;
|
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_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");
|
sol_log("Test create_program_address");
|
||||||
{
|
{
|
||||||
uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's',
|
uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's',
|
||||||
|
@ -564,6 +592,12 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||||
*accounts[0].lamports += 1;
|
*accounts[0].lamports += 1;
|
||||||
break;
|
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:
|
default:
|
||||||
sol_panic();
|
sol_panic();
|
||||||
|
|
|
@ -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_SIGNER = 9;
|
||||||
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 10;
|
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 10;
|
||||||
const uint8_t WRITE_ACCOUNT = 11;
|
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;
|
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) {
|
if (params.data_len == 0) {
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +94,12 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
||||||
sol_log("return Ok");
|
sol_log("return Ok");
|
||||||
return SUCCESS;
|
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: {
|
case RETURN_ERROR: {
|
||||||
sol_log("return error");
|
sol_log("return error");
|
||||||
return 42;
|
return 42;
|
||||||
|
|
|
@ -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,
|
||||||
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
||||||
msg,
|
msg,
|
||||||
program::{invoke, invoke_signed},
|
program::{get_return_data, invoke, invoke_signed, set_return_data},
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
pubkey::{Pubkey, PubkeyError},
|
pubkey::{Pubkey, PubkeyError},
|
||||||
system_instruction,
|
system_instruction,
|
||||||
|
@ -394,6 +394,27 @@ fn process_instruction(
|
||||||
assert_eq!(data[i], i as u8);
|
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 => {
|
TEST_PRIVILEGE_ESCALATION_SIGNER => {
|
||||||
msg!("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 VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 10;
|
||||||
pub const WRITE_ACCOUNT: u8 = 11;
|
pub const WRITE_ACCOUNT: u8 = 11;
|
||||||
pub const CREATE_AND_INIT: u8 = 12;
|
pub const CREATE_AND_INIT: u8 = 12;
|
||||||
|
pub const SET_RETURN_DATA: u8 = 13;
|
||||||
|
|
||||||
pub fn create_instruction(
|
pub fn create_instruction(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use solana_program::{
|
||||||
bpf_loader, entrypoint,
|
bpf_loader, entrypoint,
|
||||||
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
||||||
msg,
|
msg,
|
||||||
program::{invoke, invoke_signed},
|
program::{get_return_data, invoke, invoke_signed, set_return_data},
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_instruction,
|
system_instruction,
|
||||||
|
@ -27,6 +27,8 @@ fn process_instruction(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_eq!(get_return_data(), None);
|
||||||
|
|
||||||
match instruction_data[0] {
|
match instruction_data[0] {
|
||||||
VERIFY_TRANSLATIONS => {
|
VERIFY_TRANSLATIONS => {
|
||||||
msg!("verify data 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!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,8 @@ fn run_program(
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
let mut parameter_bytes = parameter_bytes.clone();
|
let mut parameter_bytes = parameter_bytes.clone();
|
||||||
{
|
{
|
||||||
|
invoke_context.set_return_data(None);
|
||||||
|
|
||||||
let mut vm = create_vm(
|
let mut vm = create_vm(
|
||||||
&loader_id,
|
&loader_id,
|
||||||
executable.as_ref(),
|
executable.as_ref(),
|
||||||
|
@ -432,6 +434,7 @@ fn test_program_bpf_sanity() {
|
||||||
("noop++", true),
|
("noop++", true),
|
||||||
("panic", false),
|
("panic", false),
|
||||||
("relative_call", true),
|
("relative_call", true),
|
||||||
|
("return_data", true),
|
||||||
("sanity", true),
|
("sanity", true),
|
||||||
("sanity++", true),
|
("sanity++", true),
|
||||||
("secp256k1_recover", true),
|
("secp256k1_recover", true),
|
||||||
|
@ -756,6 +759,7 @@ fn test_program_bpf_invoke_sanity() {
|
||||||
const TEST_WRITABLE_DEESCALATION_WRITABLE: u8 = 14;
|
const TEST_WRITABLE_DEESCALATION_WRITABLE: u8 = 14;
|
||||||
const TEST_NESTED_INVOKE_TOO_DEEP: u8 = 15;
|
const TEST_NESTED_INVOKE_TOO_DEEP: u8 = 15;
|
||||||
const TEST_EXECUTABLE_LAMPORTS: u8 = 16;
|
const TEST_EXECUTABLE_LAMPORTS: u8 = 16;
|
||||||
|
const TEST_RETURN_DATA_TOO_LARGE: u8 = 18;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[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(),
|
||||||
invoked_program_id.clone(),
|
invoked_program_id.clone(),
|
||||||
|
invoked_program_id.clone(),
|
||||||
],
|
],
|
||||||
Languages::Rust => vec![
|
Languages::Rust => vec![
|
||||||
system_program::id(),
|
system_program::id(),
|
||||||
|
@ -902,6 +907,7 @@ fn test_program_bpf_invoke_sanity() {
|
||||||
invoked_program_id.clone(),
|
invoked_program_id.clone(),
|
||||||
invoked_program_id.clone(),
|
invoked_program_id.clone(),
|
||||||
system_program::id(),
|
system_program::id(),
|
||||||
|
invoked_program_id.clone(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
|
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
|
||||||
|
@ -1030,6 +1036,12 @@ fn test_program_bpf_invoke_sanity() {
|
||||||
&[invoke_program_id.clone()],
|
&[invoke_program_id.clone()],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
do_invoke_failure_test_local(
|
||||||
|
TEST_RETURN_DATA_TOO_LARGE,
|
||||||
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
// Check resulting state
|
// Check resulting state
|
||||||
|
|
||||||
assert_eq!(43, bank.get_balance(&derived_key1));
|
assert_eq!(43, bank.get_balance(&derived_key1));
|
||||||
|
@ -1312,6 +1324,7 @@ fn assert_instruction_count() {
|
||||||
("noop", 5),
|
("noop", 5),
|
||||||
("noop++", 5),
|
("noop++", 5),
|
||||||
("relative_call", 10),
|
("relative_call", 10),
|
||||||
|
("return_data", 480),
|
||||||
("sanity", 169),
|
("sanity", 169),
|
||||||
("sanity++", 168),
|
("sanity++", 168),
|
||||||
("secp256k1_recover", 359),
|
("secp256k1_recover", 359),
|
||||||
|
|
|
@ -917,6 +917,11 @@ impl Executor for BpfExecutor {
|
||||||
let trace_string = String::from_utf8(trace_buffer).unwrap();
|
let trace_string = String::from_utf8(trace_buffer).unwrap();
|
||||||
trace!("BPF Program Instruction Trace:\n{}", trace_string);
|
trace!("BPF Program Instruction Trace:\n{}", trace_string);
|
||||||
}
|
}
|
||||||
|
drop(vm);
|
||||||
|
let return_data = invoke_context.get_return_data();
|
||||||
|
if let Some((program_id, return_data)) = return_data {
|
||||||
|
stable_log::program_return_data(&logger, program_id, return_data);
|
||||||
|
}
|
||||||
match result {
|
match result {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if status != SUCCESS {
|
if status != SUCCESS {
|
||||||
|
|
|
@ -24,7 +24,7 @@ use solana_sdk::{
|
||||||
allow_native_ids, blake3_syscall_enabled, check_seed_length,
|
allow_native_ids, blake3_syscall_enabled, check_seed_length,
|
||||||
close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar,
|
close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar,
|
||||||
enforce_aligned_host_addrs, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
|
enforce_aligned_host_addrs, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
|
||||||
secp256k1_recover_syscall_enabled,
|
return_data_syscall_enabled, secp256k1_recover_syscall_enabled,
|
||||||
},
|
},
|
||||||
hash::{Hasher, HASH_BYTES},
|
hash::{Hasher, HASH_BYTES},
|
||||||
ic_msg,
|
ic_msg,
|
||||||
|
@ -33,6 +33,7 @@ use solana_sdk::{
|
||||||
keyed_account::KeyedAccount,
|
keyed_account::KeyedAccount,
|
||||||
native_loader,
|
native_loader,
|
||||||
process_instruction::{self, stable_log, ComputeMeter, InvokeContext, Logger},
|
process_instruction::{self, stable_log, ComputeMeter, InvokeContext, Logger},
|
||||||
|
program::MAX_RETURN_DATA,
|
||||||
pubkey::{Pubkey, PubkeyError, MAX_SEEDS, MAX_SEED_LEN},
|
pubkey::{Pubkey, PubkeyError, MAX_SEEDS, MAX_SEED_LEN},
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
secp256k1_recover::{
|
secp256k1_recover::{
|
||||||
|
@ -43,6 +44,7 @@ use solana_sdk::{
|
||||||
use std::{
|
use std::{
|
||||||
alloc::Layout,
|
alloc::Layout,
|
||||||
cell::{Ref, RefCell, RefMut},
|
cell::{Ref, RefCell, RefMut},
|
||||||
|
cmp::min,
|
||||||
mem::{align_of, size_of},
|
mem::{align_of, size_of},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
slice::from_raw_parts_mut,
|
slice::from_raw_parts_mut,
|
||||||
|
@ -62,9 +64,9 @@ pub enum SyscallError {
|
||||||
Abort,
|
Abort,
|
||||||
#[error("BPF program Panicked in {0} at {1}:{2}")]
|
#[error("BPF program Panicked in {0} at {1}:{2}")]
|
||||||
Panic(String, u64, u64),
|
Panic(String, u64, u64),
|
||||||
#[error("cannot borrow invoke context")]
|
#[error("Cannot borrow invoke context")]
|
||||||
InvokeContextBorrowFailed,
|
InvokeContextBorrowFailed,
|
||||||
#[error("malformed signer seed: {0}: {1:?}")]
|
#[error("Malformed signer seed: {0}: {1:?}")]
|
||||||
MalformedSignerSeed(Utf8Error, Vec<u8>),
|
MalformedSignerSeed(Utf8Error, Vec<u8>),
|
||||||
#[error("Could not create program address with signer seeds: {0}")]
|
#[error("Could not create program address with signer seeds: {0}")]
|
||||||
BadSeeds(PubkeyError),
|
BadSeeds(PubkeyError),
|
||||||
|
@ -82,6 +84,8 @@ pub enum SyscallError {
|
||||||
TooManyAccounts,
|
TooManyAccounts,
|
||||||
#[error("Overlapping copy")]
|
#[error("Overlapping copy")]
|
||||||
CopyOverlapping,
|
CopyOverlapping,
|
||||||
|
#[error("Return data too large ({0} > {1})")]
|
||||||
|
ReturnDataTooLarge(u64, u64),
|
||||||
}
|
}
|
||||||
impl From<SyscallError> for EbpfError<BpfError> {
|
impl From<SyscallError> for EbpfError<BpfError> {
|
||||||
fn from(error: SyscallError) -> Self {
|
fn from(error: SyscallError) -> Self {
|
||||||
|
@ -173,6 +177,14 @@ pub fn register_syscalls(
|
||||||
// Memory allocator
|
// Memory allocator
|
||||||
syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?;
|
syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?;
|
||||||
|
|
||||||
|
// Return data
|
||||||
|
if invoke_context.is_feature_active(&return_data_syscall_enabled::id()) {
|
||||||
|
syscall_registry
|
||||||
|
.register_syscall_by_name(b"sol_set_return_data", SyscallSetReturnData::call)?;
|
||||||
|
syscall_registry
|
||||||
|
.register_syscall_by_name(b"sol_get_return_data", SyscallGetReturnData::call)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(syscall_registry)
|
Ok(syscall_registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +365,8 @@ pub fn bind_syscall_context_objects<'a>(
|
||||||
|
|
||||||
let is_fee_sysvar_via_syscall_active =
|
let is_fee_sysvar_via_syscall_active =
|
||||||
!invoke_context.is_feature_active(&disable_fees_sysvar::id());
|
!invoke_context.is_feature_active(&disable_fees_sysvar::id());
|
||||||
|
let is_return_data_syscall_active =
|
||||||
|
invoke_context.is_feature_active(&return_data_syscall_enabled::id());
|
||||||
|
|
||||||
let invoke_context = Rc::new(RefCell::new(invoke_context));
|
let invoke_context = Rc::new(RefCell::new(invoke_context));
|
||||||
|
|
||||||
|
@ -386,6 +400,25 @@ pub fn bind_syscall_context_objects<'a>(
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Return data
|
||||||
|
bind_feature_gated_syscall_context_object!(
|
||||||
|
vm,
|
||||||
|
is_return_data_syscall_active,
|
||||||
|
Box::new(SyscallSetReturnData {
|
||||||
|
invoke_context: invoke_context.clone(),
|
||||||
|
loader_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
bind_feature_gated_syscall_context_object!(
|
||||||
|
vm,
|
||||||
|
is_return_data_syscall_active,
|
||||||
|
Box::new(SyscallGetReturnData {
|
||||||
|
invoke_context: invoke_context.clone(),
|
||||||
|
loader_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Cross-program invocation syscalls
|
// Cross-program invocation syscalls
|
||||||
vm.bind_syscall_context_object(
|
vm.bind_syscall_context_object(
|
||||||
Box::new(SyscallInvokeSignedC {
|
Box::new(SyscallInvokeSignedC {
|
||||||
|
@ -2548,6 +2581,142 @@ fn call<'a>(
|
||||||
Ok(SUCCESS)
|
Ok(SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return data handling
|
||||||
|
pub struct SyscallSetReturnData<'a> {
|
||||||
|
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||||
|
loader_id: &'a Pubkey,
|
||||||
|
}
|
||||||
|
impl<'a> SyscallObject<BpfError> for SyscallSetReturnData<'a> {
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
addr: u64,
|
||||||
|
len: u64,
|
||||||
|
_arg3: u64,
|
||||||
|
_arg4: u64,
|
||||||
|
_arg5: u64,
|
||||||
|
memory_mapping: &MemoryMapping,
|
||||||
|
result: &mut Result<u64, EbpfError<BpfError>>,
|
||||||
|
) {
|
||||||
|
let mut invoke_context = question_mark!(
|
||||||
|
self.invoke_context
|
||||||
|
.try_borrow_mut()
|
||||||
|
.map_err(|_| SyscallError::InvokeContextBorrowFailed),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
let budget = invoke_context.get_compute_budget();
|
||||||
|
|
||||||
|
question_mark!(
|
||||||
|
invoke_context
|
||||||
|
.get_compute_meter()
|
||||||
|
.consume(len / budget.cpi_bytes_per_unit + budget.syscall_base_cost),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
if len > MAX_RETURN_DATA as u64 {
|
||||||
|
*result = Err(SyscallError::ReturnDataTooLarge(len, MAX_RETURN_DATA as u64).into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
invoke_context.set_return_data(None);
|
||||||
|
} else {
|
||||||
|
let return_data = question_mark!(
|
||||||
|
translate_slice::<u8>(memory_mapping, addr, len, self.loader_id, true),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
let program_id = *question_mark!(
|
||||||
|
invoke_context
|
||||||
|
.get_caller()
|
||||||
|
.map_err(SyscallError::InstructionError),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
invoke_context.set_return_data(Some((program_id, return_data.to_vec())));
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = Ok(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyscallGetReturnData<'a> {
|
||||||
|
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||||
|
loader_id: &'a Pubkey,
|
||||||
|
}
|
||||||
|
impl<'a> SyscallObject<BpfError> for SyscallGetReturnData<'a> {
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
return_data_addr: u64,
|
||||||
|
len: u64,
|
||||||
|
program_id_addr: u64,
|
||||||
|
_arg4: u64,
|
||||||
|
_arg5: u64,
|
||||||
|
memory_mapping: &MemoryMapping,
|
||||||
|
result: &mut Result<u64, EbpfError<BpfError>>,
|
||||||
|
) {
|
||||||
|
let invoke_context = question_mark!(
|
||||||
|
self.invoke_context
|
||||||
|
.try_borrow()
|
||||||
|
.map_err(|_| SyscallError::InvokeContextBorrowFailed),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
let budget = invoke_context.get_compute_budget();
|
||||||
|
|
||||||
|
question_mark!(
|
||||||
|
invoke_context
|
||||||
|
.get_compute_meter()
|
||||||
|
.consume(budget.syscall_base_cost),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some((program_id, return_data)) = invoke_context.get_return_data() {
|
||||||
|
if len != 0 {
|
||||||
|
let length = min(return_data.len() as u64, len);
|
||||||
|
|
||||||
|
question_mark!(
|
||||||
|
invoke_context
|
||||||
|
.get_compute_meter()
|
||||||
|
.consume((length + size_of::<Pubkey>() as u64) / budget.cpi_bytes_per_unit),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
let return_data_result = question_mark!(
|
||||||
|
translate_slice_mut::<u8>(
|
||||||
|
memory_mapping,
|
||||||
|
return_data_addr,
|
||||||
|
length,
|
||||||
|
self.loader_id,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
return_data_result.copy_from_slice(&return_data[..length as usize]);
|
||||||
|
|
||||||
|
let program_id_result = question_mark!(
|
||||||
|
translate_slice_mut::<Pubkey>(
|
||||||
|
memory_mapping,
|
||||||
|
program_id_addr,
|
||||||
|
1,
|
||||||
|
self.loader_id,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
program_id_result[0] = *program_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the actual length, rather the length returned
|
||||||
|
*result = Ok(return_data.len() as u64);
|
||||||
|
} else {
|
||||||
|
*result = Ok(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -67,6 +67,8 @@ pub struct ThisInvokeContext<'a> {
|
||||||
sysvars: RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>,
|
sysvars: RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>,
|
||||||
blockhash: &'a Hash,
|
blockhash: &'a Hash,
|
||||||
fee_calculator: &'a FeeCalculator,
|
fee_calculator: &'a FeeCalculator,
|
||||||
|
// return data and program_id that set it
|
||||||
|
return_data: Option<(Pubkey, Vec<u8>)>,
|
||||||
}
|
}
|
||||||
impl<'a> ThisInvokeContext<'a> {
|
impl<'a> ThisInvokeContext<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -116,6 +118,7 @@ impl<'a> ThisInvokeContext<'a> {
|
||||||
sysvars: RefCell::new(vec![]),
|
sysvars: RefCell::new(vec![]),
|
||||||
blockhash,
|
blockhash,
|
||||||
fee_calculator,
|
fee_calculator,
|
||||||
|
return_data: None,
|
||||||
};
|
};
|
||||||
invoke_context.push(program_id, message, instruction, program_indices, accounts)?;
|
invoke_context.push(program_id, message, instruction, program_indices, accounts)?;
|
||||||
Ok(invoke_context)
|
Ok(invoke_context)
|
||||||
|
@ -329,6 +332,12 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
|
||||||
fn get_fee_calculator(&self) -> &FeeCalculator {
|
fn get_fee_calculator(&self) -> &FeeCalculator {
|
||||||
self.fee_calculator
|
self.fee_calculator
|
||||||
}
|
}
|
||||||
|
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>) {
|
||||||
|
self.return_data = return_data;
|
||||||
|
}
|
||||||
|
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)> {
|
||||||
|
&self.return_data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub struct ThisLogger {
|
pub struct ThisLogger {
|
||||||
log_collector: Option<Rc<LogCollector>>,
|
log_collector: Option<Rc<LogCollector>>,
|
||||||
|
|
|
@ -42,6 +42,7 @@ assert_matches = { version = "1.5.0", optional = true }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
bytemuck = { version = "1.7.2", features = ["derive"] }
|
bytemuck = { version = "1.7.2", features = ["derive"] }
|
||||||
borsh = "0.9.0"
|
borsh = "0.9.0"
|
||||||
|
base64 = "0.13"
|
||||||
borsh-derive = "0.9.0"
|
borsh-derive = "0.9.0"
|
||||||
bs58 = "0.4.0"
|
bs58 = "0.4.0"
|
||||||
bv = { version = "0.11.1", features = ["serde"] }
|
bv = { version = "0.11.1", features = ["serde"] }
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
#pragma once
|
||||||
|
/**
|
||||||
|
* @brief Solana return data system calls
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <sol/types.h>
|
||||||
|
#include <sol/pubkey.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of return data
|
||||||
|
*/
|
||||||
|
#define MAX_RETURN_DATA 1024
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the return data
|
||||||
|
*
|
||||||
|
* @param bytes byte array to set
|
||||||
|
* @param bytes_len length of byte array. This may not exceed MAX_RETURN_DATA.
|
||||||
|
*/
|
||||||
|
void sol_set_return_data(const uint8_t *bytes, uint64_t bytes_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the return data
|
||||||
|
*
|
||||||
|
* @param bytes byte buffer
|
||||||
|
* @param bytes_len maximum length of buffer
|
||||||
|
* @param program_id the program_id which set the return data. Only set if there was some return data (the function returns non-zero).
|
||||||
|
* @param result length of return data (may exceed bytes_len if the return data is longer)
|
||||||
|
*/
|
||||||
|
uint64_t sol_get_return_data(const uint8_t *bytes, uint64_t bytes_len, SolPubkey *program_id);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**@}*/
|
|
@ -12,6 +12,7 @@
|
||||||
#include <sol/keccak.h>
|
#include <sol/keccak.h>
|
||||||
#include <sol/log.h>
|
#include <sol/log.h>
|
||||||
#include <sol/pubkey.h>
|
#include <sol/pubkey.h>
|
||||||
|
#include <sol/return_data.h>
|
||||||
#include <sol/secp256k1.h>
|
#include <sol/secp256k1.h>
|
||||||
#include <sol/sha.h>
|
#include <sol/sha.h>
|
||||||
#include <sol/string.h>
|
#include <sol/string.h>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::{account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction};
|
use crate::{
|
||||||
|
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
/// Invoke a cross-program instruction
|
/// Invoke a cross-program instruction
|
||||||
///
|
///
|
||||||
|
@ -35,6 +37,16 @@ pub fn invoke_signed(
|
||||||
|
|
||||||
#[cfg(target_arch = "bpf")]
|
#[cfg(target_arch = "bpf")]
|
||||||
{
|
{
|
||||||
|
extern "C" {
|
||||||
|
fn sol_invoke_signed_rust(
|
||||||
|
instruction_addr: *const u8,
|
||||||
|
account_infos_addr: *const u8,
|
||||||
|
account_infos_len: u64,
|
||||||
|
signers_seeds_addr: *const u8,
|
||||||
|
signers_seeds_len: u64,
|
||||||
|
) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
sol_invoke_signed_rust(
|
sol_invoke_signed_rust(
|
||||||
instruction as *const _ as *const u8,
|
instruction as *const _ as *const u8,
|
||||||
|
@ -54,13 +66,48 @@ pub fn invoke_signed(
|
||||||
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
|
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maximum size that can be set using sol_set_return_data()
|
||||||
|
pub const MAX_RETURN_DATA: usize = 1024;
|
||||||
|
|
||||||
|
/// Set a program's return data
|
||||||
|
pub fn set_return_data(data: &[u8]) {
|
||||||
#[cfg(target_arch = "bpf")]
|
#[cfg(target_arch = "bpf")]
|
||||||
|
{
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn sol_invoke_signed_rust(
|
fn sol_set_return_data(data: *const u8, length: u64);
|
||||||
instruction_addr: *const u8,
|
}
|
||||||
account_infos_addr: *const u8,
|
|
||||||
account_infos_len: u64,
|
unsafe { sol_set_return_data(data.as_ptr(), data.len() as u64) };
|
||||||
signers_seeds_addr: *const u8,
|
}
|
||||||
signers_seeds_len: u64,
|
|
||||||
) -> u64;
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
crate::program_stubs::sol_set_return_data(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the return data from invoked program
|
||||||
|
pub fn get_return_data() -> Option<(Pubkey, Vec<u8>)> {
|
||||||
|
#[cfg(target_arch = "bpf")]
|
||||||
|
{
|
||||||
|
use std::cmp::min;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn sol_get_return_data(data: *mut u8, length: u64, program_id: *mut Pubkey) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [0u8; MAX_RETURN_DATA];
|
||||||
|
let mut program_id = Pubkey::default();
|
||||||
|
|
||||||
|
let size =
|
||||||
|
unsafe { sol_get_return_data(buf.as_mut_ptr(), buf.len() as u64, &mut program_id) };
|
||||||
|
|
||||||
|
if size == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let size = min(size as usize, MAX_RETURN_DATA);
|
||||||
|
Some((program_id, buf[..size as usize].to_vec()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
crate::program_stubs::sol_get_return_data()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction,
|
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction,
|
||||||
program_error::UNSUPPORTED_SYSVAR,
|
program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
@ -80,6 +80,10 @@ pub trait SyscallStubs: Sync + Send {
|
||||||
*val = c;
|
*val = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn sol_set_return_data(&mut self, _data: &[u8]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DefaultSyscallStubs {}
|
struct DefaultSyscallStubs {}
|
||||||
|
@ -153,3 +157,11 @@ pub(crate) fn sol_memset(s: *mut u8, c: u8, n: usize) {
|
||||||
SYSCALL_STUBS.read().unwrap().sol_memset(s, c, n);
|
SYSCALL_STUBS.read().unwrap().sol_memset(s, c, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sol_get_return_data() -> Option<(Pubkey, Vec<u8>)> {
|
||||||
|
SYSCALL_STUBS.read().unwrap().sol_get_return_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sol_set_return_data(data: &[u8]) {
|
||||||
|
SYSCALL_STUBS.write().unwrap().sol_set_return_data(data)
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ pub struct ComputeBudget {
|
||||||
pub sysvar_base_cost: u64,
|
pub sysvar_base_cost: u64,
|
||||||
/// Number of compute units consumed to call secp256k1_recover
|
/// Number of compute units consumed to call secp256k1_recover
|
||||||
pub secp256k1_recover_cost: u64,
|
pub secp256k1_recover_cost: u64,
|
||||||
|
/// Number of compute units consumed to do a syscall without any work
|
||||||
|
pub syscall_base_cost: u64,
|
||||||
/// Optional program heap region size, if `None` then loader default
|
/// Optional program heap region size, if `None` then loader default
|
||||||
pub heap_size: Option<usize>,
|
pub heap_size: Option<usize>,
|
||||||
}
|
}
|
||||||
|
@ -95,6 +97,7 @@ impl ComputeBudget {
|
||||||
cpi_bytes_per_unit: 250, // ~50MB at 200,000 units
|
cpi_bytes_per_unit: 250, // ~50MB at 200,000 units
|
||||||
sysvar_base_cost: 100,
|
sysvar_base_cost: 100,
|
||||||
secp256k1_recover_cost: 25_000,
|
secp256k1_recover_cost: 25_000,
|
||||||
|
syscall_base_cost: 100,
|
||||||
heap_size: None,
|
heap_size: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,6 +207,10 @@ pub mod check_seed_length {
|
||||||
solana_sdk::declare_id!("8HYXgkoKGreAMA3MfJkdjbKNVbfZRQP3jqFpa7iqN4v7");
|
solana_sdk::declare_id!("8HYXgkoKGreAMA3MfJkdjbKNVbfZRQP3jqFpa7iqN4v7");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod return_data_syscall_enabled {
|
||||||
|
solana_sdk::declare_id!("BJVXq6NdLC7jCDGjfqJv7M1XHD4Y13VrpDqRF2U7UBcC");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -254,6 +258,7 @@ lazy_static! {
|
||||||
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
|
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
|
||||||
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
|
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
|
||||||
(check_seed_length::id(), "Check program address seed lengths"),
|
(check_seed_length::id(), "Check program address seed lengths"),
|
||||||
|
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall")
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -117,6 +117,10 @@ pub trait InvokeContext {
|
||||||
fn get_blockhash(&self) -> &Hash;
|
fn get_blockhash(&self) -> &Hash;
|
||||||
/// Get this invocation's `FeeCalculator`
|
/// Get this invocation's `FeeCalculator`
|
||||||
fn get_fee_calculator(&self) -> &FeeCalculator;
|
fn get_fee_calculator(&self) -> &FeeCalculator;
|
||||||
|
/// Set the return data
|
||||||
|
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>);
|
||||||
|
/// Get the return data
|
||||||
|
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience macro to log a message with an `Rc<RefCell<dyn Logger>>`
|
/// Convenience macro to log a message with an `Rc<RefCell<dyn Logger>>`
|
||||||
|
@ -197,6 +201,8 @@ pub struct BpfComputeBudget {
|
||||||
pub sysvar_base_cost: u64,
|
pub sysvar_base_cost: u64,
|
||||||
/// Number of compute units consumed to call secp256k1_recover
|
/// Number of compute units consumed to call secp256k1_recover
|
||||||
pub secp256k1_recover_cost: u64,
|
pub secp256k1_recover_cost: u64,
|
||||||
|
/// Number of compute units consumed to do a syscall without any work
|
||||||
|
pub syscall_base_cost: u64,
|
||||||
/// Optional program heap region size, if `None` then loader default
|
/// Optional program heap region size, if `None` then loader default
|
||||||
pub heap_size: Option<usize>,
|
pub heap_size: Option<usize>,
|
||||||
}
|
}
|
||||||
|
@ -217,6 +223,7 @@ impl From<ComputeBudget> for BpfComputeBudget {
|
||||||
max_cpi_instruction_size: item.max_cpi_instruction_size,
|
max_cpi_instruction_size: item.max_cpi_instruction_size,
|
||||||
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
|
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
|
||||||
sysvar_base_cost: item.sysvar_base_cost,
|
sysvar_base_cost: item.sysvar_base_cost,
|
||||||
|
syscall_base_cost: item.syscall_base_cost,
|
||||||
secp256k1_recover_cost: item.secp256k1_recover_cost,
|
secp256k1_recover_cost: item.secp256k1_recover_cost,
|
||||||
heap_size: item.heap_size,
|
heap_size: item.heap_size,
|
||||||
}
|
}
|
||||||
|
@ -240,6 +247,7 @@ impl From<BpfComputeBudget> for ComputeBudget {
|
||||||
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
|
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
|
||||||
sysvar_base_cost: item.sysvar_base_cost,
|
sysvar_base_cost: item.sysvar_base_cost,
|
||||||
secp256k1_recover_cost: item.secp256k1_recover_cost,
|
secp256k1_recover_cost: item.secp256k1_recover_cost,
|
||||||
|
syscall_base_cost: item.syscall_base_cost,
|
||||||
heap_size: item.heap_size,
|
heap_size: item.heap_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,6 +321,25 @@ pub mod stable_log {
|
||||||
ic_logger_msg!(logger, "Program log: {}", message);
|
ic_logger_msg!(logger, "Program log: {}", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log return data as from the program itself. This line will not be present if no return
|
||||||
|
/// data was set, or if the return data was set to zero length.
|
||||||
|
///
|
||||||
|
/// The general form is:
|
||||||
|
///
|
||||||
|
/// ```notrust
|
||||||
|
/// "Program return data: <program-id> <program-generated-data-in-base64>"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// That is, any program-generated output is guaranteed to be prefixed by "Program return data: "
|
||||||
|
pub fn program_return_data(logger: &Rc<RefCell<dyn Logger>>, program_id: &Pubkey, data: &[u8]) {
|
||||||
|
ic_logger_msg!(
|
||||||
|
logger,
|
||||||
|
"Program return data: {} {}",
|
||||||
|
program_id,
|
||||||
|
base64::encode(data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Log successful program execution.
|
/// Log successful program execution.
|
||||||
///
|
///
|
||||||
/// The general form is:
|
/// The general form is:
|
||||||
|
@ -397,7 +424,9 @@ pub struct MockInvokeContext<'a> {
|
||||||
pub disabled_features: HashSet<Pubkey>,
|
pub disabled_features: HashSet<Pubkey>,
|
||||||
pub blockhash: Hash,
|
pub blockhash: Hash,
|
||||||
pub fee_calculator: FeeCalculator,
|
pub fee_calculator: FeeCalculator,
|
||||||
|
pub return_data: Option<(Pubkey, Vec<u8>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MockInvokeContext<'a> {
|
impl<'a> MockInvokeContext<'a> {
|
||||||
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
|
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
|
||||||
let compute_budget = ComputeBudget::default();
|
let compute_budget = ComputeBudget::default();
|
||||||
|
@ -415,6 +444,7 @@ impl<'a> MockInvokeContext<'a> {
|
||||||
disabled_features: HashSet::default(),
|
disabled_features: HashSet::default(),
|
||||||
blockhash: Hash::default(),
|
blockhash: Hash::default(),
|
||||||
fee_calculator: FeeCalculator::default(),
|
fee_calculator: FeeCalculator::default(),
|
||||||
|
return_data: None,
|
||||||
};
|
};
|
||||||
invoke_context
|
invoke_context
|
||||||
.invoke_stack
|
.invoke_stack
|
||||||
|
@ -543,4 +573,10 @@ impl<'a> InvokeContext for MockInvokeContext<'a> {
|
||||||
fn get_fee_calculator(&self) -> &FeeCalculator {
|
fn get_fee_calculator(&self) -> &FeeCalculator {
|
||||||
&self.fee_calculator
|
&self.fee_calculator
|
||||||
}
|
}
|
||||||
|
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>) {
|
||||||
|
self.return_data = return_data;
|
||||||
|
}
|
||||||
|
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)> {
|
||||||
|
&self.return_data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue