Merge pull request #97 from gavofyork/executive_tests

Executive tests
This commit is contained in:
Gav Wood 2016-01-13 23:10:37 +01:00
commit 6f238ae7cc
8 changed files with 405 additions and 36 deletions

View File

@ -127,6 +127,12 @@ impl ContextHandle {
unsafe { std::slice::from_raw_parts(self.data_handle.call_data, self.data_handle.call_data_size as usize) }
}
/// Returns address to which funds should be transfered after suicide.
pub fn suicide_refund_address(&self) -> JitI256 {
// evmjit reuses data_handle address field to store suicide address
self.data_handle.address
}
/// Returns gas left.
pub fn gas_left(&self) -> u64 {
self.data_handle.gas as u64

View File

@ -51,7 +51,8 @@ pub trait Ext {
fn ret(&mut self, gas: u64, data: &[u8]) -> Result<u64, Error>;
/// Should be called when contract commits suicide.
fn suicide(&mut self);
/// Address to which funds should be refunded.
fn suicide(&mut self, refund_address: &Address);
/// Returns schedule.
fn schedule(&self) -> &Schedule;

View File

@ -212,16 +212,17 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> {
match self.ext.create(*io_gas, &U256::from_jit(&*endowment), slice::from_raw_parts(init_beg, init_size as usize)) {
Ok((gas_left, opt)) => {
*io_gas = gas_left;
if let Some(addr) = opt {
*address = addr.into_jit();
}
*address = match opt {
Some(addr) => addr.into_jit(),
_ => Address::new().into_jit()
};
},
Err(err @ evm::Error::OutOfGas) => {
*self.err = Some(err);
// hack to propagate `OutOfGas` to evmjit and stop
// the execution immediately.
// Works, cause evmjit uses i64, not u64
*io_gas = -1i64 as u64
*io_gas = -1i64 as u64;
},
Err(err) => *self.err = Some(err)
}
@ -346,8 +347,7 @@ impl evm::Evm for JitEvm {
evmjit::ReturnCode::Stop => Ok(U256::from(context.gas_left())),
evmjit::ReturnCode::Return => ext.ret(context.gas_left(), context.output_data()).map(|gas_left| U256::from(gas_left)),
evmjit::ReturnCode::Suicide => {
// what if there is a suicide and we run out of gas just after?
ext.suicide();
ext.suicide(&Address::from_jit(&context.suicide_refund_address()));
Ok(U256::from(context.gas_left()))
},
evmjit::ReturnCode::OutOfGas => Err(evm::Error::OutOfGas),

View File

@ -72,7 +72,7 @@ impl Ext for FakeExt {
unimplemented!();
}
fn suicide(&mut self) {
fn suicide(&mut self, _refund_address: &Address) {
unimplemented!();
}

View File

@ -14,7 +14,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
/// State changes which should be applied in finalize,
/// after transaction is fully executed.
struct Substate {
pub struct Substate {
/// Any accounts that have suicided.
suicides: HashSet<Address>,
/// Any logs.
@ -27,7 +27,7 @@ struct Substate {
impl Substate {
/// Creates new substate.
fn new() -> Self {
pub fn new() -> Self {
Substate {
suicides: HashSet::new(),
logs: vec![],
@ -160,7 +160,9 @@ impl<'a> Executive<'a> {
code: self.state.code(address).unwrap_or(vec![]),
data: t.data.clone(),
};
self.call(&params, &mut substate, &mut [])
// TODO: move output upstream
let mut out = vec![];
self.call(&params, &mut substate, BytesRef::Flexible(&mut out))
}
};
@ -172,7 +174,7 @@ impl<'a> Executive<'a> {
/// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides).
/// Modifies the substate and the output.
/// Returns either gas_left or `evm::Error`.
fn call(&mut self, params: &ActionParams, substate: &mut Substate, output: &mut [u8]) -> evm::Result {
pub fn call(&mut self, params: &ActionParams, substate: &mut Substate, mut output: BytesRef) -> evm::Result {
// at first, transfer value to destination
self.state.transfer_balance(&params.sender, &params.address, &params.value);
@ -181,7 +183,7 @@ impl<'a> Executive<'a> {
let cost = self.engine.cost_of_builtin(&params.address, &params.data);
match cost <= params.gas {
true => {
self.engine.execute_builtin(&params.address, &params.data, output);
self.engine.execute_builtin(&params.address, &params.data, &mut output);
Ok(params.gas - cost)
},
false => Err(evm::Error::OutOfGas)
@ -267,20 +269,26 @@ impl<'a> Executive<'a> {
}
/// Policy for handling output data on `RETURN` opcode.
enum OutputPolicy<'a> {
pub enum OutputPolicy<'a> {
/// Return reference to fixed sized output.
/// Used for message calls.
Return(&'a mut [u8]),
Return(BytesRef<'a>),
/// Init new contract as soon as `RETURN` is called.
InitContract
}
/// Implementation of evm Externalities.
struct Externalities<'a> {
pub struct Externalities<'a> {
#[cfg(test)]
pub state: &'a mut State,
#[cfg(not(test))]
state: &'a mut State,
info: &'a EnvInfo,
engine: &'a Engine,
depth: usize,
#[cfg(test)]
pub params: &'a ActionParams,
#[cfg(not(test))]
params: &'a ActionParams,
substate: &'a mut Substate,
schedule: Schedule,
@ -289,7 +297,7 @@ struct Externalities<'a> {
impl<'a> Externalities<'a> {
/// Basic `Externalities` constructor.
fn new(state: &'a mut State,
pub fn new(state: &'a mut State,
info: &'a EnvInfo,
engine: &'a Engine,
depth: usize,
@ -332,12 +340,12 @@ impl<'a> Ext for Externalities<'a> {
}
fn blockhash(&self, number: &U256) -> H256 {
match *number < U256::from(self.info.number) {
false => H256::from(&U256::zero()),
match *number < U256::from(self.info.number) && number.low_u64() >= cmp::max(256, self.info.number) - 256 {
true => {
let index = U256::from(self.info.number) - *number - U256::one();
self.info.last_hashes[index.low_u32() as usize].clone()
}
let index = self.info.number - number.low_u64() - 1;
self.info.last_hashes[index as usize].clone()
},
false => H256::from(&U256::zero()),
}
}
@ -405,7 +413,7 @@ impl<'a> Ext for Externalities<'a> {
};
let mut ex = Executive::from_parent(self.state, self.info, self.engine, self.depth);
ex.call(&params, self.substate, output).map(|gas_left| {
ex.call(&params, self.substate, BytesRef::Fixed(output)).map(|gas_left| {
gas + gas_left.low_u64()
})
}
@ -415,13 +423,19 @@ impl<'a> Ext for Externalities<'a> {
}
fn ret(&mut self, gas: u64, data: &[u8]) -> Result<u64, evm::Error> {
println!("ret");
match &mut self.output {
&mut OutputPolicy::Return(ref mut slice) => unsafe {
&mut OutputPolicy::Return(BytesRef::Fixed(ref mut slice)) => unsafe {
let len = cmp::min(slice.len(), data.len());
ptr::copy(data.as_ptr(), slice.as_mut_ptr(), len);
Ok(gas)
},
&mut OutputPolicy::Return(BytesRef::Flexible(ref mut vec)) => unsafe {
vec.clear();
vec.reserve(data.len());
ptr::copy(data.as_ptr(), vec.as_mut_ptr(), data.len());
vec.set_len(data.len());
Ok(gas)
},
&mut OutputPolicy::InitContract => {
let return_cost = data.len() as u64 * self.schedule.create_data_gas as u64;
if return_cost > gas {
@ -449,8 +463,10 @@ impl<'a> Ext for Externalities<'a> {
self.substate.logs.push(LogEntry::new(address, topics, data));
}
fn suicide(&mut self) {
fn suicide(&mut self, refund_address: &Address) {
let address = self.params.address.clone();
let balance = self.balance(&address);
self.state.transfer_balance(&address, refund_address, &balance);
self.substate.suicides.insert(address);
}
@ -472,7 +488,6 @@ mod tests {
use engine::*;
use spec::*;
use evm::Schedule;
use super::Substate;
struct TestEngine {
spec: Spec,
@ -482,7 +497,7 @@ mod tests {
impl TestEngine {
fn new(stack_limit: usize) -> TestEngine {
TestEngine {
spec: ethereum::new_frontier(),
spec: ethereum::new_frontier_test(),
stack_limit: stack_limit
}
}
@ -585,7 +600,60 @@ mod tests {
ex.create(&params, &mut substate).unwrap()
};
assert_eq!(gas_left, U256::from(47_976));
assert_eq!(gas_left, U256::from(62_976));
// ended with max depth
assert_eq!(substate.contracts_created.len(), 0);
}
#[test]
fn test_create_contract_value_too_high() {
// code:
//
// 7c 601080600c6000396000f3006000355415600957005b60203560003555 - push 29 bytes?
// 60 00 - push 0
// 52
// 60 1d - push 29
// 60 03 - push 3
// 60 e6 - push 230
// f0 - create a contract trying to send 230.
// 60 00 - push 0
// 55 sstore
//
// other code:
//
// 60 10 - push 16
// 80 - duplicate first stack item
// 60 0c - push 12
// 60 00 - push 0
// 39 - copy current code to memory
// 60 00 - push 0
// f3 - return
let code = "7c601080600c6000396000f3006000355415600957005b60203560003555600052601d600360e6f0600055".from_hex().unwrap();
let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
let address = contract_address(&sender, &U256::zero());
// TODO: add tests for 'callcreate'
//let next_address = contract_address(&address, &U256::zero());
let mut params = ActionParams::new();
params.address = address.clone();
params.sender = sender.clone();
params.origin = sender.clone();
params.gas = U256::from(100_000);
params.code = code.clone();
params.value = U256::from(100);
let mut state = State::new_temp();
state.add_balance(&sender, &U256::from(100));
let info = EnvInfo::new();
let engine = TestEngine::new(0);
let mut substate = Substate::new();
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine);
ex.create(&params, &mut substate).unwrap()
};
assert_eq!(gas_left, U256::from(62_976));
assert_eq!(substate.contracts_created.len(), 0);
}
@ -691,7 +759,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine);
ex.call(&params, &mut substate, &mut []).unwrap()
ex.call(&params, &mut substate, BytesRef::Fixed(&mut [])).unwrap()
};
assert_eq!(gas_left, U256::from(73_237));
@ -733,7 +801,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine);
ex.call(&params, &mut substate, &mut []).unwrap()
ex.call(&params, &mut substate, BytesRef::Fixed(&mut [])).unwrap()
};
assert_eq!(gas_left, U256::from(59_870));
@ -864,8 +932,8 @@ mod tests {
match res {
Err(Error::Execution(ExecutionError::NotEnoughCash { required , is }))
if required == U512::zero() && is == U512::one() => (),
_ => assert!(false, "Expected not enough cash error.")
if required == U512::from(100_018) && is == U512::from(100_017) => (),
_ => assert!(false, "Expected not enough cash error. {:?}", res)
}
}
}

293
src/tests/executive.rs Normal file
View File

@ -0,0 +1,293 @@
use super::test_common::*;
use state::*;
use executive::*;
use spec::*;
use engine::*;
use evm;
use evm::{Schedule, Ext, Factory};
use ethereum;
struct TestEngine {
spec: Spec,
stack_limit: usize
}
impl TestEngine {
fn new(stack_limit: usize) -> TestEngine {
TestEngine {
spec: ethereum::new_frontier_test(),
stack_limit: stack_limit
}
}
}
impl Engine for TestEngine {
fn name(&self) -> &str { "TestEngine" }
fn spec(&self) -> &Spec { &self.spec }
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
let mut schedule = Schedule::new_frontier();
schedule.stack_limit = self.stack_limit;
schedule
}
}
struct CallCreate {
data: Bytes,
destination: Address,
_gas_limit: U256,
value: U256
}
/// Tiny wrapper around executive externalities.
/// Stores callcreates.
struct TestExt<'a> {
ext: Externalities<'a>,
callcreates: Vec<CallCreate>
}
impl<'a> TestExt<'a> {
fn new(ext: Externalities<'a>) -> TestExt {
TestExt {
ext: ext,
callcreates: vec![]
}
}
}
impl<'a> Ext for TestExt<'a> {
fn sload(&self, key: &H256) -> H256 {
self.ext.sload(key)
}
fn sstore(&mut self, key: H256, value: H256) {
self.ext.sstore(key, value)
}
fn balance(&self, address: &Address) -> U256 {
self.ext.balance(address)
}
fn blockhash(&self, number: &U256) -> H256 {
self.ext.blockhash(number)
}
fn create(&mut self, gas: u64, value: &U256, code: &[u8]) -> Result<(u64, Option<Address>), evm::Error> {
// in call and create we need to check if we exited with insufficient balance or max limit reached.
// in case of reaching max depth, we should store callcreates. Otherwise, ignore.
let res = self.ext.create(gas, value, code);
let ext = &self.ext;
match res {
// just record call create
Ok((gas_left, Some(address))) => {
self.callcreates.push(CallCreate {
data: code.to_vec(),
destination: address.clone(),
_gas_limit: U256::from(gas),
value: *value
});
Ok((gas_left, Some(address)))
},
// creation failed only due to reaching stack_limit
Ok((gas_left, None)) if ext.state.balance(&ext.params.address) >= *value => {
let address = contract_address(&ext.params.address, &ext.state.nonce(&ext.params.address));
self.callcreates.push(CallCreate {
data: code.to_vec(),
// TODO: address is not stored here?
destination: Address::new(),
_gas_limit: U256::from(gas),
value: *value
});
Ok((gas_left, Some(address)))
},
other => other
}
}
fn call(&mut self,
gas: u64,
call_gas: u64,
receive_address: &Address,
value: &U256,
data: &[u8],
code_address: &Address,
output: &mut [u8]) -> Result<u64, evm::Error> {
let res = self.ext.call(gas, call_gas, receive_address, value, data, code_address, output);
let ext = &self.ext;
match res {
Ok(gas_left) if ext.state.balance(&ext.params.address) >= *value => {
self.callcreates.push(CallCreate {
data: data.to_vec(),
destination: receive_address.clone(),
_gas_limit: U256::from(call_gas),
value: *value
});
Ok(gas_left)
},
other => other
}
}
fn extcode(&self, address: &Address) -> Vec<u8> {
self.ext.extcode(address)
}
fn log(&mut self, topics: Vec<H256>, data: Bytes) {
self.ext.log(topics, data)
}
fn ret(&mut self, gas: u64, data: &[u8]) -> Result<u64, evm::Error> {
self.ext.ret(gas, data)
}
fn suicide(&mut self, refund_address: &Address) {
self.ext.suicide(refund_address)
}
fn schedule(&self) -> &Schedule {
self.ext.schedule()
}
fn env_info(&self) -> &EnvInfo {
self.ext.env_info()
}
}
fn do_json_test(json_data: &[u8]) -> Vec<String> {
let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid");
let mut failed = Vec::new();
for (name, test) in json.as_object().unwrap() {
// sync io is usefull when something crashes in jit
//::std::io::stdout().write(&name.as_bytes());
//::std::io::stdout().write(b"\n");
//::std::io::stdout().flush();
let mut fail = false;
//let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.to_string()); fail = true };
let mut fail_unless = |cond: bool, s: &str | if !cond && !fail { failed.push(name.to_string() + ": "+ s); fail = true };
// test env
let mut state = State::new_temp();
test.find("pre").map(|pre| for (addr, s) in pre.as_object().unwrap() {
let address = Address::from(addr.as_ref());
let balance = u256_from_json(&s["balance"]);
let code = bytes_from_json(&s["code"]);
let _nonce = u256_from_json(&s["nonce"]);
state.new_contract(&address);
state.add_balance(&address, &balance);
state.init_code(&address, code);
for (k, v) in s["storage"].as_object().unwrap() {
let key = H256::from(&u256_from_str(k));
let val = H256::from(&u256_from_json(v));
state.set_storage(&address, key, val);
}
});
let mut info = EnvInfo::new();
test.find("env").map(|env| {
info.author = address_from_json(&env["currentCoinbase"]);
info.difficulty = u256_from_json(&env["currentDifficulty"]);
info.gas_limit = u256_from_json(&env["currentGasLimit"]);
info.number = u256_from_json(&env["currentNumber"]).low_u64();
info.timestamp = u256_from_json(&env["currentTimestamp"]).low_u64();
});
let engine = TestEngine::new(0);
// params
let mut params = ActionParams::new();
test.find("exec").map(|exec| {
params.address = address_from_json(&exec["address"]);
params.sender = address_from_json(&exec["caller"]);
params.origin = address_from_json(&exec["origin"]);
params.code = bytes_from_json(&exec["code"]);
params.data = bytes_from_json(&exec["data"]);
params.gas = u256_from_json(&exec["gas"]);
params.gas_price = u256_from_json(&exec["gasPrice"]);
params.value = u256_from_json(&exec["value"]);
});
let out_of_gas = test.find("callcreates").map(|_calls| {
}).is_none();
let mut substate = Substate::new();
let mut output = vec![];
// execute
let (res, callcreates) = {
let ex = Externalities::new(&mut state, &info, &engine, 0, &params, &mut substate, OutputPolicy::Return(BytesRef::Flexible(&mut output)));
let mut test_ext = TestExt::new(ex);
let evm = Factory::create();
let res = evm.exec(&params, &mut test_ext);
(res, test_ext.callcreates)
};
// then validate
match res {
Err(_) => fail_unless(out_of_gas, "didn't expect to run out of gas."),
Ok(gas_left) => {
//println!("name: {}, gas_left : {:?}, expected: {:?}", name, gas_left, u256_from_json(&test["gas"]));
fail_unless(!out_of_gas, "expected to run out of gas.");
fail_unless(gas_left == u256_from_json(&test["gas"]), "gas_left is incorrect");
fail_unless(output == bytes_from_json(&test["out"]), "output is incorrect");
test.find("post").map(|pre| for (addr, s) in pre.as_object().unwrap() {
let address = Address::from(addr.as_ref());
//let balance = u256_from_json(&s["balance"]);
fail_unless(state.code(&address).unwrap_or(vec![]) == bytes_from_json(&s["code"]), "code is incorrect");
fail_unless(state.balance(&address) == u256_from_json(&s["balance"]), "balance is incorrect");
fail_unless(state.nonce(&address) == u256_from_json(&s["nonce"]), "nonce is incorrect");
for (k, v) in s["storage"].as_object().unwrap() {
let key = H256::from(&u256_from_str(k));
let val = H256::from(&u256_from_json(v));
fail_unless(state.storage_at(&address, &key) == val, "storage is incorrect");
}
});
let cc = test["callcreates"].as_array().unwrap();
fail_unless(callcreates.len() == cc.len(), "callcreates does not match");
for i in 0..cc.len() {
let is = &callcreates[i];
let expected = &cc[i];
fail_unless(is.data == bytes_from_json(&expected["data"]), "callcreates data is incorrect");
fail_unless(is.destination == address_from_json(&expected["destination"]), "callcreates destination is incorrect");
fail_unless(is.value == u256_from_json(&expected["value"]), "callcreates value is incorrect");
// TODO: call_gas is calculated in externalities and is not exposed to TestExt.
// maybe move it to it's own function to simplify calculation?
//println!("name: {:?}, is {:?}, expected: {:?}", name, is.gas_limit, u256_from_json(&expected["gasLimit"]));
//fail_unless(is.gas_limit == u256_from_json(&expected["gasLimit"]), "callcreates gas_limit is incorrect");
}
}
}
}
for f in failed.iter() {
println!("FAILED: {:?}", f);
}
//assert!(false);
failed
}
declare_test!{ExecutiveTests_vmArithmeticTest, "VMTests/vmArithmeticTest"}
declare_test!{ExecutiveTests_vmBitwiseLogicOperationTest, "VMTests/vmBitwiseLogicOperationTest"}
// this one crashes with some vm internal error. Separately they pass.
declare_test_ignore!{ExecutiveTests_vmBlockInfoTest, "VMTests/vmBlockInfoTest"}
declare_test!{ExecutiveTests_vmEnvironmentalInfoTest, "VMTests/vmEnvironmentalInfoTest"}
declare_test!{ExecutiveTests_vmIOandFlowOperationsTest, "VMTests/vmIOandFlowOperationsTest"}
// this one take way too long.
declare_test_ignore!{ExecutiveTests_vmInputLimits, "VMTests/vmInputLimits"}
declare_test!{ExecutiveTests_vmLogTest, "VMTests/vmLogTest"}
declare_test!{ExecutiveTests_vmPerformanceTest, "VMTests/vmPerformanceTest"}
declare_test!{ExecutiveTests_vmPushDupSwapTest, "VMTests/vmPushDupSwapTest"}
declare_test!{ExecutiveTests_vmSha3Test, "VMTests/vmSha3Test"}
declare_test!{ExecutiveTests_vmSystemOperationsTest, "VMTests/vmSystemOperationsTest"}
declare_test!{ExecutiveTests_vmtests, "VMTests/vmtests"}

View File

@ -2,4 +2,5 @@
mod test_common;
mod transaction;
mod executive;
mod state;

View File

@ -4,14 +4,14 @@ use ethereum;
pub fn hashmap_h256_h256_from_json(json: &Json) -> HashMap<H256, H256> {
json.as_object().unwrap().iter().fold(HashMap::new(), |mut m, (key, value)| {
m.insert(H256::from(&u256_from_hex(key)), H256::from(&u256_from_json(value)));
m.insert(H256::from(&u256_from_str(key)), H256::from(&u256_from_json(value)));
m
})
}
pub fn map_h256_h256_from_json(json: &Json) -> BTreeMap<H256, H256> {
json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut m, (key, value)| {
m.insert(H256::from(&u256_from_hex(key)), H256::from(&u256_from_json(value)));
m.insert(H256::from(&u256_from_str(key)), H256::from(&u256_from_json(value)));
m
})
}