Skip to content

EVM: Implement reverts correctly #864

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

Merged
merged 27 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
618669f
add exit to runtime interface
vyzo Nov 22, 2022
725e8b1
propagate revert data in contract invocations
vyzo Nov 22, 2022
6897e50
add data to ActorError
vyzo Nov 22, 2022
0571628
propagate data in actor errors from send
vyzo Nov 22, 2022
8f5740f
correctly handle reverts in calls
vyzo Nov 22, 2022
72b3a70
implement exit with panics in mock runtime
vyzo Nov 22, 2022
d400e46
implement exit with panics in test vm
vyzo Nov 22, 2022
62d93a1
fix clippy
vyzo Nov 22, 2022
120111a
fix test vm: exit data should be in new context
vyzo Nov 22, 2022
2db6318
use pop instead of ugly gets in ret
vyzo Nov 22, 2022
fe7ecb8
cosmetics
vyzo Nov 22, 2022
4da310a
add naked revert test
vyzo Nov 22, 2022
2b1793f
rustfmt
vyzo Nov 22, 2022
a19b15e
only test naked revert in unit test
vyzo Nov 22, 2022
c323b54
fix callvariants contract to check reverts
vyzo Nov 22, 2022
e286fa1
really fix callvariants contract: iszero is what you want
vyzo Nov 22, 2022
0915423
test_vm: missing panic handler
vyzo Nov 22, 2022
6ad8a76
callvariants is also testing mutation
vyzo Nov 22, 2022
64f9799
deduplicate actor exit handler code
vyzo Nov 22, 2022
674b0c9
rustfmt
vyzo Nov 22, 2022
75bbed1
remove unnecessary replaces for exits.
vyzo Nov 23, 2022
385e932
fix runtime trampoline to propagate data from errors
vyzo Nov 23, 2022
47d6418
perform contract/constructor invocation abortive returns with errors
vyzo Nov 23, 2022
cf2e4f8
don't synethesize exit data if it is not there
vyzo Nov 23, 2022
583e8fa
propagate error data in test_vm
vyzo Nov 23, 2022
d59827c
rustfmt
vyzo Nov 23, 2022
3f91e75
simplify/dedup identical code
vyzo Nov 23, 2022
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
46 changes: 23 additions & 23 deletions actors/evm/src/interpreter/instructions/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ use {
crate::interpreter::System,
crate::interpreter::U256,
crate::RawBytes,
crate::{DelegateCallParams, Method, EVM_CONTRACT_REVERTED},
crate::{DelegateCallParams, Method, EVM_CONTRACT_EXECUTION_ERROR, EVM_CONTRACT_REVERTED},
fil_actors_runtime::runtime::builtins::Type,
fil_actors_runtime::runtime::Runtime,
fil_actors_runtime::ActorError,
fvm_shared::econ::TokenAmount,
};

