diff --git a/ethcore/res/ethereum/metropolis_test.json b/ethcore/res/ethereum/metropolis_test.json new file mode 100644 index 000000000..9b367c9f4 --- /dev/null +++ b/ethcore/res/ethereum/metropolis_test.json @@ -0,0 +1,51 @@ +{ + "name": "Metropolis (Test)", + "engine": { + "Ethash": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "homesteadTransition": "0x0", + "eip150Transition": "0x0", + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "maxCodeSize": 24576 + } + } + }, + "params": { + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x1", + "eip98Transition": "0x7fffffffffffffff", + "eip86Transition": "0x0", + "eip140Transition": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } } + } +} diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index ef191fdc6..4e8b9be3f 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit ef191fdc61cf76cdb9cdc147465fb447304b0ed2 +Subproject commit 4e8b9be3fba16ec32e0cdf50b8f9329826283aaa diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index af0582a36..475496c2b 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -75,6 +75,9 @@ pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/t /// Create a new Foundation Mainnet chain spec without genesis accounts. pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } +/// Create a new Foundation Metropolis era spec. +pub fn new_metropolis_test() -> Spec { load(include_bytes!("../../res/ethereum/metropolis_test.json")) } + /// Create a new Foundation Ropsten chain spec. pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 265b83559..e03ded071 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -104,8 +104,25 @@ pub type Result = ::std::result::Result; pub enum GasLeft<'a> { /// Known gas left Known(U256), - /// Return instruction must be processed. - NeedsReturn(U256, &'a [u8]), + /// Return or Revert instruction must be processed. + NeedsReturn { + /// Amount of gas left. + gas_left: U256, + /// Return data buffer. + data: &'a [u8], + /// Apply or revert state changes on revert. + apply_state: bool + }, +} + +/// Finalization result. Gas Left: either it is a known value, or it needs to be computed by processing +/// a return instruction. +#[derive(Debug)] +pub struct FinalizationResult { + /// Final amount of gas left. + pub gas_left: U256, + /// Apply execution state changes or revert them. + pub apply_state: bool, } /// Types that can be "finalized" using an EVM. @@ -113,15 +130,18 @@ pub enum GasLeft<'a> { /// In practice, this is just used to define an inherent impl on /// `Reult>`. pub trait Finalize { - /// Consume the externalities, call return if necessary, and produce a final amount of gas left. - fn finalize(self, ext: E) -> Result; + /// Consume the externalities, call return if necessary, and produce call result. + fn finalize(self, ext: E) -> Result; } impl<'a> Finalize for Result> { - fn finalize(self, ext: E) -> Result { + fn finalize(self, ext: E) -> Result { match self { - Ok(GasLeft::Known(gas)) => Ok(gas), - Ok(GasLeft::NeedsReturn(gas, ret_code)) => ext.ret(&gas, ret_code), + Ok(GasLeft::Known(gas_left)) => Ok(FinalizationResult { gas_left: gas_left, apply_state: true }), + Ok(GasLeft::NeedsReturn {gas_left, data, apply_state}) => ext.ret(&gas_left, data).map(|gas_left| FinalizationResult { + gas_left: gas_left, + apply_state: apply_state, + }), Err(err) => Err(err), } } diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index eef1a9e3b..336a3dcf1 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -279,6 +279,7 @@ lazy_static! { arr[DELEGATECALL as usize] = InstructionInfo::new("DELEGATECALL", 0, 6, 1, true, GasPriceTier::Special); arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Special); arr[CREATE2 as usize] = InstructionInfo::new("CREATE2", 0, 3, 1, true, GasPriceTier::Special); + arr[REVERT as usize] = InstructionInfo::new("REVERT", 0, 2, 0, true, GasPriceTier::Zero); arr }; } @@ -556,6 +557,8 @@ pub const RETURN: Instruction = 0xf3; pub const DELEGATECALL: Instruction = 0xf4; /// create a new account and set creation address to sha3(sender + sha3(init code)) % 2**160 pub const CREATE2: Instruction = 0xfb; +/// stop execution and revert state changes. Return output data. +pub const REVERT: Instruction = 0xfd; /// halt execution and register account for later deletion pub const SUICIDE: Instruction = 0xff; diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index 246c93bad..8f2309fe2 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -164,7 +164,7 @@ impl Gasometer { instructions::MSTORE8 => { Request::GasMem(default_gas, mem_needed_const(stack.peek(0), 1)?) }, - instructions::RETURN => { + instructions::RETURN | instructions::REVERT => { Request::GasMem(default_gas, mem_needed(stack.peek(0), stack.peek(1))?) }, instructions::SHA3 => { diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index f08737d24..c0a151f49 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -84,8 +84,16 @@ enum InstructionResult { Ok, UnusedGas(Gas), JumpToPosition(U256), - // gas left, init_orf, init_size - StopExecutionNeedsReturn(Gas, U256, U256), + StopExecutionNeedsReturn { + /// Gas left. + gas: Gas, + /// Return data offset. + init_off: U256, + /// Return data size. + init_size: U256, + /// Apply or revert state changes. + apply: bool, + }, StopExecution, } @@ -156,9 +164,13 @@ impl evm::Evm for Interpreter { let pos = self.verify_jump(position, &valid_jump_destinations)?; reader.position = pos; }, - InstructionResult::StopExecutionNeedsReturn(gas, off, size) => { + InstructionResult::StopExecutionNeedsReturn {gas, init_off, init_size, apply} => { informant.done(); - return Ok(GasLeft::NeedsReturn(gas.as_u256(), self.mem.read_slice(off, size))); + return Ok(GasLeft::NeedsReturn { + gas_left: gas.as_u256(), + data: self.mem.read_slice(init_off, init_size), + apply_state: apply + }); }, InstructionResult::StopExecution => break, _ => {}, @@ -183,7 +195,8 @@ impl Interpreter { let schedule = ext.schedule(); if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call) || - (instruction == instructions::CREATE2 && !schedule.have_create2) { + (instruction == instructions::CREATE2 && !schedule.have_create2) || + (instruction == instructions::REVERT && !schedule.have_revert) { return Err(evm::Error::BadInstruction { instruction: instruction @@ -363,7 +376,13 @@ impl Interpreter { let init_off = stack.pop_back(); let init_size = stack.pop_back(); - return Ok(InstructionResult::StopExecutionNeedsReturn(gas, init_off, init_size)) + return Ok(InstructionResult::StopExecutionNeedsReturn {gas: gas, init_off: init_off, init_size: init_size, apply: true}) + }, + instructions::REVERT => { + let init_off = stack.pop_back(); + let init_size = stack.pop_back(); + + return Ok(InstructionResult::StopExecutionNeedsReturn {gas: gas, init_off: init_off, init_size: init_size, apply: false}) }, instructions::STOP => { return Ok(InstructionResult::StopExecution); diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index 7906b81ff..8693a3467 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -31,7 +31,7 @@ mod tests; #[cfg(all(feature="benches", test))] mod benches; -pub use self::evm::{Evm, Error, Finalize, GasLeft, Result, CostType}; +pub use self::evm::{Evm, Error, Finalize, FinalizationResult, GasLeft, Result, CostType}; pub use self::ext::{Ext, ContractCreateResult, MessageCallResult, CreateContractAddress}; pub use self::factory::{Factory, VMType}; pub use self::schedule::Schedule; diff --git a/ethcore/src/evm/schedule.rs b/ethcore/src/evm/schedule.rs index 3e01f3925..2a3d0f70a 100644 --- a/ethcore/src/evm/schedule.rs +++ b/ethcore/src/evm/schedule.rs @@ -24,6 +24,8 @@ pub struct Schedule { pub have_delegate_call: bool, /// Does it have a CREATE_P2SH instruction pub have_create2: bool, + /// Does it have a REVERT instruction + pub have_revert: bool, /// VM stack limit pub stack_limit: usize, /// Max number of nested calls/creates @@ -120,6 +122,7 @@ impl Schedule { exceptional_failed_code_deposit: true, have_delegate_call: true, have_create2: have_metropolis_instructions, + have_revert: have_metropolis_instructions, stack_limit: 1024, max_depth: 1024, tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0], @@ -171,6 +174,7 @@ impl Schedule { exceptional_failed_code_deposit: efcd, have_delegate_call: hdc, have_create2: false, + have_revert: false, stack_limit: 1024, max_depth: 1024, tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0], diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index b5b2341aa..21a0fc378 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -64,7 +64,7 @@ pub struct FakeExt { fn test_finalize(res: Result) -> Result { match res { Ok(GasLeft::Known(gas)) => Ok(gas), - Ok(GasLeft::NeedsReturn(_, _)) => unimplemented!(), // since ret is unimplemented. + Ok(GasLeft::NeedsReturn{..}) => unimplemented!(), // since ret is unimplemented. Err(e) => Err(e), } } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index bfba4ab3d..33f7f59d5 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -22,7 +22,7 @@ use engines::Engine; use types::executed::CallType; use env_info::EnvInfo; use error::ExecutionError; -use evm::{self, Ext, Factory, Finalize, CreateContractAddress}; +use evm::{self, Ext, Factory, Finalize, CreateContractAddress, FinalizationResult}; use externalities::*; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; use transaction::{Action, SignedTransaction}; @@ -246,7 +246,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { output_policy: OutputPolicy, tracer: &mut T, vm_tracer: &mut V - ) -> evm::Result where T: Tracer, V: VMTracer { + ) -> evm::Result where T: Tracer, V: VMTracer { let depth_threshold = ::io::LOCAL_STACK_SIZE.with(|sz| sz.get() / STACK_SIZE_PER_DEPTH); @@ -366,9 +366,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let traces = subtracer.traces(); match res { - Ok(ref gas_left) => tracer.trace_call( + Ok(ref res) => tracer.trace_call( trace_info, - gas - *gas_left, + gas - res.gas_left, trace_output, traces ), @@ -379,7 +379,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { self.enact_result(&res, substate, unconfirmed_substate); trace!(target: "executive", "enacted: substate={:?}\n", substate); - res + res.map(|r| r.gas_left) } else { // otherwise it's just a basic transaction, only do tracing, if necessary. self.state.discard_checkpoint(); @@ -438,9 +438,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { vm_tracer.done_subtrace(subvmtracer); match res { - Ok(ref gas_left) => tracer.trace_create( + Ok(ref res) => tracer.trace_create( trace_info, - gas - *gas_left, + gas - res.gas_left, trace_output, created, subtracer.traces() @@ -449,7 +449,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { }; self.enact_result(&res, substate, unconfirmed_substate); - res + res.map(|r| r.gas_left) } /// Finalizes the transaction (does refunds and suicides). @@ -536,14 +536,15 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { } } - fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate) { + fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate) { match *result { Err(evm::Error::OutOfGas) | Err(evm::Error::BadJumpDestination {..}) | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::BuiltIn {..}) - | Err(evm::Error::OutOfStack {..}) => { + | Err(evm::Error::OutOfStack {..}) + | Ok(FinalizationResult { apply_state: false, .. }) => { self.state.revert_to_checkpoint(); }, Ok(_) | Err(evm::Error::Internal(_)) => { @@ -1242,11 +1243,43 @@ mod tests { }; match result { - Err(_) => { - }, - _ => { - panic!("Expected OutOfGas"); - } + Err(_) => {}, + _ => panic!("Expected OutOfGas"), } } + + evm_test!{test_revert: test_revert_jit, test_revert_int} + fn test_revert(factory: Factory) { + let contract_address = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); + let sender = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); + // EIP-140 test case + let code = "6c726576657274656420646174616000557f726576657274206d657373616765000000000000000000000000000000000000600052600e6000fd".from_hex().unwrap(); + let returns = "726576657274206d657373616765".from_hex().unwrap(); + let mut state = get_temp_state(); + state.add_balance(&sender, &U256::from_str("152d02c7e14af68000000").unwrap(), CleanupMode::NoEmpty).unwrap(); + state.commit().unwrap(); + + let mut params = ActionParams::default(); + params.address = contract_address.clone(); + params.sender = sender.clone(); + params.origin = sender.clone(); + params.gas = U256::from(20025); + params.code = Some(Arc::new(code)); + params.value = ActionValue::Transfer(U256::zero()); + let mut state = get_temp_state(); + state.add_balance(&sender, &U256::from_str("152d02c7e14af68000000").unwrap(), CleanupMode::NoEmpty).unwrap(); + let info = EnvInfo::default(); + let engine = TestEngine::new_metropolis(); + let mut substate = Substate::new(); + + let mut output = [0u8; 14]; + let result = { + let mut ex = Executive::new(&mut state, &info, &engine, &factory); + ex.call(params, &mut substate, BytesRef::Fixed(&mut output), &mut NoopTracer, &mut NoopVMTracer).unwrap() + }; + + assert_eq!(result, U256::from(1)); + assert_eq!(output[..], returns[..]); + assert_eq!(state.storage_at(&contract_address, &H256::from(&U256::zero())).unwrap(), H256::from(&U256::from(0))); + } } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index c34ad69e3..37c7ebf50 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -254,9 +254,9 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { match res { Err(_) => fail_unless(out_of_gas, "didn't expect to run out of gas."), - Ok(gas_left) => { + Ok(res) => { fail_unless(!out_of_gas, "expected to run out of gas."); - fail_unless(Some(gas_left) == vm.gas_left.map(Into::into), "gas_left is incorrect"); + fail_unless(Some(res.gas_left) == vm.gas_left.map(Into::into), "gas_left is incorrect"); let vm_output: Option> = vm.output.map(Into::into); fail_unless(Some(output) == vm_output, "output is incorrect"); diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index c15847896..d0d5e9746 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -29,6 +29,7 @@ lazy_static! { pub static ref HOMESTEAD: Spec = ethereum::new_homestead_test(); pub static ref EIP150: Spec = ethereum::new_eip150_test(); pub static ref EIP161: Spec = ethereum::new_eip161_test(); + pub static ref _METROPOLIS: Spec = ethereum::new_metropolis_test(); } pub fn json_chain_test(json_data: &[u8]) -> Vec { @@ -92,7 +93,6 @@ mod state_tests { } declare_test!{GeneralStateTest_stAttackTest, "GeneralStateTests/stAttackTest/"} - declare_test!{GeneralStateTest_stBlockHashTest, "GeneralStateTests/stBlockHashTest/"} declare_test!{GeneralStateTest_stBoundsTest, "GeneralStateTests/stBoundsTest/"} declare_test!{GeneralStateTest_stCallCodes, "GeneralStateTests/stCallCodes/"} declare_test!{skip => [ "createJS_ExampleContract" ], GeneralStateTest_stCallCreateCallCodeTest, "GeneralStateTests/stCallCreateCallCodeTest/"} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index efbfc435d..74805b233 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -61,6 +61,8 @@ pub struct CommonParams { pub validate_receipts_transition: u64, /// Number of first block where EIP-86 (Metropolis) rules begin. pub eip86_transition: BlockNumber, + /// Number of first block where EIP-140 (Metropolis: REVERT opcode) rules begin. + pub eip140_transition: BlockNumber, } impl From for CommonParams { @@ -76,6 +78,7 @@ impl From for CommonParams { eip98_transition: p.eip98_transition.map_or(0, Into::into), validate_receipts_transition: p.validate_receipts_transition.map_or(0, Into::into), eip86_transition: p.eip86_transition.map_or(BlockNumber::max_value(), Into::into), + eip140_transition: p.eip140_transition.map_or(BlockNumber::max_value(), Into::into), } } } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 548187e48..57b0adbea 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -56,6 +56,13 @@ impl TestEngine { max_depth: max_depth, } } + + pub fn new_metropolis() -> TestEngine { + TestEngine { + engine: ethereum::new_metropolis_test().engine, + max_depth: 0, + } + } } impl Engine for TestEngine { @@ -72,7 +79,7 @@ impl Engine for TestEngine { } fn schedule(&self, _block_number: u64) -> Schedule { - let mut schedule = Schedule::new_frontier(); + let mut schedule = self.engine.schedule(0); schedule.max_depth = self.max_depth; schedule } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 6fbe5a280..02513eebd 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -75,12 +75,12 @@ pub fn run_vm(params: ActionParams) -> Result { let mut ext = ext::FakeExt::default(); let start = Instant::now(); - let gas_left = vm.exec(params, &mut ext).finalize(ext); + let res = vm.exec(params, &mut ext).finalize(ext); let duration = start.elapsed(); - match gas_left { - Ok(gas_left) => Ok(Success { - gas_used: initial_gas - gas_left, + match res { + Ok(res) => Ok(Success { + gas_used: initial_gas - res.gas_left, // TODO [ToDr] get output from ext output: Vec::new(), time: duration, diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 31b5cf68a..824466ffc 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -59,6 +59,9 @@ pub struct Params { /// See `CommonParams` docs. #[serde(rename="eip86Transition")] pub eip86_transition: Option, + /// See `CommonParams` docs. + #[serde(rename="eip140Transition")] + pub eip140_transition: Option, } #[cfg(test)]