Skip to content

Commit 7bb7572

Browse files
mriiseStebalien
authored andcommitted
EVM: fix EXTCODE_ ops (#870)
* wip fixes * cleanup implementation * return 0 for non existient address in extcodesize * use enum instead of result result maddness * fmt & cleanup some logic * update tests for the remaining two ext opcodes * redo logic to make things a bit more clearn * fix bugs & keep bytecode logic seperate, verify expectations before asserting for better debugging help * add another test case for ext op * use closure for early return * nit: reduce indentation * fix: formatting * remove unnecessary closure * fix: make delegatecall behave more like the EVM - address todo - use the new get_cid_type * fix some review nits Co-authored-by: Steven Allen <steven@stebalien.com>
1 parent 2c0d187 commit 7bb7572

File tree

6 files changed

+326
-113
lines changed

6 files changed

+326
-113
lines changed

actors/evm/src/interpreter/instructions/call.rs

+23-15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use fvm_shared::{address::Address, METHOD_SEND};
55

66
use crate::interpreter::precompiles::PrecompileContext;
77

8+
use super::ext::{get_cid_type, get_evm_bytecode_cid, ContractType};
9+
810
use {
911
super::memory::{copy_to_memory, get_memory_region},
1012
crate::interpreter::address::EthAddress,
@@ -255,22 +257,28 @@ pub fn call_generic<RT: Runtime>(
255257
system.send_with_gas(&dst_addr, method, params, value, gas_limit, read_only)
256258
}
257259
}
258-
CallKind::DelegateCall => {
259-
// first invoke GetBytecode to get the code CID from the target
260-
let code = crate::interpreter::instructions::ext::get_evm_bytecode_cid(
261-
system.rt, dst,
262-
)?;
263-
264-
// and then invoke self with delegate; readonly context is sticky
265-
let params = DelegateCallParams { code, input: input_data.to_vec() };
266-
system.send(
267-
&system.rt.message().receiver(),
268-
Method::InvokeContractDelegate as u64,
269-
RawBytes::serialize(&params)?,
270-
TokenAmount::from(&value),
271-
)
272-
}
260+
CallKind::DelegateCall => match get_cid_type(system.rt, dst) {
261+
ContractType::EVM(dst_addr) => {
262+
// If we're calling an actual EVM actor, get it's code.
263+
let code = get_evm_bytecode_cid(system.rt, &dst_addr)?;
273264

265+
// and then invoke self with delegate; readonly context is sticky
266+
let params = DelegateCallParams { code, input: input_data.into() };
267+
system.send(
268+
&system.rt.message().receiver(),
269+
Method::InvokeContractDelegate as u64,
270+
RawBytes::serialize(&params)?,
271+
TokenAmount::from(&value),
272+
)
273+
}
274+
// If we're calling an account or a non-existent actor, return nothing because
275+
// this is how the EVM behaves.
276+
ContractType::Account | ContractType::NotFound => Ok(RawBytes::default()),
277+
// If we're calling a "native" actor, always revert.
278+
ContractType::Native(_) => {
279+
Err(ActorError::forbidden("cannot delegate-call to native actors".into()))
280+
}
281+
},
274282
CallKind::CallCode => Err(ActorError::unchecked(
275283
EVM_CONTRACT_EXECUTION_ERROR,
276284
"unsupported opcode".to_string(),

actors/evm/src/interpreter/instructions/ext.rs

+57-30
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@ use {
1212
fil_actors_runtime::runtime::Runtime,
1313
};
1414

15-
#[inline]
1615
pub fn extcodesize(
1716
_state: &mut ExecutionState,
1817
system: &System<impl Runtime>,
1918
addr: U256,
2019
) -> Result<U256, StatusCode> {
21-
// TODO we're fetching the entire block here just to get its size. We should instead use
20+
// TODO (M2.2) we're fetching the entire block here just to get its size. We should instead use
2221
// the ipld::block_stat syscall, but the Runtime nor the Blockstore expose it.
2322
// Tracked in https://github.com/filecoin-project/ref-fvm/issues/867
24-
let len = get_evm_bytecode_cid(system.rt, addr)
25-
.and_then(|cid| get_evm_bytecode(system.rt, &cid))
26-
.map(|bytecode| bytecode.len())?;
23+
let len = match get_cid_type(system.rt, addr) {
24+
ContractType::EVM(addr) => {
25+
get_evm_bytecode(system.rt, &addr).map(|bytecode| bytecode.len())?
26+
}
27+
ContractType::Native(_) => 1,
28+
// account and not found are flattened to 0 size
29+
_ => 0,
30+
};
2731

2832
Ok(len.into())
2933
}
@@ -33,8 +37,16 @@ pub fn extcodehash(
3337
system: &System<impl Runtime>,
3438
addr: U256,
3539
) -> Result<U256, StatusCode> {
36-
let cid = get_evm_bytecode_cid(system.rt, addr)?;
37-
let digest = cid.hash().digest();
40+
let addr = match get_cid_type(system.rt, addr) {
41+
ContractType::EVM(a) => Ok(a),
42+
_ => Err(StatusCode::InvalidArgument(
43+
"Cannot invoke EXTCODEHASH for non-EVM actor.".to_string(),
44+
)),
45+
}?;
46+
let bytecode_cid = get_evm_bytecode_cid(system.rt, &addr)?;
47+
48+
let digest = bytecode_cid.hash().digest();
49+
3850
// Take the first 32 bytes of the Multihash
3951
let digest_len = digest.len().min(32);
4052
Ok(digest[..digest_len].into())
@@ -48,40 +60,55 @@ pub fn extcodecopy(
4860
data_offset: U256,
4961
size: U256,
5062
) -> Result<(), StatusCode> {
51-
let bytecode =
52-
get_evm_bytecode_cid(system.rt, addr).and_then(|cid| get_evm_bytecode(system.rt, &cid))?;
63+
let bytecode = match get_cid_type(system.rt, addr) {
64+
ContractType::EVM(addr) => get_evm_bytecode(system.rt, &addr)?,
65+
ContractType::NotFound | ContractType::Account => Vec::new(),
66+
// calling EXTCODECOPY on native actors results with a single byte 0xFE which solidtiy uses for its `assert`/`throw` methods
67+
// and in general invalid EVM bytecode
68+
_ => vec![0xFE],
69+
};
5370

5471
copy_to_memory(&mut state.memory, dest_offset, size, data_offset, bytecode.as_slice(), true)
5572
}
5673

57-
pub fn get_evm_bytecode_cid(rt: &impl Runtime, addr: U256) -> Result<Cid, StatusCode> {
58-
let addr: EthAddress = addr.into();
59-
let addr: Address = addr.try_into()?;
60-
// TODO: just return none in most of these cases?
61-
let actor_id = rt.resolve_address(&addr).ok_or_else(|| {
62-
StatusCode::InvalidArgument("failed to resolve address".to_string())
63-
// TODO better error code
64-
})?;
74+
#[derive(Debug)]
75+
pub enum ContractType {
76+
/// EVM Address and the CID of the actor (not the bytecode)
77+
EVM(Address),
78+
Native(Cid),
79+
Account,
80+
NotFound,
81+
}
6582

66-
let evm_cid = rt.get_code_cid_for_type(Type::EVM);
67-
let target_cid = rt.get_actor_code_cid(&actor_id);
83+
/// Resolves an address to the address type
84+
pub fn get_cid_type(rt: &impl Runtime, addr: U256) -> ContractType {
85+
let addr: EthAddress = addr.into();
6886

69-
if Some(evm_cid) != target_cid {
70-
return Err(StatusCode::InvalidArgument(
71-
"cannot invoke EXTCODESIZE for non-EVM actor".to_string(),
72-
)); // TODO better error code
73-
}
87+
addr.try_into()
88+
.ok() // into filecoin address
89+
.and_then(|addr| rt.resolve_address(&addr)) // resolve actor id
90+
.and_then(|id| rt.get_actor_code_cid(&id).map(|cid| (id, cid))) // resolve code cid
91+
.map(|(id, cid)| match rt.resolve_builtin_actor_type(&cid) {
92+
// TODO part of current account abstraction hack where embryos are accounts
93+
Some(Type::Account | Type::Embryo) => ContractType::Account,
94+
Some(Type::EVM) => ContractType::EVM(Address::new_id(id)),
95+
// remaining builtin actors are native
96+
_ => ContractType::Native(cid),
97+
})
98+
.unwrap_or(ContractType::NotFound)
99+
}
74100

75-
let cid = rt
76-
.send(&addr, crate::Method::GetBytecode as u64, Default::default(), TokenAmount::zero())?
77-
.deserialize()?;
78-
Ok(cid)
101+
pub fn get_evm_bytecode_cid(rt: &impl Runtime, addr: &Address) -> Result<Cid, ActorError> {
102+
Ok(rt
103+
.send(addr, crate::Method::GetBytecode as u64, Default::default(), TokenAmount::zero())?
104+
.deserialize()?)
79105
}
80106

81-
pub fn get_evm_bytecode(rt: &impl Runtime, cid: &Cid) -> Result<Vec<u8>, StatusCode> {
107+
pub fn get_evm_bytecode(rt: &impl Runtime, addr: &Address) -> Result<Vec<u8>, StatusCode> {
108+
let cid = get_evm_bytecode_cid(rt, addr)?;
82109
let raw_bytecode = rt
83110
.store()
84-
.get(cid) // TODO this is inefficient; should call stat here.
111+
.get(&cid) // TODO this is inefficient; should call stat here.
85112
.map_err(|e| StatusCode::InternalError(format!("failed to get bytecode block: {}", &e)))?
86113
.ok_or_else(|| ActorError::not_found("bytecode block not found".to_string()))?;
87114
Ok(raw_bytecode)

actors/evm/tests/asm.rs

+9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ const PRELUDE: &str = r#"
2323
dup1
2424
revert
2525
%end
26+
27+
%macro return_stack_word()
28+
# store at 0x00
29+
push1 0x00
30+
mstore
31+
push1 0x20 # always return a full word
32+
push1 0x00
33+
return
34+
%end
2635
"#;
2736

2837
#[allow(dead_code)]

actors/evm/tests/create.rs

-10
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ mod util;
1616
pub fn magic_precompile_contract() -> Vec<u8> {
1717
let init = "";
1818
let body = r#"
19-
20-
21-
%macro return_stack_word()
22-
push1 0x00
23-
mstore
24-
push1 0x20 # always return a full word
25-
push1 0x00
26-
return
27-
%end
28-
2919
# magic value, used as initcode
3020
push16 0x666F6F206261722062617A20626F7879 # foo bar baz boxy
3121
push2 0x0100 # offset of input data

0 commit comments

Comments
 (0)