Expand Down Expand Up @@ -113,7 +114,7 @@ pub fn call<RT: Runtime>(
let input_region = get_memory_region(memory, input_offset, input_size)
.map_err(|_| StatusCode::InvalidMemoryAccess)?;

state.return_data = {
let (call_result, return_data) = {
// ref to memory is dropped after calling so we can mutate it on output later
let input_data = if let Some(MemoryRegion { offset, size }) = input_region {
&memory[offset..][..size.get()]
Expand All @@ -128,9 +129,15 @@ pub fn call<RT: Runtime>(
value,
};

// TODO: DO NOT FAIL!!!
precompiles::Precompiles::call_precompile(system.rt, dst, input_data, context)
.map_err(StatusCode::from)?
match precompiles::Precompiles::call_precompile(system.rt, dst, input_data, context)
.map_err(StatusCode::from)
{
Ok(return_data) => (1, return_data),
Err(status) => {
let msg = format!("{}", status);
(0, msg.as_bytes().to_vec())
}
}
} else {
let call_result = match kind {
CallKind::Call | CallKind::StaticCall => {
Expand Down Expand Up @@ -199,41 +206,34 @@ pub fn call<RT: Runtime>(
)
}

CallKind::CallCode => {
todo!()
}
CallKind::CallCode => Err(ActorError::unchecked(
EVM_CONTRACT_EXECUTION_ERROR,
"unsupported opcode".to_string(),
)),
};
match call_result {
Ok(result) => {
// Support the "empty" result. We often use this to mean "returned nothing" and
// it's important to support, e.g., sending to accounts.
if result.is_empty() {
Vec::new()
(1, Vec::new())
} else {
// TODO: support IPLD codecs #758
let BytesDe(result) = result.deserialize()?;
result
(1, result)
}
}
Err(ae) => {
return if ae.exit_code() == EVM_CONTRACT_REVERTED {
// reverted -- we don't have return data yet
// push failure
stack.push(U256::zero());
Ok(())
} else {
Err(StatusCode::from(ae))
};
}
Err(ae) => (0, ae.data().to_vec()),
}
}
}
.into();
};

state.return_data = return_data.into();

// copy return data to output region if it is non-zero
copy_to_memory(memory, output_offset, output_size, U256::zero(), &state.return_data, false)?;

stack.push(U256::from(1));
stack.push(U256::from(call_result));
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions actors/evm/src/interpreter/instructions/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use {

#[inline]
pub fn ret(state: &mut ExecutionState) -> Result<(), StatusCode> {
let offset = *state.stack.get(0);
let size = *state.stack.get(1);
let offset = state.stack.pop();
let size = state.stack.pop();

if let Some(region) = super::memory::get_memory_region(&mut state.memory, offset, size)
.map_err(|_| StatusCode::InvalidMemoryAccess)?
Expand Down
26 changes: 17 additions & 9 deletions actors/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ use {
#[cfg(feature = "fil-actor")]
fil_actors_runtime::wasm_trampoline!(EvmContractActor);

pub const EVM_CONTRACT_REVERTED: ExitCode = ExitCode::new(27);
pub const EVM_CONTRACT_REVERTED: ExitCode = ExitCode::new(33);
pub const EVM_CONTRACT_EXECUTION_ERROR: ExitCode = ExitCode::new(34);

const EVM_MAX_RESERVED_METHOD: u64 = 1023;
pub const NATIVE_METHOD_SIGNATURE: &str = "handle_filecoin_method(uint64,uint64,bytes)";
Expand Down Expand Up @@ -118,14 +119,21 @@ impl EvmContractActor {

// TODO this does not return revert data yet, but it has correct semantics.
if exec_status.reverted {
Err(ActorError::unchecked(EVM_CONTRACT_REVERTED, "constructor reverted".to_string()))
Err(ActorError::unchecked_with_data(
EVM_CONTRACT_REVERTED,
"constructor reverted".to_string(),
RawBytes::from(exec_status.output_data.to_vec()),
))
} else if exec_status.status_code == StatusCode::Success {
system.set_bytecode(&exec_status.output_data)?;
system.flush()
} else if let StatusCode::ActorError(e) = exec_status.status_code {
Err(e)
} else {
Err(ActorError::unspecified("EVM constructor failed".to_string()))
Err(ActorError::unchecked(
EVM_CONTRACT_EXECUTION_ERROR,
format!("constructor execution error: {}", exec_status.status_code),
))
}
}

Expand Down Expand Up @@ -175,21 +183,21 @@ impl EvmContractActor {
_ => ActorError::unspecified(format!("EVM execution error: {e:?}")),
})?;

// TODO this does not return revert data yet, but it has correct semantics.
if exec_status.reverted {
return Err(ActorError::unchecked(
return Err(ActorError::unchecked_with_data(
EVM_CONTRACT_REVERTED,
"contract reverted".to_string(),
RawBytes::from(exec_status.output_data.to_vec()),
));
} else if exec_status.status_code == StatusCode::Success {
system.flush()?;
} else if let StatusCode::ActorError(e) = exec_status.status_code {
return Err(e);
} else {
return Err(ActorError::unspecified(format!(
"EVM contract invocation failed: status: {}",
exec_status.status_code
)));
return Err(ActorError::unchecked(
EVM_CONTRACT_EXECUTION_ERROR,
format!("contract execution error: {}", exec_status.status_code),
));
}

if let Some(addr) = exec_status.selfdestroyed {
Expand Down
2 changes: 1 addition & 1 deletion actors/evm/tests/contracts/callvariants.hex
Original file line number Diff line number Diff line change
@@ -1 +1 @@
306000556101cf8060106000396000f360003560e01c80600114607557806002146101af5780600314609157806004146101bb578060051460ad578060061460cf578060071460ed578060081461010f578060091461012d5780600a146101495780600b1461016b5780600c1461012d5780600d1461018d5780600e1461014957600080fd5b60206000600260e01b600052600460006004356000fa60206000f35b60206000600460e01b600052600460006004356000fa60206000f35b60206000600660e01b600052602435600452602460006004356000fa60206000f35b60206000600260e01b6000526004600060006004356000f160206000f35b60206000600860e01b600052602435600452602460006004356000fa60206000f35b60206000600460e01b6000526004600060006004356000f160206000f35b60206000600260e01b600052600460006004356000f460206000f35b60206000600460e01b600052600460006004356000f460005460005260206000f35b60206000600c60e01b600052602435600452602460006004356000fa60206000f35b60206000600e60e01b600052602435600452602460006004356000fa60206000f35b60005460005260206000f35b63ffffff4260005560005460005260206000f3
306000556102108060106000396000f360003560e01c80600114607657806002146101e25780600314609757806004146101ee578060051460b8578060061460df57806007146101025780600814610129578060091461014c5780600a1461016d5780600b146101945780600c1461014c5780600d146101bb5780600e1461016d57600080fd5b60206000600260e01b600052600460006004356000fa156102025760206000f35b60206000600460e01b600052600460006004356000fa156102025760206000f35b60206000600660e01b600052602435600452602460006004356000fa156102025760206000f35b60206000600260e01b6000526004600060006004356000f1156102025760206000f35b60206000600860e01b600052602435600452602460006004356000fa156102025760206000f35b60206000600460e01b6000526004600060006004356000f1156102025760206000f35b60206000600260e01b600052600460006004356000f4156102025760206000f35b60206000600460e01b600052600460006004356000f4156102025760005460005260206000f35b60206000600c60e01b600052602435600452602460006004356000fa156102025760206000f35b60206000600e60e01b600052602435600452602460006004356000fa156102025760206000f35b60005460005260206000f35b63ffffff4260005560005460005260206000f35b63deadbeef6000526004601cfd
26 changes: 26 additions & 0 deletions actors/evm/tests/contracts/callvariants_body.eas
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
revert
%end

%macro check()
iszero
%push(call_failed)
jumpi
%end

# method dispatch
%dispatch_begin()

Expand Down Expand Up @@ -77,6 +83,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -97,6 +104,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -121,6 +129,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -142,6 +151,7 @@ push1 0x04 # input (dest) offset
calldataload # call dest
push1 0x00 # call gas (ignored)
call # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -166,6 +176,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -187,6 +198,7 @@ push1 0x04 # input (dest) offset
calldataload # icall dest
push1 0x00 # call gas (ignored)
call # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -207,6 +219,7 @@ push1 0x04 # input (dest) offset
calldataload # delegate dest
push1 0x00 # delegate gas (ignored)
delegatecall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -227,6 +240,7 @@ push1 0x04 # input (dest) offset
calldataload # delegate dest
push1 0x00 # delegate gas (ignored)
delegatecall # do it!
%check()
push1 0x00 # slot
sload # read
push1 0x00 # ret offset
Expand Down Expand Up @@ -255,6 +269,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand All @@ -279,6 +294,7 @@ push1 0x04 # input (dest) offset
calldataload # staticcall dest
push1 0x00 # staticcall gas (ignored)
staticcall # do it!
%check()
push1 0x20 # ret length
push1 0x00 # ret offset
return
Expand Down Expand Up @@ -310,3 +326,13 @@ mstore
push1 0x20
push1 0x00
return

# call_failed
call_failed:
jumpdest
%push(0xdeadbeef)
push1 0x00
mstore
push1 0x04
push1 0x1c
revert
34 changes: 34 additions & 0 deletions actors/evm/tests/revert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use fil_actor_evm as evm;
use fvm_ipld_encoding::{BytesSer, RawBytes};

mod asm;
mod util;

#[test]
fn test_revert() {
let contract = asm::new_contract(
"naked-revert",
"",
r#"
%push(0xdeadbeef)
push1 0x00
mstore
push1 0x04
push1 0x1c # skip top 28 bytes
revert
"#,
)
.unwrap();

let mut rt = util::construct_and_verify(contract);
rt.expect_validate_caller_any();

let result = rt.call::<evm::EvmContractActor>(
evm::Method::InvokeContract as u64,
&RawBytes::serialize(BytesSer(&[])).unwrap(),
);
assert!(result.is_err());
let e = result.unwrap_err();
assert_eq!(e.exit_code(), evm::EVM_CONTRACT_REVERTED);
assert_eq!(e.data(), RawBytes::from(vec![0xde, 0xad, 0xbe, 0xef]));
}
Loading