Skip to content

Commit

Permalink
Merge pull request ethereum#243 from g-r-a-n-t/runtime-testing
Browse files Browse the repository at this point in the history
Runtime testing
  • Loading branch information
g-r-a-n-t authored Feb 12, 2021
2 parents c39d75b + da1098c commit 9c49805
Show file tree
Hide file tree
Showing 8 changed files with 521 additions and 277 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions compiler/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::types::{
YulIr,
};

/// Compiles Fe to bytecode. It uses Yul as an intermediate representation.
/// Compile a map of Yul contracts to a map of bytecode contracts.
pub fn compile(
mut contracts: NamedYulContracts,
optimize: bool,
Expand All @@ -21,7 +21,8 @@ pub fn compile(
.collect::<Result<NamedBytecodeContracts, _>>()
}

fn compile_single_contract(
/// Compiles a single Yul contract to bytecode.
pub fn compile_single_contract(
name: &str,
yul_src: YulIr,
optimize: bool,
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/yul/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod constructor;
mod mappers;
mod names;
mod operations;
mod runtime;
pub mod runtime;
mod utils;

/// Compiles Fe source code to Yul.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/yul/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod abi_dispatcher;
mod functions;
pub mod functions;

use fe_analyzer::namespace::types::{
AbiDecodeLocation,
Expand Down
275 changes: 3 additions & 272 deletions compiler/tests/evm_contracts.rs
Original file line number Diff line number Diff line change
@@ -1,284 +1,15 @@
#![cfg(feature = "solc-backend")]
use evm_runtime::{
ExitReason,
Handler,
};
use fe_compiler as compiler;
use evm_runtime::Handler;
use primitive_types::{
H160,
U256,
};
use rstest::rstest;
use std::collections::BTreeMap;
use std::fs;
use std::iter;
use std::str::FromStr;
use stringreader::StringReader;

type Executor<'a> = evm::executor::StackExecutor<'a, 'a, evm::backend::MemoryBackend<'a>>;

const DEFAULT_CALLER: &str = "1000000000000000000000000000000000000001";

struct ContractHarness {
address: H160,
abi: ethabi::Contract,
caller: H160,
value: U256,
}

impl ContractHarness {
fn new(contract_address: H160, abi: ethabi::Contract) -> Self {
let caller = address(DEFAULT_CALLER);

ContractHarness {
address: contract_address,
abi,
caller,
value: U256::zero(),
}
}

pub fn capture_call(
&self,
executor: &mut Executor,
name: &str,
input: &[ethabi::Token],
) -> evm::Capture<(evm::ExitReason, Vec<u8>), std::convert::Infallible> {
let function = &self.abi.functions[name][0];

let context = evm::Context {
address: self.address,
caller: self.caller,
apparent_value: self.value,
};

let input = function
.encode_input(input)
.expect("Unable to encode input");

executor.call(self.address, None, input, None, false, context)
}

pub fn test_function(
&self,
executor: &mut Executor,
name: &str,
input: &[ethabi::Token],
output: Option<&ethabi::Token>,
) {
let actual_output = self.call_function(executor, name, input);
assert_eq!(output.map(|token| token.to_owned()), actual_output)
}

pub fn call_function(
&self,
executor: &mut Executor,
name: &str,
input: &[ethabi::Token],
) -> Option<ethabi::Token> {
let function = &self.abi.functions[name][0];

match self.capture_call(executor, name, &input) {
evm::Capture::Exit((ExitReason::Succeed(_), output)) => {
let output = function
.decode_output(&output)
.expect(&format!("unable to decode output: {:?}", &output))
.pop();
return output;
}
evm::Capture::Exit((reason, _)) => panic!("failed to run \"{}\": {:?}", name, reason),
_ => panic!("trap"),
}
}

pub fn test_function_reverts(
&self,
executor: &mut Executor,
name: &str,
input: &[ethabi::Token],
) {
match self.capture_call(executor, name, input) {
evm::Capture::Exit((ExitReason::Revert(_), _)) => {}
_ => panic!("function did not revert"),
}
}

// Executor must be passed by value to get emitted events.
pub fn events_emitted(&self, executor: Executor, events: &[(&str, &[ethabi::Token])]) {
let raw_logs = executor
.deconstruct()
.1
.into_iter()
.map(|log| ethabi::RawLog::from((log.topics, log.data)))
.collect::<Vec<ethabi::RawLog>>();

for (name, expected_output) in events {
let event = self
.abi
.events()
.find(|event| event.name.eq(name))
.expect("unable to find event for name");

let outputs_for_event = raw_logs
.iter()
.filter_map(|raw_log| event.parse_log(raw_log.to_owned()).ok())
.map(|event_log| {
event_log
.params
.into_iter()
.map(|param| param.value)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();

if !outputs_for_event.iter().any(|v| &v == expected_output) {
panic!(
"no {} logs matching: {:?}\nfound: {:?}",
name, expected_output, outputs_for_event
)
}
}
}

pub fn set_caller(&mut self, caller: H160) {
self.caller = caller;
}
}

fn with_executor(test: &dyn Fn(Executor)) {
let vicinity = evm::backend::MemoryVicinity {
gas_price: U256::zero(),
origin: H160::zero(),
chain_id: U256::zero(),
block_hashes: Vec::new(),
block_number: U256::zero(),
block_coinbase: H160::zero(),
block_timestamp: U256::zero(),
block_difficulty: U256::zero(),
block_gas_limit: primitive_types::U256::MAX,
};
let state: BTreeMap<primitive_types::H160, evm::backend::MemoryAccount> = BTreeMap::new();
let backend = evm::backend::MemoryBackend::new(&vicinity, state);

with_executor_backend(backend, test)
}

fn with_executor_backend(backend: evm::backend::MemoryBackend, test: &dyn Fn(Executor)) {
let config = evm::Config::istanbul();
let executor = evm::executor::StackExecutor::new(&backend, usize::max_value(), &config);

test(executor)
}

fn deploy_contract(
executor: &mut Executor,
fixture: &str,
contract_name: &str,
init_params: &[ethabi::Token],
) -> ContractHarness {
let src = fs::read_to_string(format!("tests/fixtures/{}", fixture))
.expect("unable to read fixture file");
let compiled_module = compiler::compile(&src, true, false).expect("failed to compile module");
let compiled_contract = compiled_module
.contracts
.get(contract_name)
.expect("could not find contract in fixture");
let abi = ethabi::Contract::load(StringReader::new(&compiled_contract.json_abi))
.expect("unable to load the ABI");
let mut bytecode = hex::decode(&compiled_contract.bytecode).expect("failed to decode bytecode");

if let Some(constructor) = &abi.constructor {
bytecode = constructor.encode_input(bytecode, init_params).unwrap()
}

if let evm::Capture::Exit(exit) = executor.create(
address(DEFAULT_CALLER),
evm_runtime::CreateScheme::Legacy {
caller: address(DEFAULT_CALLER),
},
U256::zero(),
bytecode,
None,
) {
return ContractHarness::new(exit.1.expect("Unable to retrieve contract address"), abi);
}

panic!("Failed to create contract")
}

fn load_contract(address: H160, fixture: &str, contract_name: &str) -> ContractHarness {
let src = fs::read_to_string(format!("tests/fixtures/{}", fixture))
.expect("unable to read fixture file");
let compiled_module = compiler::compile(&src, true, false).expect("failed to compile module");
let compiled_contract = compiled_module
.contracts
.get(contract_name)
.expect("could not find contract in fixture");
let abi = ethabi::Contract::load(StringReader::new(&compiled_contract.json_abi))
.expect("unable to load the ABI");

ContractHarness::new(address, abi)
}

fn uint_token(n: usize) -> ethabi::Token {
ethabi::Token::Uint(U256::from(n))
}

fn int_token(val: isize) -> ethabi::Token {
ethabi::Token::Int(to_2s_complement(val))
}

fn string_token(s: &str) -> ethabi::Token {
ethabi::Token::String(s.to_string())
}

fn address(s: &str) -> H160 {
H160::from_str(s).expect(&format!("couldn't create address from: {}", s))
}

fn address_token(s: &str) -> ethabi::Token {
// left pads to 40 characters
ethabi::Token::Address(address(&format!("{:0>40}", s)))
}

fn bool_token(val: bool) -> ethabi::Token {
ethabi::Token::Bool(val)
}

fn bytes_token(s: &str) -> ethabi::Token {
ethabi::Token::FixedBytes(ethabi::FixedBytes::from(s))
}

fn u256_array_token(v: &[usize]) -> ethabi::Token {
ethabi::Token::FixedArray(v.iter().map(|n| uint_token(*n)).collect())
}

fn address_array_token(v: &[&str]) -> ethabi::Token {
ethabi::Token::FixedArray(v.iter().map(|s| address_token(s)).collect())
}

fn to_2s_complement(val: isize) -> U256 {
// Since this API takes an `isize` we can be sure that the min and max values
// will never be above what fits the `I256` type which has the same capacity
// as U256 but splits it so that one half covers numbers above 0 and the
// other half covers the numbers below 0.

// Conversion to Two's Complement: https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html

if val >= 0 {
U256::from(val)
} else {
let positive_val = val * -1;
get_2s_complement_for_negative(U256::from(positive_val))
}
}

/// To get the 2s complement value for e.g. -128 call
/// get_2s_complement_for_negative(128)
fn get_2s_complement_for_negative(assume_negative: U256) -> U256 {
let (negated, _) = assume_negative.overflowing_neg();
negated + 1
}
mod utils;
use utils::*;

#[test]
fn test_to_2s_complement() {
Expand Down
Loading

0 comments on commit 9c49805

Please sign in to comment.