Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVM: Refactor FVM precompiles #1047

Merged
merged 8 commits into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 88 additions & 29 deletions actors/evm/src/interpreter/precompiles/fvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use crate::interpreter::{instructions::call::CallKind, precompiles::NativeType,
use super::{parameter::ParameterReader, PrecompileContext, PrecompileError, PrecompileResult};

/// Read right padded BE encoded low u64 ID address from a u256 word.
/// Returns variant of [`BuiltinType`] encoded as a u256 word.
/// Returns nothing inputs >2^65
/// - Reverts if the input is greater than `MAX::u64`.
/// - Returns variant of [`BuiltinType`] encoded as a u256 word.
pub(super) fn get_actor_type<RT: Runtime>(
system: &mut System<RT>,
input: &[u8],
Expand Down Expand Up @@ -130,11 +130,7 @@ pub(super) fn resolve_address<RT: Runtime>(
input: &[u8],
_: PrecompileContext,
) -> PrecompileResult {
let mut input_params = ParameterReader::new(input);

let len = input_params.read_param::<u32>()? as usize;
let addr_bytes = input_params.read_padded(len);
let addr = match Address::from_bytes(&addr_bytes) {
let addr = match Address::from_bytes(&input) {
Ok(o) => o,
Err(e) => {
log::debug!(target: "evm", "Address parsing failed: {e}");
Expand All @@ -151,14 +147,26 @@ pub(super) fn resolve_address<RT: Runtime>(
.unwrap_or_default())
}

/// Errors:
/// TODO should just give 0s?
/// - `IncorrectInputSize` if offset is larger than total input length
/// - `InvalidInput` if supplied address bytes isnt a filecoin address
/// Calls an actor by address.
///
/// Parameters are encoded according to the solidity ABI, with no function selector:
///
/// ```text
/// u64 method
/// u256 value
/// u64 flags (1 for read-only, 0 otherwise)
/// u64 codec (0x71 for "dag-cbor", or `0` for "nothing")
/// bytes params (must be empty if the codec is 0x0)
/// bytes address
/// ```
///
/// Returns:
/// Returns (also solidity ABI encoded):
///
/// `[int256 exit_code, uint codec, uint offset, uint size, []bytes <actor return value>]`
/// ```text
/// i256 exit_code
/// u64 codec
/// bytes return_value
/// ```
///
/// for exit_code:
/// - negative values are system errors
Expand All @@ -168,6 +176,48 @@ pub(super) fn call_actor<RT: Runtime>(
system: &mut System<RT>,
input: &[u8],
ctx: PrecompileContext,
) -> PrecompileResult {
call_actor_shared(system, input, ctx, false)
}

/// Calls an actor by the actor's actor ID.
///
/// Parameters are encoded according to the solidity ABI, with no function selector:
///
/// ```text
/// u64 method
/// u256 value
/// u64 flags (1 for read-only, 0 otherwise)
/// u64 codec (0x71 for "dag-cbor", or `0` for "nothing")
/// bytes params (must be empty if the codec is 0x0)
/// u64 actor_id
/// ```
///
/// Returns (also solidity ABI encoded):
///
/// ```text
/// i256 exit_code
/// u64 codec
/// bytes return_value
/// ```
///
/// for exit_code:
/// - negative values are system errors
/// - positive are user errors (from the called actor)
/// - 0 is success
pub(super) fn call_actor_id<RT: Runtime>(
system: &mut System<RT>,
input: &[u8],
ctx: PrecompileContext,
) -> PrecompileResult {
call_actor_shared(system, input, ctx, true)
}

pub(super) fn call_actor_shared<RT: Runtime>(
system: &mut System<RT>,
input: &[u8],
ctx: PrecompileContext,
by_id: bool,
) -> PrecompileResult {
// ----- Input Parameters -------

Expand All @@ -186,8 +236,22 @@ pub(super) fn call_actor<RT: Runtime>(

let codec: u64 = input_params.read_param()?;

let send_data_size = input_params.read_param::<u32>()? as usize;
let address_size = input_params.read_param::<u32>()? as usize;
let params_off: u32 = input_params.read_param()?;
let id_or_addr_off: u64 = input_params.read_param()?;

input_params.seek(params_off.try_into()?);
let params_len: u32 = input_params.read_param()?;
let params = input_params.read_padded(params_len.try_into()?);

let address = if by_id {
Address::new_id(id_or_addr_off)
} else {
input_params.seek(id_or_addr_off.try_into()?);
let addr_len: u32 = input_params.read_param()?;
let addr_bytes = input_params
.read_padded(addr_len.try_into().map_err(|_| PrecompileError::InvalidInput)?);
Address::from_bytes(&addr_bytes).map_err(|_| PrecompileError::InvalidInput)?
};

if method <= EVM_MAX_RESERVED_METHOD {
return Err(PrecompileError::InvalidInput);
Expand All @@ -196,14 +260,10 @@ pub(super) fn call_actor<RT: Runtime>(
// ------ Begin Call -------

let result = {
let input_data = input_params.read_padded(send_data_size);
let address = input_params.read_padded(address_size);
let address = Address::from_bytes(&address).map_err(|_| PrecompileError::InvalidInput)?;

// TODO only CBOR or "nothing" for now
let params = match codec {
fvm_ipld_encoding::DAG_CBOR => Some(IpldBlock { codec, data: input_data.into() }),
0 if input_data.is_empty() => None,
fvm_ipld_encoding::DAG_CBOR => Some(IpldBlock { codec, data: params.into() }),
0 if params.is_empty() => None,
_ => return Err(PrecompileError::InvalidInput),
};
system.send(&address, method, params, TokenAmount::from(&value), Some(ctx.gas_limit), flags)
Expand All @@ -228,20 +288,19 @@ pub(super) fn call_actor<RT: Runtime>(
Ok(ret) => (U256::zero(), ret),
};

const NUM_OUTPUT_PARAMS: u32 = 4;

let ret_blk = data.unwrap_or(IpldBlock { codec: 0, data: vec![] });
let offset = NUM_OUTPUT_PARAMS * 32;

let mut output = Vec::with_capacity(NUM_OUTPUT_PARAMS as usize * 32 + ret_blk.data.len());
let mut output = Vec::with_capacity(4 * 32 + ret_blk.data.len());
output.extend_from_slice(&exit_code.to_bytes());
output.extend_from_slice(&U256::from(ret_blk.codec).to_bytes());
output.extend_from_slice(&U256::from(offset).to_bytes());
output.extend_from_slice(&U256::from(output.len() + 32).to_bytes());
output.extend_from_slice(&U256::from(ret_blk.data.len()).to_bytes());
// NOTE:
// we dont pad out to 32 bytes here, the idea being that users will already be in the "everything is bytes" mode
// and will want re-pack align and whatever else by themselves
output.extend_from_slice(&ret_blk.data);
// Pad out to the next increment of 32 bytes for solidity compatibility.
let offset = output.len() % 32;
if offset > 0 {
output.resize(output.len() - offset + 32, 0);
}
output
};

Expand Down
15 changes: 11 additions & 4 deletions actors/evm/src/interpreter/precompiles/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::marker::PhantomData;
use std::{marker::PhantomData, num::TryFromIntError};

use fil_actors_runtime::runtime::Runtime;
use substrate_bn::{CurveError, GroupError};
Expand All @@ -10,7 +10,7 @@ mod fvm;
pub mod parameter;

use evm::{blake2f, ec_add, ec_mul, ec_pairing, ec_recover, identity, modexp, ripemd160, sha256};
use fvm::{call_actor, get_actor_type, lookup_delegated_address, resolve_address};
use fvm::{call_actor, call_actor_id, get_actor_type, lookup_delegated_address, resolve_address};

// really I'd want to have context as a type parameter, but since the table we generate must have the same types (or dyn) its messy
type PrecompileFn<RT> = unsafe fn(*mut System<RT>, &[u8], PrecompileContext) -> PrecompileResult;
Expand Down Expand Up @@ -52,12 +52,13 @@ const fn gen_evm_precompiles<RT: Runtime>() -> [PrecompileFn<RT>; 9] {
}
}

const fn gen_native_precompiles<RT: Runtime>() -> [PrecompileFn<RT>; 4] {
const fn gen_native_precompiles<RT: Runtime>() -> [PrecompileFn<RT>; 5] {
precompiles! {
resolve_address, // 0xfe00..01 resolve_address
lookup_delegated_address, // 0xfe00..02 lookup_delegated_address
call_actor, // 0xfe00..03 call_actor
get_actor_type, // 0xfe00..04 get_actor_type
call_actor_id, // 0xfe00..05 call_actor_id
}
}

Expand All @@ -72,7 +73,7 @@ pub struct Precompiles<RT>(PhantomData<RT>);

impl<RT: Runtime> Precompiles<RT> {
const EVM_PRECOMPILES: [PrecompileFn<RT>; 9] = gen_evm_precompiles();
const NATIVE_PRECOMPILES: [PrecompileFn<RT>; 4] = gen_native_precompiles();
const NATIVE_PRECOMPILES: [PrecompileFn<RT>; 5] = gen_native_precompiles();

fn lookup_precompile(addr: &EthAddress) -> Option<PrecompileFn<RT>> {
let [prefix, _m @ .., index] = addr.0;
Expand Down Expand Up @@ -120,6 +121,12 @@ pub enum PrecompileError {
CallForbidden,
}

impl From<TryFromIntError> for PrecompileError {
fn from(_: TryFromIntError) -> Self {
Self::InvalidInput
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct PrecompileContext {
pub call_type: CallKind,
Expand Down
12 changes: 11 additions & 1 deletion actors/evm/src/interpreter/precompiles/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,22 @@ impl_param_int!(u16 i16 u32 i32 u64 i64);
/// Provides a nice API interface for reading Parameters from input. This API treats the input as if
/// it is followed by infinite zeros.
pub(super) struct ParameterReader<'a> {
full: &'a [u8],
slice: &'a [u8],
}
mriise marked this conversation as resolved.
Show resolved Hide resolved

impl<'a> ParameterReader<'a> {
pub(super) fn new(slice: &'a [u8]) -> Self {
ParameterReader { slice }
ParameterReader { full: slice, slice }
}

/// Seek to an offset from the beginning of the input.
pub fn seek(&mut self, offset: usize) {
if offset > self.full.len() {
self.slice = &[];
} else {
self.slice = &self.full[offset..];
}
}

/// Drop a fixed number of bytes, and return an error if said bytes are not zeros.
Expand Down
20 changes: 13 additions & 7 deletions actors/evm/tests/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ push1 0x00 # offset
push1 0x01 # dest offset (we have already written the exit code of the call)
returndatacopy

returndatasize
returndatasize
push1 0x01 # (add 1 to the returndatasize to accommodate for the exitcode)
add
push1 0x00
Expand Down Expand Up @@ -443,7 +443,7 @@ fn test_callactor_restrict() {
fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_input: bool) {
let contract = callactor_proxy_contract();

const CALLACTOR_NUM_PARAMS: usize = 6;
const CALLACTOR_NUM_PARAMS: usize = 8;

// construct the proxy
let mut rt = util::construct_and_verify(contract);
Expand All @@ -460,20 +460,25 @@ fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_i
let send_flags = SendFlags::default();
let codec = U256::from(0);

let proxy_call_input_data = vec![];
let data_size = U256::from(proxy_call_input_data.len());
mriise marked this conversation as resolved.
Show resolved Hide resolved

let target_bytes = target.to_bytes();
let target_size = U256::from(target_bytes.len());

let proxy_call_input_data = vec![];
let data_size = U256::from(proxy_call_input_data.len());
let data_off = U256::from(6 * 32);
let target_off = data_off + 32 + data_size;

contract_params.extend_from_slice(&method.to_bytes());
contract_params.extend_from_slice(&value.to_bytes());
contract_params.extend_from_slice(&U256::from(send_flags.bits()).to_bytes());
contract_params.extend_from_slice(&codec.to_bytes());
contract_params.extend_from_slice(&data_off.to_bytes());
contract_params.extend_from_slice(&target_off.to_bytes());
contract_params.extend_from_slice(&data_size.to_bytes());
contract_params.extend_from_slice(&proxy_call_input_data);
contract_params.extend_from_slice(&target_size.to_bytes());
contract_params.extend_from_slice(&target_bytes);
contract_params.extend_from_slice(&proxy_call_input_data);

assert_eq!(
32 * CALLACTOR_NUM_PARAMS + target_bytes.len() + proxy_call_input_data.len(),
Expand Down Expand Up @@ -562,7 +567,8 @@ fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_i
assert_zero_bytes::<4>(bytes);
u32::from_be_bytes(bytes[28..32].try_into().unwrap())
};
let data = Vec::from(&src[offset as usize..(offset + size) as usize]);

let data = Vec::from(&src[128..128 + size as usize]);

Self { exit_code, codec, data_offset: offset, data_size: size, data }
}
Expand All @@ -572,7 +578,7 @@ fn test_callactor_inner(method_num: MethodNum, exit_code: ExitCode, valid_call_i
let expected = CallActorReturn {
exit_code,
codec: send_return.clone().codec,
data_offset: 128,
data_offset: 96,
data_size: send_return.clone().data.len() as u32,
data: send_return.data,
};
Expand Down
Loading