Skip to content

Commit 3df8736

Browse files
committed
redo logic to make things a bit more clearn
1 parent 6811569 commit 3df8736

File tree

2 files changed

+162
-160
lines changed

2 files changed

+162
-160
lines changed

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

+110-96
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use fvm_ipld_encoding::{BytesDe, BytesSer};
22
use fvm_shared::{address::Address, METHOD_SEND};
3+
use num_traits::Zero;
34

45
use crate::interpreter::precompiles::PrecompileContext;
56

@@ -122,112 +123,125 @@ pub fn call<RT: Runtime>(
122123
&[]
123124
};
124125

125-
if precompiles::Precompiles::<RT>::is_precompile(&dst) {
126-
let context = PrecompileContext {
127-
is_static: matches!(kind, CallKind::StaticCall) || system.readonly,
128-
gas,
129-
value,
130-
};
131-
132-
match precompiles::Precompiles::call_precompile(system.rt, dst, input_data, context)
133-
.map_err(StatusCode::from)
134-
{
135-
Ok(return_data) => (1, return_data),
136-
Err(status) => {
137-
let msg = format!("{}", status);
138-
(0, msg.as_bytes().to_vec())
126+
'early_ret: {
127+
if precompiles::Precompiles::<RT>::is_precompile(&dst) {
128+
let context = PrecompileContext {
129+
is_static: matches!(kind, CallKind::StaticCall) || system.readonly,
130+
gas,
131+
value,
132+
};
133+
134+
match precompiles::Precompiles::call_precompile(system.rt, dst, input_data, context)
135+
.map_err(StatusCode::from)
136+
{
137+
Ok(return_data) => (1, return_data),
138+
Err(status) => {
139+
let msg = format!("{}", status);
140+
(0, msg.as_bytes().to_vec())
141+
}
139142
}
140-
}
141-
} else {
142-
let call_result = match kind {
143-
CallKind::Call | CallKind::StaticCall => {
144-
let dst_addr: EthAddress = dst.into();
145-
let dst_addr: Address = dst_addr.try_into().expect("address is a precompile");
146-
147-
// Special casing for account/embryo/non-existent actors: we just do a SEND (method 0)
148-
// which allows us to transfer funds (and create embryos)
149-
let target_actor_code = system
150-
.rt
151-
.resolve_address(&dst_addr)
152-
.and_then(|actor_id| system.rt.get_actor_code_cid(&actor_id));
153-
let target_actor_type = target_actor_code
154-
.as_ref()
155-
.and_then(|cid| system.rt.resolve_builtin_actor_type(cid));
156-
let actor_exists = target_actor_code.is_some();
157-
158-
if !actor_exists && value.is_zero() {
159-
// If the actor doesn't exist and we're not sending value, return with
160-
// "success". The EVM only auto-creates actors when sending value.
161-
//
162-
// NOTE: this will also apply if we're in read-only mode, because we can't
163-
// send value in read-only mode anyways.
164-
Ok(RawBytes::default())
165-
} else {
166-
let method = if !actor_exists
167-
|| matches!(target_actor_type, Some(Type::Embryo | Type::Account))
168-
{
169-
// If the target actor doesn't exist or is an account or an embryo,
170-
// switch to a basic "send" so the call will still work even if the
171-
// target actor would reject a normal ethereum call.
172-
METHOD_SEND
173-
} else if system.readonly || kind == CallKind::StaticCall {
174-
// Invoke, preserving read-only mode.
175-
Method::InvokeContractReadOnly as u64
143+
} else {
144+
let dst_addr: EthAddress = dst.into();
145+
let dst_addr: Address = dst_addr.try_into().expect("address is a precompile");
146+
147+
let call_result = match kind {
148+
CallKind::Call | CallKind::StaticCall => {
149+
// Special casing for account/embryo/non-existent actors: we just do a SEND (method 0)
150+
// which allows us to transfer funds (and create embryos)
151+
let target_actor_code = system
152+
.rt
153+
.resolve_address(&dst_addr)
154+
.and_then(|actor_id| system.rt.get_actor_code_cid(&actor_id));
155+
let target_actor_type = target_actor_code
156+
.as_ref()
157+
.and_then(|cid| system.rt.resolve_builtin_actor_type(cid));
158+
let actor_exists = target_actor_code.is_some();
159+
160+
if !actor_exists && value.is_zero() {
161+
// If the actor doesn't exist and we're not sending value, return with
162+
// "success". The EVM only auto-creates actors when sending value.
163+
//
164+
// NOTE: this will also apply if we're in read-only mode, because we can't
165+
// send value in read-only mode anyways.
166+
Ok(RawBytes::default())
176167
} else {
177-
// Otherwise, invoke normally.
178-
Method::InvokeContract as u64
168+
let method = if !actor_exists
169+
|| matches!(target_actor_type, Some(Type::Embryo | Type::Account))
170+
{
171+
// If the target actor doesn't exist or is an account or an embryo,
172+
// switch to a basic "send" so the call will still work even if the
173+
// target actor would reject a normal ethereum call.
174+
METHOD_SEND
175+
} else if system.readonly || kind == CallKind::StaticCall {
176+
// Invoke, preserving read-only mode.
177+
Method::InvokeContractReadOnly as u64
178+
} else {
179+
// Otherwise, invoke normally.
180+
Method::InvokeContract as u64
181+
};
182+
system.send(
183+
&dst_addr,
184+
method,
185+
// TODO: support IPLD codecs #758
186+
RawBytes::serialize(BytesSer(input_data))?,
187+
TokenAmount::from(&value),
188+
)
189+
}
190+
}
191+
CallKind::DelegateCall => {
192+
// first invoke GetBytecode to get the code CID from the target
193+
let cid = match system
194+
.rt
195+
.resolve_address(&dst_addr)
196+
.and_then(|id| system.rt.get_actor_code_cid(&id))
197+
{
198+
Some(cid) => cid,
199+
// failure to find CID is flattened to an empty return
200+
None => break 'early_ret (1, vec![]),
201+
};
202+
203+
let code = match system.rt.resolve_builtin_actor_type(&cid) {
204+
Some(Type::EVM) => system.rt
205+
.send(&dst_addr, crate::Method::GetBytecode as u64, Default::default(), TokenAmount::zero())?
206+
.deserialize()?
207+
,
208+
// other builtin actors & native actors
209+
_ => todo!("revert when calling delegate call for native actors")
210+
};
211+
212+
// and then invoke self with delegate; readonly context is sticky
213+
let params = DelegateCallParams {
214+
code,
215+
input: input_data.to_vec(),
216+
readonly: system.readonly,
179217
};
180218
system.send(
181-
&dst_addr,
182-
method,
183-
// TODO: support IPLD codecs #758
184-
RawBytes::serialize(BytesSer(input_data))?,
219+
&system.rt.message().receiver(),
220+
Method::InvokeContractDelegate as u64,
221+
RawBytes::serialize(&params)?,
185222
TokenAmount::from(&value),
186223
)
187224
}
188-
}
189-
CallKind::DelegateCall => {
190-
// first invoke GetBytecode to get the code CID from the target
191-
let code = crate::interpreter::instructions::ext::get_evm_bytecode_cid(
192-
system.rt, dst,
193-
)?
194-
.unwrap_evm(StatusCode::InvalidArgument(format!(
195-
"DELEGATECALL cannot call native actor {}",
196-
dst
197-
)))?;
198-
199-
// and then invoke self with delegate; readonly context is sticky
200-
let params = DelegateCallParams {
201-
code,
202-
input: input_data.to_vec(),
203-
readonly: system.readonly,
204-
};
205-
system.send(
206-
&system.rt.message().receiver(),
207-
Method::InvokeContractDelegate as u64,
208-
RawBytes::serialize(&params)?,
209-
TokenAmount::from(&value),
210-
)
211-
}
212225

213-
CallKind::CallCode => Err(ActorError::unchecked(
214-
EVM_CONTRACT_EXECUTION_ERROR,
215-
"unsupported opcode".to_string(),
216-
)),
217-
};
218-
match call_result {
219-
Ok(result) => {
220-
// Support the "empty" result. We often use this to mean "returned nothing" and
221-
// it's important to support, e.g., sending to accounts.
222-
if result.is_empty() {
223-
(1, Vec::new())
224-
} else {
225-
// TODO: support IPLD codecs #758
226-
let BytesDe(result) = result.deserialize()?;
227-
(1, result)
226+
CallKind::CallCode => Err(ActorError::unchecked(
227+
EVM_CONTRACT_EXECUTION_ERROR,
228+
"unsupported opcode".to_string(),
229+
)),
230+
};
231+
match call_result {
232+
Ok(result) => {
233+
// Support the "empty" result. We often use this to mean "returned nothing" and
234+
// it's important to support, e.g., sending to accounts.
235+
if result.is_empty() {
236+
(1, Vec::new())
237+
} else {
238+
// TODO: support IPLD codecs #758
239+
let BytesDe(result) = result.deserialize()?;
240+
(1, result)
241+
}
228242
}
243+
Err(ae) => (0, ae.data().to_vec()),
229244
}
230-
Err(ae) => (0, ae.data().to_vec()),
231245
}
232246
}
233247
};

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

+52-64
Original file line numberDiff line numberDiff line change
@@ -21,38 +21,23 @@ pub fn extcodesize(
2121
// TODO (M2.2) we're fetching the entire block here just to get its size. We should instead use
2222
// the ipld::block_stat syscall, but the Runtime nor the Blockstore expose it.
2323
// Tracked in https://github.com/filecoin-project/ref-fvm/issues/867
24-
let len = match get_evm_bytecode_cid(system.rt, addr) {
25-
Ok(CodeCid::EVM(cid)) =>
26-
// TODO this is part of account abstraction hack where EOAs are Embryos
27-
{
28-
if cid == system.rt.get_code_cid_for_type(Type::Embryo) {
29-
Ok(0)
30-
} else {
31-
get_evm_bytecode(system.rt, &cid).map(|bytecode| bytecode.len())
32-
}
33-
}
34-
Ok(CodeCid::Native(cid)) => {
35-
if cid == system.rt.get_code_cid_for_type(Type::Account) {
36-
// system account has no code (and we want solidity isContract to return false)
37-
Ok(0)
38-
} else {
39-
// native actor code
40-
// TODO bikeshed this, needs to be at least non-zero though for solidity isContract.
41-
// https://github.com/filecoin-project/ref-fvm/issues/1134
42-
Ok(1)
43-
}
44-
}
45-
// non-existent address
46-
Err(StatusCode::InvalidArgument(msg)) => {
47-
// REMOVEME: kinda sketchy
48-
if msg == "EVM EXT opcode failed to resolve address" {
49-
Ok(0)
50-
} else {
51-
Err(StatusCode::InvalidArgument(msg))
52-
}
53-
}
54-
Err(e) => Err(e),
55-
}?;
24+
let cid = get_cid_type(system.rt, addr)?;
25+
26+
let len = match cid {
27+
CodeCid::EVM(evm) => system
28+
.rt
29+
.send(
30+
&evm.0,
31+
crate::Method::GetBytecode as u64,
32+
Default::default(),
33+
TokenAmount::zero(),
34+
)?
35+
.deserialize()
36+
.map(|cid| get_evm_bytecode(system.rt, &cid).map(|bytecode| bytecode.len()))??,
37+
CodeCid::Native(_) => 1,
38+
// account and not found are flattened to 0 size
39+
_ => 0,
40+
};
5641

5742
state.stack.push(len.into());
5843
Ok(())
@@ -63,9 +48,11 @@ pub fn extcodehash(
6348
system: &System<impl Runtime>,
6449
) -> Result<(), StatusCode> {
6550
let addr = state.stack.pop();
66-
let cid = get_evm_bytecode_cid(system.rt, addr)?.unwrap_evm(StatusCode::InvalidArgument(
67-
"Cannot invoke EXTCODEHASH for non-EVM actor.".to_string(),
68-
))?;
51+
let cid = get_cid_type(system.rt, addr)?
52+
.unwrap_evm(StatusCode::InvalidArgument(
53+
"Cannot invoke EXTCODEHASH for non-EVM actor.".to_string(),
54+
))?
55+
.1;
6956

7057
let digest = cid.hash().digest();
7158

@@ -83,9 +70,9 @@ pub fn extcodecopy(
8370
let (addr, dest_offset, data_offset, size) =
8471
(stack.pop(), stack.pop(), stack.pop(), stack.pop());
8572

86-
let bytecode = get_evm_bytecode_cid(system.rt, addr).map(|cid| {
73+
let bytecode = get_cid_type(system.rt, addr).map(|cid| {
8774
cid.unwrap_evm(())
88-
.map(|evm_cid| get_evm_bytecode(system.rt, &evm_cid))
75+
.map(|evm_cid| get_evm_bytecode(system.rt, &evm_cid.1))
8976
// calling EXTCODECOPY on native actors results with a single byte 0xFE which solidtiy uses for its `assert`/`throw` methods
9077
// and in general invalid EVM bytecode
9178
.unwrap_or_else(|_| Ok(vec![0xFE]))
@@ -96,12 +83,15 @@ pub fn extcodecopy(
9683

9784
#[derive(Debug)]
9885
pub enum CodeCid {
99-
EVM(Cid),
86+
/// EVM Address and the CID of the actor (not the bytecode)
87+
EVM((Address, Cid)),
10088
Native(Cid),
89+
Account,
90+
NotFound,
10191
}
10292

10393
impl CodeCid {
104-
pub fn unwrap_evm<E>(&self, err: E) -> Result<Cid, E> {
94+
pub fn unwrap_evm<E>(&self, err: E) -> Result<(Address, Cid), E> {
10595
if let CodeCid::EVM(cid) = self {
10696
Ok(*cid)
10797
} else {
@@ -110,34 +100,32 @@ impl CodeCid {
110100
}
111101
}
112102

113-
/// Attempts to get bytecode CID of an evm contract, returning either an error or the CID of the native actor as the error
114-
pub fn get_evm_bytecode_cid(rt: &impl Runtime, addr: U256) -> Result<CodeCid, StatusCode> {
103+
/// Resolves an address to the address type
104+
pub fn get_cid_type(rt: &impl Runtime, addr: U256) -> Result<CodeCid, StatusCode> {
115105
let addr: EthAddress = addr.into();
116106
let addr: Address = addr.try_into()?;
117-
let actor_id = rt.resolve_address(&addr).ok_or_else(|| {
118-
StatusCode::InvalidArgument("EVM EXT opcode failed to resolve address".to_string())
119-
// TODO better error code
120-
})?;
121-
122-
let evm_cid = rt.get_code_cid_for_type(Type::EVM);
123-
let embryo_cid = rt.get_code_cid_for_type(Type::Embryo);
124-
let target_cid = rt.get_actor_code_cid(&actor_id);
125-
126-
if let Some(cid) = target_cid {
127-
// TODO part of embryo hack
128-
if cid == embryo_cid {
129-
return Ok(CodeCid::EVM(cid));
130-
} else if cid != evm_cid {
131-
return Ok(CodeCid::Native(cid));
132-
}
133-
}
134-
// REMOVEME if we dont find a CID for these things it fails silently till send...
135107

136-
let cid = CodeCid::EVM(
137-
rt.send(&addr, crate::Method::GetBytecode as u64, Default::default(), TokenAmount::zero())?
138-
.deserialize()?,
139-
);
140-
Ok(cid)
108+
rt.resolve_address(&addr)
109+
.and_then(|id| {
110+
rt.get_actor_code_cid(&id).map(|cid| {
111+
let code_cid = rt
112+
.resolve_builtin_actor_type(&cid)
113+
.map(|t| {
114+
match t {
115+
Type::Account => CodeCid::Account,
116+
// TODO part of current account abstraction hack where emryos are accounts
117+
Type::Embryo => CodeCid::Account,
118+
Type::EVM => CodeCid::EVM((addr, cid)),
119+
// remaining builtin actors are native
120+
_ => CodeCid::Native(cid),
121+
}
122+
// not a builtin actor, so it is probably a native actor
123+
})
124+
.unwrap_or(CodeCid::Native(cid));
125+
Ok(code_cid)
126+
})
127+
})
128+
.unwrap_or(Ok(CodeCid::NotFound))
141129
}
142130

143131
pub fn get_evm_bytecode(rt: &impl Runtime, cid: &Cid) -> Result<Vec<u8>, StatusCode> {

0 commit comments

Comments
 (0)