diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5256c1077..f38feb1f2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ master ] + branches: [ master, next ] pull_request: - branches: [ master ] + branches: [ master, next ] env: CARGO_TERM_COLOR: always @@ -16,32 +16,34 @@ jobs: - uses: actions/checkout@v2 - name: Rustfmt run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --all build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose - - name: Build for feature (tracing) - run: cargo build --features tracing --verbose - name: Run tests run: cargo test --verbose jsontests: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - uses: actions/checkout@v2 with: - repository: "rust-blockchain/evm-tests" - submodules: recursive - - name: Submodules - run: | - cd evm - git remote set-url origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" - git fetch origin $GITHUB_SHA - git checkout $GITHUB_SHA + path: jsontests/res/ethtests + repository: ethereum/tests + ref: 428f218d7d6f4a52544e12684afbfe6e2882ffbf - name: Run tests run: | - cd jsontests - cargo test --release --verbose + cargo run --release --verbose -p jsontests -- \ + jsontests/res/ethtests/GeneralStateTests/stArgsZeroOneBalance/ \ + jsontests/res/ethtests/GeneralStateTests/stCodeCopyTest/ \ + jsontests/res/ethtests/GeneralStateTests/stExample/ \ + jsontests/res/ethtests/GeneralStateTests/stSelfBalance \ + jsontests/res/ethtests/GeneralStateTests/stSLoadTest/ \ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmArithmeticTest/ \ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmBitwiseLogicOperation/ \ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmIOandFlowOperations/ \ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmLogTest/ \ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmTests/ + diff --git a/.gitignore b/.gitignore index f16e0361b..bd82f0750 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ target **/result tests.bin -Cargo.lock \ No newline at end of file +Cargo.lock +perf.data +perf.data.old diff --git a/Cargo.toml b/Cargo.toml index c867e3f21..10b94bff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm" -version = "0.41.0" +version = "0.40.0" license = "Apache-2.0" authors = ["Wei Tang ", "Parity Technologies "] description = "SputnikVM - a Portable Blockchain Virtual Machine" @@ -10,7 +10,7 @@ edition = "2018" [dependencies] auto_impl = "1.0" -ethereum = { version = "0.15", default-features = false } +ethereum = { version = "0.14", default-features = false } log = { version = "0.4", default-features = false } primitive-types = { version = "0.12", default-features = false, features = ["rlp"] } rlp = { version = "0.5", default-features = false } @@ -22,18 +22,12 @@ scale-codec = { package = "parity-scale-codec", version = "3.2", default-feature scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } -evm-core = { version = "0.41", path = "core", default-features = false } -evm-gasometer = { version = "0.41", path = "gasometer", default-features = false } -evm-runtime = { version = "0.41", path = "runtime", default-features = false } +evm-interpreter = { version = "0.2.0-dev", path = "interpreter", default-features = false } [dev-dependencies] criterion = "0.4" hex = "0.4" -[[bench]] -name = "loop" -harness = false - [features] default = ["std"] std = [ @@ -46,9 +40,7 @@ std = [ "scale-codec/std", "scale-info/std", "serde/std", - "evm-core/std", - "evm-gasometer/std", - "evm-runtime/std", + "evm-interpreter/std", ] with-codec = [ "scale-codec", @@ -56,28 +48,19 @@ with-codec = [ "primitive-types/codec", "primitive-types/scale-info", "ethereum/with-codec", - "evm-core/with-codec", + "evm-interpreter/with-codec", ] with-serde = [ "serde", "primitive-types/impl-serde", - "evm-core/with-serde", + "evm-interpreter/with-serde", "ethereum/with-serde", ] -tracing = [ - "environmental", - "evm-gasometer/tracing", - "evm-runtime/tracing", -] -force-debug = [ - "evm-core/force-debug", - "evm-gasometer/force-debug", -] [workspace] members = [ - "core", - "gasometer", - "runtime", - "fuzzer", + "interpreter", + "jsontests", + "precompiles", + "tracer", ] diff --git a/benches/loop.rs b/benches/loop.rs deleted file mode 100644 index b9ca7ae78..000000000 --- a/benches/loop.rs +++ /dev/null @@ -1,68 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use evm::backend::{MemoryAccount, MemoryBackend, MemoryVicinity}; -use evm::executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata}; -use evm::Config; -use primitive_types::{H160, U256}; -use std::{collections::BTreeMap, str::FromStr}; - -fn run_loop_contract() { - let config = Config::istanbul(); - - let vicinity = MemoryVicinity { - gas_price: U256::zero(), - origin: H160::default(), - block_hashes: Vec::new(), - block_number: Default::default(), - block_coinbase: Default::default(), - block_timestamp: Default::default(), - block_difficulty: Default::default(), - block_gas_limit: Default::default(), - chain_id: U256::one(), - block_base_fee_per_gas: U256::zero(), - block_randomness: None, - }; - - let mut state = BTreeMap::new(); - state.insert( - H160::from_str("0x1000000000000000000000000000000000000000").unwrap(), - MemoryAccount { - nonce: U256::one(), - balance: U256::from(10000000), - storage: BTreeMap::new(), - code: hex::decode("6080604052348015600f57600080fd5b506004361060285760003560e01c80630f14a40614602d575b600080fd5b605660048036036020811015604157600080fd5b8101908080359060200190929190505050606c565b6040518082815260200191505060405180910390f35b6000806000905060005b83811015608f5760018201915080806001019150506076565b508091505091905056fea26469706673582212202bc9ec597249a9700278fe4ce78da83273cb236e76d4d6797b441454784f901d64736f6c63430007040033").unwrap(), - } - ); - state.insert( - H160::from_str("0xf000000000000000000000000000000000000000").unwrap(), - MemoryAccount { - nonce: U256::one(), - balance: U256::from(10000000), - storage: BTreeMap::new(), - code: Vec::new(), - }, - ); - - let backend = MemoryBackend::new(&vicinity, state); - let metadata = StackSubstateMetadata::new(u64::MAX, &config); - let state = MemoryStackState::new(metadata, &backend); - let precompiles = BTreeMap::new(); - let mut executor = StackExecutor::new_with_precompiles(state, &config, &precompiles); - - let _reason = executor.transact_call( - H160::from_str("0xf000000000000000000000000000000000000000").unwrap(), - H160::from_str("0x1000000000000000000000000000000000000000").unwrap(), - U256::zero(), - hex::decode("0f14a4060000000000000000000000000000000000000000000000000000000000b71b00") - .unwrap(), - // hex::decode("0f14a4060000000000000000000000000000000000000000000000000000000000002ee0").unwrap(), - u64::MAX, - Vec::new(), - ); -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("loop contract", |b| b.iter(run_loop_contract)); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs deleted file mode 100644 index 519f5d925..000000000 --- a/core/src/eval/mod.rs +++ /dev/null @@ -1,577 +0,0 @@ -#[macro_use] -mod macros; -mod arithmetic; -mod bitwise; -mod misc; - -use crate::{ExitError, ExitReason, ExitSucceed, Machine, Opcode}; -use core::ops::{BitAnd, BitOr, BitXor}; -use primitive_types::{H256, U256}; - -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum Control { - Continue(usize), - Exit(ExitReason), - Jump(usize), - Trap(Opcode), -} - -fn eval_stop(_state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - Control::Exit(ExitSucceed::Stopped.into()) -} - -fn eval_add(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_tuple!(state, overflowing_add) -} - -fn eval_mul(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_tuple!(state, overflowing_mul) -} - -fn eval_sub(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_tuple!(state, overflowing_sub) -} - -fn eval_div(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::div) -} - -fn eval_sdiv(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::sdiv) -} - -fn eval_mod(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::rem) -} - -fn eval_smod(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::srem) -} - -fn eval_addmod(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op3_u256_fn!(state, self::arithmetic::addmod) -} - -fn eval_mulmod(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op3_u256_fn!(state, self::arithmetic::mulmod) -} - -fn eval_exp(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::exp) -} - -fn eval_signextend(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::arithmetic::signextend) -} - -fn eval_lt(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_bool_ref!(state, lt) -} - -fn eval_gt(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_bool_ref!(state, gt) -} - -fn eval_slt(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::slt) -} - -fn eval_sgt(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::sgt) -} - -fn eval_eq(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_bool_ref!(state, eq) -} - -fn eval_iszero(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op1_u256_fn!(state, self::bitwise::iszero) -} - -fn eval_and(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256!(state, bitand) -} - -fn eval_or(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256!(state, bitor) -} - -fn eval_xor(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256!(state, bitxor) -} - -fn eval_not(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op1_u256_fn!(state, self::bitwise::not) -} - -fn eval_byte(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::byte) -} - -fn eval_shl(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::shl) -} - -fn eval_shr(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::shr) -} - -fn eval_sar(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - op2_u256_fn!(state, self::bitwise::sar) -} - -fn eval_codesize(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::codesize(state) -} - -fn eval_codecopy(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::codecopy(state) -} - -fn eval_calldataload(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::calldataload(state) -} - -fn eval_calldatasize(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::calldatasize(state) -} - -fn eval_calldatacopy(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::calldatacopy(state) -} - -fn eval_pop(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::pop(state) -} - -fn eval_mload(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::mload(state) -} - -fn eval_mstore(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::mstore(state) -} - -fn eval_mstore8(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::mstore8(state) -} - -fn eval_jump(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::jump(state) -} - -fn eval_jumpi(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::jumpi(state) -} - -fn eval_pc(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::pc(state, position) -} - -fn eval_msize(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::msize(state) -} - -fn eval_jumpdest(_state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - Control::Continue(1) -} - -fn eval_push0(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 0, position) -} - -fn eval_push1(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 1, position) -} - -fn eval_push2(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 2, position) -} - -fn eval_push3(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 3, position) -} - -fn eval_push4(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 4, position) -} - -fn eval_push5(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 5, position) -} - -fn eval_push6(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 6, position) -} - -fn eval_push7(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 7, position) -} - -fn eval_push8(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 8, position) -} - -fn eval_push9(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 9, position) -} - -fn eval_push10(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 10, position) -} - -fn eval_push11(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 11, position) -} - -fn eval_push12(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 12, position) -} - -fn eval_push13(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 13, position) -} - -fn eval_push14(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 14, position) -} - -fn eval_push15(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 15, position) -} - -fn eval_push16(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 16, position) -} - -fn eval_push17(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 17, position) -} - -fn eval_push18(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 18, position) -} - -fn eval_push19(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 19, position) -} - -fn eval_push20(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 20, position) -} - -fn eval_push21(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 21, position) -} - -fn eval_push22(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 22, position) -} - -fn eval_push23(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 23, position) -} - -fn eval_push24(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 24, position) -} - -fn eval_push25(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 25, position) -} - -fn eval_push26(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 26, position) -} - -fn eval_push27(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 27, position) -} - -fn eval_push28(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 28, position) -} - -fn eval_push29(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 29, position) -} - -fn eval_push30(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 30, position) -} - -fn eval_push31(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 31, position) -} - -fn eval_push32(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { - self::misc::push(state, 32, position) -} - -fn eval_dup1(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 1) -} - -fn eval_dup2(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 2) -} - -fn eval_dup3(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 3) -} - -fn eval_dup4(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 4) -} - -fn eval_dup5(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 5) -} - -fn eval_dup6(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 6) -} - -fn eval_dup7(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 7) -} - -fn eval_dup8(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 8) -} - -fn eval_dup9(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 9) -} - -fn eval_dup10(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 10) -} - -fn eval_dup11(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 11) -} - -fn eval_dup12(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 12) -} - -fn eval_dup13(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 13) -} - -fn eval_dup14(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 14) -} - -fn eval_dup15(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 15) -} - -fn eval_dup16(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::dup(state, 16) -} - -fn eval_swap1(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 1) -} - -fn eval_swap2(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 2) -} - -fn eval_swap3(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 3) -} - -fn eval_swap4(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 4) -} - -fn eval_swap5(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 5) -} - -fn eval_swap6(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 6) -} - -fn eval_swap7(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 7) -} - -fn eval_swap8(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 8) -} - -fn eval_swap9(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 9) -} - -fn eval_swap10(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 10) -} - -fn eval_swap11(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 11) -} - -fn eval_swap12(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 12) -} - -fn eval_swap13(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 13) -} - -fn eval_swap14(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 14) -} - -fn eval_swap15(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 15) -} - -fn eval_swap16(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::swap(state, 16) -} - -fn eval_return(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::ret(state) -} - -fn eval_revert(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - self::misc::revert(state) -} - -fn eval_invalid(_state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { - Control::Exit(ExitError::DesignatedInvalid.into()) -} - -fn eval_external(_state: &mut Machine, opcode: Opcode, _position: usize) -> Control { - Control::Trap(opcode) -} - -#[inline] -pub fn eval(state: &mut Machine, opcode: Opcode, position: usize) -> Control { - static TABLE: [fn(state: &mut Machine, opcode: Opcode, position: usize) -> Control; 256] = { - let mut table = [eval_external as _; 256]; - - table[Opcode::STOP.as_usize()] = eval_stop as _; - table[Opcode::ADD.as_usize()] = eval_add as _; - table[Opcode::MUL.as_usize()] = eval_mul as _; - table[Opcode::SUB.as_usize()] = eval_sub as _; - table[Opcode::DIV.as_usize()] = eval_div as _; - table[Opcode::SDIV.as_usize()] = eval_sdiv as _; - table[Opcode::MOD.as_usize()] = eval_mod as _; - table[Opcode::SMOD.as_usize()] = eval_smod as _; - table[Opcode::ADDMOD.as_usize()] = eval_addmod as _; - table[Opcode::MULMOD.as_usize()] = eval_mulmod as _; - table[Opcode::EXP.as_usize()] = eval_exp as _; - table[Opcode::SIGNEXTEND.as_usize()] = eval_signextend as _; - table[Opcode::LT.as_usize()] = eval_lt as _; - table[Opcode::GT.as_usize()] = eval_gt as _; - table[Opcode::SLT.as_usize()] = eval_slt as _; - table[Opcode::SGT.as_usize()] = eval_sgt as _; - table[Opcode::EQ.as_usize()] = eval_eq as _; - table[Opcode::ISZERO.as_usize()] = eval_iszero as _; - table[Opcode::AND.as_usize()] = eval_and as _; - table[Opcode::OR.as_usize()] = eval_or as _; - table[Opcode::XOR.as_usize()] = eval_xor as _; - table[Opcode::NOT.as_usize()] = eval_not as _; - table[Opcode::BYTE.as_usize()] = eval_byte as _; - table[Opcode::SHL.as_usize()] = eval_shl as _; - table[Opcode::SHR.as_usize()] = eval_shr as _; - table[Opcode::SAR.as_usize()] = eval_sar as _; - table[Opcode::CODESIZE.as_usize()] = eval_codesize as _; - table[Opcode::CODECOPY.as_usize()] = eval_codecopy as _; - table[Opcode::CALLDATALOAD.as_usize()] = eval_calldataload as _; - table[Opcode::CALLDATASIZE.as_usize()] = eval_calldatasize as _; - table[Opcode::CALLDATACOPY.as_usize()] = eval_calldatacopy as _; - table[Opcode::POP.as_usize()] = eval_pop as _; - table[Opcode::MLOAD.as_usize()] = eval_mload as _; - table[Opcode::MSTORE.as_usize()] = eval_mstore as _; - table[Opcode::MSTORE8.as_usize()] = eval_mstore8 as _; - table[Opcode::JUMP.as_usize()] = eval_jump as _; - table[Opcode::JUMPI.as_usize()] = eval_jumpi as _; - table[Opcode::PC.as_usize()] = eval_pc as _; - table[Opcode::MSIZE.as_usize()] = eval_msize as _; - table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; - - table[Opcode::PUSH0.as_usize()] = eval_push0 as _; - table[Opcode::PUSH1.as_usize()] = eval_push1 as _; - table[Opcode::PUSH2.as_usize()] = eval_push2 as _; - table[Opcode::PUSH3.as_usize()] = eval_push3 as _; - table[Opcode::PUSH4.as_usize()] = eval_push4 as _; - table[Opcode::PUSH5.as_usize()] = eval_push5 as _; - table[Opcode::PUSH6.as_usize()] = eval_push6 as _; - table[Opcode::PUSH7.as_usize()] = eval_push7 as _; - table[Opcode::PUSH8.as_usize()] = eval_push8 as _; - table[Opcode::PUSH9.as_usize()] = eval_push9 as _; - table[Opcode::PUSH10.as_usize()] = eval_push10 as _; - table[Opcode::PUSH11.as_usize()] = eval_push11 as _; - table[Opcode::PUSH12.as_usize()] = eval_push12 as _; - table[Opcode::PUSH13.as_usize()] = eval_push13 as _; - table[Opcode::PUSH14.as_usize()] = eval_push14 as _; - table[Opcode::PUSH15.as_usize()] = eval_push15 as _; - table[Opcode::PUSH16.as_usize()] = eval_push16 as _; - table[Opcode::PUSH17.as_usize()] = eval_push17 as _; - table[Opcode::PUSH18.as_usize()] = eval_push18 as _; - table[Opcode::PUSH19.as_usize()] = eval_push19 as _; - table[Opcode::PUSH20.as_usize()] = eval_push20 as _; - table[Opcode::PUSH21.as_usize()] = eval_push21 as _; - table[Opcode::PUSH22.as_usize()] = eval_push22 as _; - table[Opcode::PUSH23.as_usize()] = eval_push23 as _; - table[Opcode::PUSH24.as_usize()] = eval_push24 as _; - table[Opcode::PUSH25.as_usize()] = eval_push25 as _; - table[Opcode::PUSH26.as_usize()] = eval_push26 as _; - table[Opcode::PUSH27.as_usize()] = eval_push27 as _; - table[Opcode::PUSH28.as_usize()] = eval_push28 as _; - table[Opcode::PUSH29.as_usize()] = eval_push29 as _; - table[Opcode::PUSH30.as_usize()] = eval_push30 as _; - table[Opcode::PUSH31.as_usize()] = eval_push31 as _; - table[Opcode::PUSH32.as_usize()] = eval_push32 as _; - - table[Opcode::DUP1.as_usize()] = eval_dup1 as _; - table[Opcode::DUP2.as_usize()] = eval_dup2 as _; - table[Opcode::DUP3.as_usize()] = eval_dup3 as _; - table[Opcode::DUP4.as_usize()] = eval_dup4 as _; - table[Opcode::DUP5.as_usize()] = eval_dup5 as _; - table[Opcode::DUP6.as_usize()] = eval_dup6 as _; - table[Opcode::DUP7.as_usize()] = eval_dup7 as _; - table[Opcode::DUP8.as_usize()] = eval_dup8 as _; - table[Opcode::DUP9.as_usize()] = eval_dup9 as _; - table[Opcode::DUP10.as_usize()] = eval_dup10 as _; - table[Opcode::DUP11.as_usize()] = eval_dup11 as _; - table[Opcode::DUP12.as_usize()] = eval_dup12 as _; - table[Opcode::DUP13.as_usize()] = eval_dup13 as _; - table[Opcode::DUP14.as_usize()] = eval_dup14 as _; - table[Opcode::DUP15.as_usize()] = eval_dup15 as _; - table[Opcode::DUP16.as_usize()] = eval_dup16 as _; - - table[Opcode::SWAP1.as_usize()] = eval_swap1 as _; - table[Opcode::SWAP2.as_usize()] = eval_swap2 as _; - table[Opcode::SWAP3.as_usize()] = eval_swap3 as _; - table[Opcode::SWAP4.as_usize()] = eval_swap4 as _; - table[Opcode::SWAP5.as_usize()] = eval_swap5 as _; - table[Opcode::SWAP6.as_usize()] = eval_swap6 as _; - table[Opcode::SWAP7.as_usize()] = eval_swap7 as _; - table[Opcode::SWAP8.as_usize()] = eval_swap8 as _; - table[Opcode::SWAP9.as_usize()] = eval_swap9 as _; - table[Opcode::SWAP10.as_usize()] = eval_swap10 as _; - table[Opcode::SWAP11.as_usize()] = eval_swap11 as _; - table[Opcode::SWAP12.as_usize()] = eval_swap12 as _; - table[Opcode::SWAP13.as_usize()] = eval_swap13 as _; - table[Opcode::SWAP14.as_usize()] = eval_swap14 as _; - table[Opcode::SWAP15.as_usize()] = eval_swap15 as _; - table[Opcode::SWAP16.as_usize()] = eval_swap16 as _; - - table[Opcode::RETURN.as_usize()] = eval_return as _; - table[Opcode::REVERT.as_usize()] = eval_revert as _; - table[Opcode::INVALID.as_usize()] = eval_invalid as _; - - table - }; - - TABLE[opcode.as_usize()](state, opcode, position) -} diff --git a/core/src/external.rs b/core/src/external.rs deleted file mode 100644 index 591e09ad4..000000000 --- a/core/src/external.rs +++ /dev/null @@ -1,13 +0,0 @@ -use primitive_types::{H160, U256}; - -/// Operations for recording external costs -pub enum ExternalOperation { - /// Reading basic account from storage. Fixed size. - AccountBasicRead, - /// Reading address code from storage. Dynamic size. - AddressCodeRead(H160), - /// Basic check for account emptiness. Fixed size. - IsEmpty, - /// Writing to storage (Number of bytes written). - Write(U256), -} diff --git a/core/src/lib.rs b/core/src/lib.rs deleted file mode 100644 index 53aafc364..000000000 --- a/core/src/lib.rs +++ /dev/null @@ -1,177 +0,0 @@ -//! Core layer for EVM. - -#![deny(warnings)] -#![forbid(unsafe_code, unused_variables, unused_imports)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -mod error; -mod eval; -mod external; -mod memory; -mod opcode; -mod stack; -mod utils; -mod valids; - -pub use crate::error::{Capture, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed, Trap}; -pub use crate::external::ExternalOperation; -pub use crate::memory::Memory; -pub use crate::opcode::Opcode; -pub use crate::stack::Stack; -pub use crate::valids::Valids; - -use crate::eval::{eval, Control}; -use alloc::rc::Rc; -use alloc::vec::Vec; -use core::ops::Range; -use primitive_types::U256; - -/// Core execution layer for EVM. -pub struct Machine { - /// Program data. - data: Rc>, - /// Program code. - code: Rc>, - /// Program counter. - position: Result, - /// Return value. - return_range: Range, - /// Code validity maps. - valids: Valids, - /// Memory. - memory: Memory, - /// Stack. - stack: Stack, -} - -impl Machine { - /// Reference of machine stack. - pub fn stack(&self) -> &Stack { - &self.stack - } - /// Mutable reference of machine stack. - pub fn stack_mut(&mut self) -> &mut Stack { - &mut self.stack - } - /// Reference of machine memory. - pub fn memory(&self) -> &Memory { - &self.memory - } - /// Mutable reference of machine memory. - pub fn memory_mut(&mut self) -> &mut Memory { - &mut self.memory - } - /// Return a reference of the program counter. - pub fn position(&self) -> &Result { - &self.position - } - - /// Create a new machine with given code and data. - pub fn new( - code: Rc>, - data: Rc>, - stack_limit: usize, - memory_limit: usize, - ) -> Self { - let valids = Valids::new(&code[..]); - - Self { - data, - code, - position: Ok(0), - return_range: U256::zero()..U256::zero(), - valids, - memory: Memory::new(memory_limit), - stack: Stack::new(stack_limit), - } - } - - /// Explicit exit of the machine. Further step will return error. - pub fn exit(&mut self, reason: ExitReason) { - self.position = Err(reason); - } - - /// Inspect the machine's next opcode and current stack. - pub fn inspect(&self) -> Option<(Opcode, &Stack)> { - let position = match self.position { - Ok(position) => position, - Err(_) => return None, - }; - self.code.get(position).map(|v| (Opcode(*v), &self.stack)) - } - - /// Copy and get the return value of the machine, if any. - pub fn return_value(&self) -> Vec { - if self.return_range.start > U256::from(usize::MAX) { - let mut ret = Vec::new(); - ret.resize( - (self.return_range.end - self.return_range.start).as_usize(), - 0, - ); - ret - } else if self.return_range.end > U256::from(usize::MAX) { - let mut ret = self.memory.get( - self.return_range.start.as_usize(), - usize::MAX - self.return_range.start.as_usize(), - ); - while ret.len() < (self.return_range.end - self.return_range.start).as_usize() { - ret.push(0); - } - ret - } else { - self.memory.get( - self.return_range.start.as_usize(), - (self.return_range.end - self.return_range.start).as_usize(), - ) - } - } - - /// Loop stepping the machine, until it stops. - pub fn run(&mut self) -> Capture { - loop { - match self.step() { - Ok(()) => (), - Err(res) => return res, - } - } - } - - #[inline] - /// Step the machine, executing one opcode. It then returns. - pub fn step(&mut self) -> Result<(), Capture> { - let position = *self - .position - .as_ref() - .map_err(|reason| Capture::Exit(reason.clone()))?; - - match self.code.get(position).map(|v| Opcode(*v)) { - Some(opcode) => match eval(self, opcode, position) { - Control::Continue(p) => { - self.position = Ok(position + p); - Ok(()) - } - Control::Exit(e) => { - self.position = Err(e.clone()); - Err(Capture::Exit(e)) - } - Control::Jump(p) => { - self.position = Ok(p); - Ok(()) - } - Control::Trap(opcode) => { - #[cfg(feature = "force-debug")] - log::trace!(target: "evm", "OpCode Trap: {:?}", opcode); - - self.position = Ok(position + 1); - Err(Capture::Trap(opcode)) - } - }, - None => { - self.position = Err(ExitSucceed::Stopped.into()); - Err(Capture::Exit(ExitSucceed::Stopped.into())) - } - } - } -} diff --git a/core/src/stack.rs b/core/src/stack.rs deleted file mode 100644 index 893af517c..000000000 --- a/core/src/stack.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::ExitError; -use alloc::vec::Vec; -use primitive_types::H256; - -/// EVM stack. -#[derive(Clone, Debug)] -pub struct Stack { - data: Vec, - limit: usize, -} - -impl Stack { - /// Create a new stack with given limit. - pub fn new(limit: usize) -> Self { - Self { - data: Vec::new(), - limit, - } - } - - #[inline] - /// Stack limit. - pub fn limit(&self) -> usize { - self.limit - } - - #[inline] - /// Stack length. - pub fn len(&self) -> usize { - self.data.len() - } - - #[inline] - /// Whether the stack is empty. - pub fn is_empty(&self) -> bool { - self.data.is_empty() - } - - #[inline] - /// Stack data. - pub fn data(&self) -> &Vec { - &self.data - } - - #[inline] - /// Pop a value from the stack. If the stack is already empty, returns the - /// `StackUnderflow` error. - pub fn pop(&mut self) -> Result { - self.data.pop().ok_or(ExitError::StackUnderflow) - } - - #[inline] - /// Push a new value into the stack. If it will exceed the stack limit, - /// returns `StackOverflow` error and leaves the stack unchanged. - pub fn push(&mut self, value: H256) -> Result<(), ExitError> { - if self.data.len() + 1 > self.limit { - return Err(ExitError::StackOverflow); - } - self.data.push(value); - Ok(()) - } - - #[inline] - /// Peek a value at given index for the stack, where the top of - /// the stack is at index `0`. If the index is too large, - /// `StackError::Underflow` is returned. - pub fn peek(&self, no_from_top: usize) -> Result { - if self.data.len() > no_from_top { - Ok(self.data[self.data.len() - no_from_top - 1]) - } else { - Err(ExitError::StackUnderflow) - } - } - - #[inline] - /// Set a value at given index for the stack, where the top of the - /// stack is at index `0`. If the index is too large, - /// `StackError::Underflow` is returned. - pub fn set(&mut self, no_from_top: usize, val: H256) -> Result<(), ExitError> { - if self.data.len() > no_from_top { - let len = self.data.len(); - self.data[len - no_from_top - 1] = val; - Ok(()) - } else { - Err(ExitError::StackUnderflow) - } - } -} diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml deleted file mode 100644 index ecc1ff6dc..000000000 --- a/fuzzer/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "evm-fuzzer" -version = "0.2.0-dev" -authors = ["Vincent Ulitzsch "] -edition = "2018" -description = "Fuzzer for EVM." -license = "Apache-2.0" - -[dependencies] -honggfuzz = "0.5" - -evm-core = { version = "0.41", path = "../core" } - -[[bin]] -name = "evm_fuzz" -path = "src/main.rs" diff --git a/fuzzer/README.md b/fuzzer/README.md deleted file mode 100644 index 20f7814b2..000000000 --- a/fuzzer/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Fuzzing the rust evm -This provides a simple fuzzing harness that can be used a start when fuzzing the rust evm. -The fuzzer will take a byte input from honggfuzz, split it at a fixed delimiter -and give some of it as code and some of it as data to the evm. - -# Running the fuzzer -Run the fuzzer like so: -``` -cargo hfuzz run evm_fuzz -``` -Honggfuzz saves the fuzzing queue in the directory `hfuzz_workspace/evm_fuzz/input`. -To debug the fuzzer, you can also compile the fuzzer as a normal binary via `cargo build`. -Then, to debug a certain input in the fuzzing queue, run -`./target/debug/evm_fuzz hfuzz_workspace/evm_fuzz/input/` - -# Notes -Because this fuzzer does not implement any gasometer, honggfuzz will report some timeouts. -A reasonable approach to improve the perfomance would be to extend this fuzzer with a gasometer. diff --git a/fuzzer/src/main.rs b/fuzzer/src/main.rs deleted file mode 100644 index c6d736926..000000000 --- a/fuzzer/src/main.rs +++ /dev/null @@ -1,72 +0,0 @@ -use evm_core::Machine; -use std::rc::Rc; - -fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { - return haystack - .windows(needle.len()) - .position(|window| window == needle); -} - -fn split_at_delim(sequence: &[u8], delim: &[u8]) -> (Vec, Vec) { - let mut res_vec = Vec::new(); - let mut current_pos = 0; - if let Some(index) = find_subsequence(&sequence[current_pos..], delim) { - let found_index = index + current_pos; - res_vec.push(sequence[current_pos..found_index].to_vec()); - current_pos = found_index + delim.len(); - } - if current_pos == 0 { - (sequence.to_vec(), Vec::new()) - } else { - res_vec.push(sequence[current_pos..].to_vec()); - (res_vec[0].to_owned(), res_vec[1].to_owned()) - } -} - -fn handle_data(sequence: &[u8]) { - let (code, data) = split_at_delim(sequence, vec![0xde, 0xad, 0xbe, 0xef].as_slice()); - let stack_limit = 1024; - let memory_limit = 10000; - let mut vm = Machine::new(Rc::new(code), Rc::new(data), stack_limit, memory_limit); - let res = vm.run(); - #[cfg(not(fuzzing))] - { - println!("Result: {:?}", res); - } -} - -fn main() { - #[cfg(fuzzing)] - { - use honggfuzz::fuzz; - - loop { - fuzz!(|data: &[u8]| { - handle_data(data); - }); - } - } - #[cfg(not(fuzzing))] - { - use std::env; - use std::fs; - use std::fs::File; - use std::io::Read; - let args: Vec<_> = env::args().collect(); - let md = fs::metadata(&args[1]).unwrap(); - let all_files = match md.is_dir() { - true => fs::read_dir(&args[1]) - .unwrap() - .map(|x| x.unwrap().path().to_str().unwrap().to_string()) - .collect::>(), - false => args[1..].to_vec(), - }; - for argument in all_files { - println!("Now doing file {:?}", argument); - let mut buffer: Vec = Vec::new(); - let mut f = File::open(argument).unwrap(); - f.read_to_end(&mut buffer).unwrap(); - handle_data(buffer.as_slice()); - } - } -} diff --git a/gasometer/Cargo.toml b/gasometer/Cargo.toml deleted file mode 100644 index 25f4a2b91..000000000 --- a/gasometer/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "evm-gasometer" -version = "0.41.0" -license = "Apache-2.0" -authors = ["Wei Tang ", "Parity Technologies "] -description = "Portable Ethereum Virtual Machine implementation written in pure Rust." -repository = "https://github.com/sorpaas/rust-evm" -keywords = ["no_std", "ethereum"] -edition = "2018" - -[dependencies] -environmental = { version = "1.1.2", default-features = false, optional = true } -log = { version = "0.4", optional = true } -primitive-types = { version = "0.12", default-features = false } - -evm-core = { version = "0.41", path = "../core", default-features = false } -evm-runtime = { version = "0.41", path = "../runtime", default-features = false } - -[features] -default = ["std"] -std = [ - "environmental/std", - "primitive-types/std", - "evm-core/std", - "evm-runtime/std", -] -tracing = [ - "environmental", -] - -force-debug = [ - "log", -] diff --git a/gasometer/src/consts.rs b/gasometer/src/consts.rs deleted file mode 100644 index 285a4c1ac..000000000 --- a/gasometer/src/consts.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub const G_ZERO: u64 = 0; -pub const G_BASE: u64 = 2; -pub const G_VERYLOW: u64 = 3; -pub const G_LOW: u64 = 5; -pub const G_MID: u64 = 8; -pub const G_HIGH: u64 = 10; -pub const G_JUMPDEST: u64 = 1; -pub const R_SUICIDE: i64 = 24000; -pub const G_CREATE: u64 = 32000; -pub const G_CALLVALUE: u64 = 9000; -pub const G_NEWACCOUNT: u64 = 25000; -pub const G_EXP: u64 = 10; -pub const G_MEMORY: u64 = 3; -pub const G_LOG: u64 = 375; -pub const G_LOGDATA: u64 = 8; -pub const G_LOGTOPIC: u64 = 375; -pub const G_SHA3: u64 = 30; -pub const G_SHA3WORD: u64 = 6; -pub const G_COPY: u64 = 3; -pub const G_BLOCKHASH: u64 = 20; -pub const G_CODEDEPOSIT: u64 = 200; diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs deleted file mode 100644 index 8e4e6a143..000000000 --- a/gasometer/src/lib.rs +++ /dev/null @@ -1,1127 +0,0 @@ -//! EVM gasometer. - -#![deny(warnings)] -#![forbid(unsafe_code, unused_variables)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -#[cfg(feature = "tracing")] -pub mod tracing; - -#[cfg(feature = "tracing")] -macro_rules! event { - ($x:expr) => { - use crate::tracing::Event::*; - crate::tracing::with(|listener| listener.event($x)); - }; -} -#[cfg(feature = "force-debug")] -macro_rules! log_gas { - ($self:expr, $($arg:tt)*) => (log::trace!(target: "evm", "Gasometer {} [Gas used: {}, Gas left: {}]", format_args!($($arg)*), - $self.total_used_gas(), $self.gas())); -} - -#[cfg(not(feature = "force-debug"))] -macro_rules! log_gas { - ($self:expr, $($arg:tt)*) => {}; -} - -#[cfg(not(feature = "tracing"))] -macro_rules! event { - ($x:expr) => {}; -} - -mod consts; -mod costs; -mod memory; -mod utils; - -use alloc::vec::Vec; -use core::cmp::max; -use evm_core::{ExitError, Opcode, Stack}; -use evm_runtime::{Config, Handler}; -use primitive_types::{H160, H256, U256}; - -macro_rules! try_or_fail { - ( $inner:expr, $e:expr ) => { - match $e { - Ok(value) => value, - Err(e) => { - $inner = Err(e.clone()); - return Err(e); - } - } - }; -} - -#[cfg(feature = "tracing")] -#[derive(Debug, Copy, Clone)] -pub struct Snapshot { - pub gas_limit: u64, - pub memory_gas: u64, - pub used_gas: u64, - pub refunded_gas: i64, -} - -/// EVM gasometer. -#[derive(Clone, Debug)] -pub struct Gasometer<'config> { - gas_limit: u64, - config: &'config Config, - inner: Result, ExitError>, -} - -impl<'config> Gasometer<'config> { - /// Create a new gasometer with given gas limit and config. - pub fn new(gas_limit: u64, config: &'config Config) -> Self { - Self { - gas_limit, - config, - inner: Ok(Inner { - memory_gas: 0, - used_gas: 0, - refunded_gas: 0, - config, - }), - } - } - - #[inline] - /// Returns the numerical gas cost value. - pub fn gas_cost(&self, cost: GasCost, gas: u64) -> Result { - match self.inner.as_ref() { - Ok(inner) => inner.gas_cost(cost, gas), - Err(e) => Err(e.clone()), - } - } - - #[inline] - fn inner_mut(&mut self) -> Result<&mut Inner<'config>, ExitError> { - self.inner.as_mut().map_err(|e| e.clone()) - } - - #[inline] - /// Reference of the config. - pub fn config(&self) -> &'config Config { - self.config - } - - #[inline] - /// Remaining gas. - pub fn gas(&self) -> u64 { - match self.inner.as_ref() { - Ok(inner) => self.gas_limit - inner.used_gas - inner.memory_gas, - Err(_) => 0, - } - } - - #[inline] - /// Total used gas. - pub fn total_used_gas(&self) -> u64 { - match self.inner.as_ref() { - Ok(inner) => inner.used_gas + inner.memory_gas, - Err(_) => self.gas_limit, - } - } - - #[inline] - /// Refunded gas. - pub fn refunded_gas(&self) -> i64 { - match self.inner.as_ref() { - Ok(inner) => inner.refunded_gas, - Err(_) => 0, - } - } - - /// Explicitly fail the gasometer with out of gas. Return `OutOfGas` error. - pub fn fail(&mut self) -> ExitError { - self.inner = Err(ExitError::OutOfGas); - ExitError::OutOfGas - } - - #[inline] - /// Record an explicit cost. - pub fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - event!(RecordCost { - cost, - snapshot: self.snapshot(), - }); - log_gas!(self, "Record cost {}", cost); - - let all_gas_cost = self.total_used_gas() + cost; - if self.gas_limit < all_gas_cost { - self.inner = Err(ExitError::OutOfGas); - return Err(ExitError::OutOfGas); - } - - self.inner_mut()?.used_gas += cost; - Ok(()) - } - - #[inline] - /// Record an explicit refund. - pub fn record_refund(&mut self, refund: i64) -> Result<(), ExitError> { - event!(RecordRefund { - refund, - snapshot: self.snapshot(), - }); - log_gas!(self, "Record refund -{}", refund); - - self.inner_mut()?.refunded_gas += refund; - Ok(()) - } - - #[inline] - /// Record `CREATE` code deposit. - pub fn record_deposit(&mut self, len: usize) -> Result<(), ExitError> { - let cost = len as u64 * consts::G_CODEDEPOSIT; - self.record_cost(cost) - } - - /// Record opcode gas cost. - pub fn record_dynamic_cost( - &mut self, - cost: GasCost, - memory: Option, - ) -> Result<(), ExitError> { - let gas = self.gas(); - - let memory_gas = match memory { - Some(memory) => try_or_fail!(self.inner, self.inner_mut()?.memory_gas(memory)), - None => self.inner_mut()?.memory_gas, - }; - let gas_cost = try_or_fail!(self.inner, self.inner_mut()?.gas_cost(cost, gas)); - let gas_refund = self.inner_mut()?.gas_refund(cost); - let used_gas = self.inner_mut()?.used_gas; - - event!(RecordDynamicCost { - gas_cost, - memory_gas, - gas_refund, - snapshot: self.snapshot(), - }); - - let all_gas_cost = memory_gas + used_gas + gas_cost; - if self.gas_limit < all_gas_cost { - self.inner = Err(ExitError::OutOfGas); - return Err(ExitError::OutOfGas); - } - - log_gas!( - self, - "Record dynamic cost {} - memory_gas {} - gas_refund {}", - gas_cost, - memory_gas, - gas_refund - ); - - let after_gas = self.gas_limit - all_gas_cost; - try_or_fail!(self.inner, self.inner_mut()?.extra_check(cost, after_gas)); - - self.inner_mut()?.used_gas += gas_cost; - self.inner_mut()?.memory_gas = memory_gas; - self.inner_mut()?.refunded_gas += gas_refund; - - Ok(()) - } - - #[inline] - /// Record opcode stipend. - pub fn record_stipend(&mut self, stipend: u64) -> Result<(), ExitError> { - event!(RecordStipend { - stipend, - snapshot: self.snapshot(), - }); - - self.inner_mut()?.used_gas -= stipend; - log_gas!(self, "Record stipent {}", stipend); - Ok(()) - } - - /// Record transaction cost. - pub fn record_transaction(&mut self, cost: TransactionCost) -> Result<(), ExitError> { - let gas_cost = match cost { - TransactionCost::Call { - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len, - } => { - #[deny(clippy::let_and_return)] - let cost = self.config.gas_transaction_call - + zero_data_len as u64 * self.config.gas_transaction_zero_data - + non_zero_data_len as u64 * self.config.gas_transaction_non_zero_data - + access_list_address_len as u64 * self.config.gas_access_list_address - + access_list_storage_len as u64 * self.config.gas_access_list_storage_key; - - log_gas!( - self, - "Record Call {} [gas_transaction_call: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}]", - cost, - self.config.gas_transaction_call, - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len - ); - - cost - } - TransactionCost::Create { - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len, - initcode_cost, - } => { - let mut cost = self.config.gas_transaction_create - + zero_data_len as u64 * self.config.gas_transaction_zero_data - + non_zero_data_len as u64 * self.config.gas_transaction_non_zero_data - + access_list_address_len as u64 * self.config.gas_access_list_address - + access_list_storage_len as u64 * self.config.gas_access_list_storage_key; - if self.config.max_initcode_size.is_some() { - cost += initcode_cost; - } - - log_gas!( - self, - "Record Create {} [gas_transaction_create: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, initcode_cost: {}]", - cost, - self.config.gas_transaction_create, - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len, - initcode_cost - ); - cost - } - }; - - event!(RecordTransaction { - cost: gas_cost, - snapshot: self.snapshot(), - }); - - if self.gas() < gas_cost { - self.inner = Err(ExitError::OutOfGas); - return Err(ExitError::OutOfGas); - } - - self.inner_mut()?.used_gas += gas_cost; - Ok(()) - } - - #[cfg(feature = "tracing")] - pub fn snapshot(&self) -> Option { - self.inner.as_ref().ok().map(|inner| Snapshot { - gas_limit: self.gas_limit, - memory_gas: inner.memory_gas, - used_gas: inner.used_gas, - refunded_gas: inner.refunded_gas, - }) - } -} - -/// Calculate the call transaction cost. -#[allow(clippy::naive_bytecount)] -pub fn call_transaction_cost(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { - let zero_data_len = data.iter().filter(|v| **v == 0).count(); - let non_zero_data_len = data.len() - zero_data_len; - let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); - - TransactionCost::Call { - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len, - } -} - -/// Calculate the create transaction cost. -#[allow(clippy::naive_bytecount)] -pub fn create_transaction_cost(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { - let zero_data_len = data.iter().filter(|v| **v == 0).count(); - let non_zero_data_len = data.len() - zero_data_len; - let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); - let initcode_cost = init_code_cost(data); - - TransactionCost::Create { - zero_data_len, - non_zero_data_len, - access_list_address_len, - access_list_storage_len, - initcode_cost, - } -} - -pub fn init_code_cost(data: &[u8]) -> u64 { - // As per EIP-3860: - // > We define initcode_cost(initcode) to equal INITCODE_WORD_COST * ceil(len(initcode) / 32). - // where INITCODE_WORD_COST is 2. - 2 * ((data.len() as u64 + 31) / 32) -} - -/// Counts the number of addresses and storage keys in the access list -fn count_access_list(access_list: &[(H160, Vec)]) -> (usize, usize) { - let access_list_address_len = access_list.len(); - let access_list_storage_len = access_list.iter().map(|(_, keys)| keys.len()).sum(); - - (access_list_address_len, access_list_storage_len) -} - -#[inline] -pub fn static_opcode_cost(opcode: Opcode) -> Option { - static TABLE: [Option; 256] = { - let mut table = [None; 256]; - - table[Opcode::STOP.as_usize()] = Some(consts::G_ZERO); - table[Opcode::CALLDATASIZE.as_usize()] = Some(consts::G_BASE); - table[Opcode::CODESIZE.as_usize()] = Some(consts::G_BASE); - table[Opcode::POP.as_usize()] = Some(consts::G_BASE); - table[Opcode::PC.as_usize()] = Some(consts::G_BASE); - table[Opcode::MSIZE.as_usize()] = Some(consts::G_BASE); - - table[Opcode::ADDRESS.as_usize()] = Some(consts::G_BASE); - table[Opcode::ORIGIN.as_usize()] = Some(consts::G_BASE); - table[Opcode::CALLER.as_usize()] = Some(consts::G_BASE); - table[Opcode::CALLVALUE.as_usize()] = Some(consts::G_BASE); - table[Opcode::COINBASE.as_usize()] = Some(consts::G_BASE); - table[Opcode::TIMESTAMP.as_usize()] = Some(consts::G_BASE); - table[Opcode::NUMBER.as_usize()] = Some(consts::G_BASE); - table[Opcode::DIFFICULTY.as_usize()] = Some(consts::G_BASE); - table[Opcode::GASLIMIT.as_usize()] = Some(consts::G_BASE); - table[Opcode::GASPRICE.as_usize()] = Some(consts::G_BASE); - table[Opcode::GAS.as_usize()] = Some(consts::G_BASE); - - table[Opcode::ADD.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SUB.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::NOT.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::LT.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::GT.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SLT.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SGT.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::EQ.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::ISZERO.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::AND.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::OR.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::XOR.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::BYTE.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::CALLDATALOAD.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH1.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH2.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH3.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH4.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH5.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH6.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH7.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH8.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH9.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH10.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH11.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH12.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH13.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH14.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH15.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH16.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH17.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH18.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH19.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH20.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH21.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH22.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH23.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH24.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH25.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH26.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH27.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH28.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH29.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH30.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH31.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::PUSH32.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP1.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP2.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP3.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP4.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP5.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP6.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP7.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP8.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP9.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP10.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP11.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP12.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP13.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP14.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP15.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::DUP16.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP1.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP2.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP3.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP4.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP5.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP6.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP7.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP8.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP9.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP10.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP11.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP12.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP13.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP14.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP15.as_usize()] = Some(consts::G_VERYLOW); - table[Opcode::SWAP16.as_usize()] = Some(consts::G_VERYLOW); - - table[Opcode::MUL.as_usize()] = Some(consts::G_LOW); - table[Opcode::DIV.as_usize()] = Some(consts::G_LOW); - table[Opcode::SDIV.as_usize()] = Some(consts::G_LOW); - table[Opcode::MOD.as_usize()] = Some(consts::G_LOW); - table[Opcode::SMOD.as_usize()] = Some(consts::G_LOW); - table[Opcode::SIGNEXTEND.as_usize()] = Some(consts::G_LOW); - - table[Opcode::ADDMOD.as_usize()] = Some(consts::G_MID); - table[Opcode::MULMOD.as_usize()] = Some(consts::G_MID); - table[Opcode::JUMP.as_usize()] = Some(consts::G_MID); - - table[Opcode::JUMPI.as_usize()] = Some(consts::G_HIGH); - table[Opcode::JUMPDEST.as_usize()] = Some(consts::G_JUMPDEST); - - table - }; - - TABLE[opcode.as_usize()] -} - -/// Calculate the opcode cost. -#[allow(clippy::nonminimal_bool)] -pub fn dynamic_opcode_cost( - address: H160, - opcode: Opcode, - stack: &Stack, - is_static: bool, - config: &Config, - handler: &mut H, -) -> Result<(GasCost, StorageTarget, Option), ExitError> { - let mut storage_target = StorageTarget::None; - let gas_cost = match opcode { - Opcode::RETURN => GasCost::Zero, - - Opcode::MLOAD | Opcode::MSTORE | Opcode::MSTORE8 => GasCost::VeryLow, - - Opcode::REVERT if config.has_revert => GasCost::Zero, - Opcode::REVERT => GasCost::Invalid(opcode), - - Opcode::CHAINID if config.has_chain_id => GasCost::Base, - Opcode::CHAINID => GasCost::Invalid(opcode), - - Opcode::SHL | Opcode::SHR | Opcode::SAR if config.has_bitwise_shifting => GasCost::VeryLow, - Opcode::SHL | Opcode::SHR | Opcode::SAR => GasCost::Invalid(opcode), - - Opcode::SELFBALANCE if config.has_self_balance => GasCost::Low, - Opcode::SELFBALANCE => GasCost::Invalid(opcode), - - Opcode::BASEFEE if config.has_base_fee => GasCost::Base, - Opcode::BASEFEE => GasCost::Invalid(opcode), - - Opcode::EXTCODESIZE => { - let target = stack.peek(0)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::ExtCodeSize { - target_is_cold: handler.is_cold(target, None)?, - } - } - Opcode::BALANCE => { - let target = stack.peek(0)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::Balance { - target_is_cold: handler.is_cold(target, None)?, - } - } - Opcode::BLOCKHASH => GasCost::BlockHash, - - Opcode::EXTCODEHASH if config.has_ext_code_hash => { - let target = stack.peek(0)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::ExtCodeHash { - target_is_cold: handler.is_cold(target, None)?, - } - } - Opcode::EXTCODEHASH => GasCost::Invalid(opcode), - - Opcode::CALLCODE => { - let target = stack.peek(1)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::CallCode { - value: U256::from_big_endian(&stack.peek(2)?[..]), - gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None)?, - target_exists: { - handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; - handler.exists(target) - }, - } - } - Opcode::STATICCALL => { - let target = stack.peek(1)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::StaticCall { - gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None)?, - target_exists: { - handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; - handler.exists(target) - }, - } - } - Opcode::SHA3 => GasCost::Sha3 { - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::EXTCODECOPY => { - let target = stack.peek(0)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::ExtCodeCopy { - target_is_cold: handler.is_cold(target, None)?, - len: U256::from_big_endian(&stack.peek(3)?[..]), - } - } - Opcode::CALLDATACOPY | Opcode::CODECOPY => GasCost::VeryLowCopy { - len: U256::from_big_endian(&stack.peek(2)?[..]), - }, - Opcode::EXP => GasCost::Exp { - power: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::SLOAD => { - let index = stack.peek(0)?; - storage_target = StorageTarget::Slot(address, index); - GasCost::SLoad { - target_is_cold: handler.is_cold(address, Some(index))?, - } - } - - Opcode::DELEGATECALL if config.has_delegate_call => { - let target = stack.peek(1)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::DelegateCall { - gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None)?, - target_exists: { - handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; - handler.exists(target) - }, - } - } - Opcode::DELEGATECALL => GasCost::Invalid(opcode), - - Opcode::RETURNDATASIZE if config.has_return_data => GasCost::Base, - Opcode::RETURNDATACOPY if config.has_return_data => GasCost::VeryLowCopy { - len: U256::from_big_endian(&stack.peek(2)?[..]), - }, - Opcode::RETURNDATASIZE | Opcode::RETURNDATACOPY => GasCost::Invalid(opcode), - - Opcode::SSTORE if !is_static => { - let index = stack.peek(0)?; - let value = stack.peek(1)?; - storage_target = StorageTarget::Slot(address, index); - - GasCost::SStore { - original: handler.original_storage(address, index), - current: handler.storage(address, index), - new: value, - target_is_cold: handler.is_cold(address, Some(index))?, - } - } - Opcode::LOG0 if !is_static => GasCost::Log { - n: 0, - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::LOG1 if !is_static => GasCost::Log { - n: 1, - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::LOG2 if !is_static => GasCost::Log { - n: 2, - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::LOG3 if !is_static => GasCost::Log { - n: 3, - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::LOG4 if !is_static => GasCost::Log { - n: 4, - len: U256::from_big_endian(&stack.peek(1)?[..]), - }, - Opcode::CREATE if !is_static => GasCost::Create, - Opcode::CREATE2 if !is_static && config.has_create2 => GasCost::Create2 { - len: U256::from_big_endian(&stack.peek(2)?[..]), - }, - Opcode::SUICIDE if !is_static => { - let target = stack.peek(0)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::Suicide { - value: handler.balance(address), - target_is_cold: handler.is_cold(target, None)?, - target_exists: { - handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; - handler.exists(target) - }, - already_removed: handler.deleted(address), - } - } - Opcode::CALL - if !is_static - || (is_static && U256::from_big_endian(&stack.peek(2)?[..]) == U256::zero()) => - { - let target = stack.peek(1)?.into(); - storage_target = StorageTarget::Address(target); - GasCost::Call { - value: U256::from_big_endian(&stack.peek(2)?[..]), - gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None)?, - target_exists: { - handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; - handler.exists(target) - }, - } - } - - Opcode::PUSH0 if config.has_push0 => GasCost::Base, - - _ => GasCost::Invalid(opcode), - }; - - let memory_cost = match opcode { - Opcode::SHA3 - | Opcode::RETURN - | Opcode::REVERT - | Opcode::LOG0 - | Opcode::LOG1 - | Opcode::LOG2 - | Opcode::LOG3 - | Opcode::LOG4 => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(0)?[..]), - len: U256::from_big_endian(&stack.peek(1)?[..]), - }), - - Opcode::CODECOPY | Opcode::CALLDATACOPY | Opcode::RETURNDATACOPY => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(0)?[..]), - len: U256::from_big_endian(&stack.peek(2)?[..]), - }), - - Opcode::EXTCODECOPY => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(1)?[..]), - len: U256::from_big_endian(&stack.peek(3)?[..]), - }), - - Opcode::MLOAD | Opcode::MSTORE => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(0)?[..]), - len: U256::from(32), - }), - - Opcode::MSTORE8 => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(0)?[..]), - len: U256::from(1), - }), - - Opcode::CREATE | Opcode::CREATE2 => Some(MemoryCost { - offset: U256::from_big_endian(&stack.peek(1)?[..]), - len: U256::from_big_endian(&stack.peek(2)?[..]), - }), - - Opcode::CALL | Opcode::CALLCODE => Some( - MemoryCost { - offset: U256::from_big_endian(&stack.peek(3)?[..]), - len: U256::from_big_endian(&stack.peek(4)?[..]), - } - .join(MemoryCost { - offset: U256::from_big_endian(&stack.peek(5)?[..]), - len: U256::from_big_endian(&stack.peek(6)?[..]), - }), - ), - - Opcode::DELEGATECALL | Opcode::STATICCALL => Some( - MemoryCost { - offset: U256::from_big_endian(&stack.peek(2)?[..]), - len: U256::from_big_endian(&stack.peek(3)?[..]), - } - .join(MemoryCost { - offset: U256::from_big_endian(&stack.peek(4)?[..]), - len: U256::from_big_endian(&stack.peek(5)?[..]), - }), - ), - - _ => None, - }; - - Ok((gas_cost, storage_target, memory_cost)) -} - -/// Holds the gas consumption for a Gasometer instance. -#[derive(Clone, Debug)] -struct Inner<'config> { - memory_gas: u64, - used_gas: u64, - refunded_gas: i64, - config: &'config Config, -} - -impl<'config> Inner<'config> { - fn memory_gas(&self, memory: MemoryCost) -> Result { - let from = memory.offset; - let len = memory.len; - - if len == U256::zero() { - return Ok(self.memory_gas); - } - - let end = from.checked_add(len).ok_or(ExitError::OutOfGas)?; - - if end > U256::from(usize::MAX) { - return Err(ExitError::OutOfGas); - } - let end = end.as_usize(); - - let rem = end % 32; - let new = if rem == 0 { end / 32 } else { end / 32 + 1 }; - - Ok(max(self.memory_gas, memory::memory_gas(new)?)) - } - - fn extra_check(&self, cost: GasCost, after_gas: u64) -> Result<(), ExitError> { - match cost { - GasCost::Call { gas, .. } => costs::call_extra_check(gas, after_gas, self.config), - GasCost::CallCode { gas, .. } => costs::call_extra_check(gas, after_gas, self.config), - GasCost::DelegateCall { gas, .. } => { - costs::call_extra_check(gas, after_gas, self.config) - } - GasCost::StaticCall { gas, .. } => costs::call_extra_check(gas, after_gas, self.config), - _ => Ok(()), - } - } - - /// Returns the gas cost numerical value. - fn gas_cost(&self, cost: GasCost, gas: u64) -> Result { - Ok(match cost { - GasCost::Call { - value, - target_is_cold, - target_exists, - .. - } => costs::call_cost( - value, - target_is_cold, - true, - true, - !target_exists, - self.config, - ), - GasCost::CallCode { - value, - target_is_cold, - target_exists, - .. - } => costs::call_cost( - value, - target_is_cold, - true, - false, - !target_exists, - self.config, - ), - GasCost::DelegateCall { - target_is_cold, - target_exists, - .. - } => costs::call_cost( - U256::zero(), - target_is_cold, - false, - false, - !target_exists, - self.config, - ), - GasCost::StaticCall { - target_is_cold, - target_exists, - .. - } => costs::call_cost( - U256::zero(), - target_is_cold, - false, - true, - !target_exists, - self.config, - ), - - GasCost::Suicide { - value, - target_is_cold, - target_exists, - .. - } => costs::suicide_cost(value, target_is_cold, target_exists, self.config), - GasCost::SStore { - original, - current, - new, - target_is_cold, - } => costs::sstore_cost(original, current, new, gas, target_is_cold, self.config)?, - - GasCost::Sha3 { len } => costs::sha3_cost(len)?, - GasCost::Log { n, len } => costs::log_cost(n, len)?, - GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?, - GasCost::Exp { power } => costs::exp_cost(power, self.config)?, - GasCost::Create => consts::G_CREATE, - GasCost::Create2 { len } => costs::create2_cost(len)?, - GasCost::SLoad { target_is_cold } => costs::sload_cost(target_is_cold, self.config), - - GasCost::Zero => consts::G_ZERO, - GasCost::Base => consts::G_BASE, - GasCost::VeryLow => consts::G_VERYLOW, - GasCost::Low => consts::G_LOW, - GasCost::Invalid(opcode) => return Err(ExitError::InvalidCode(opcode)), - - GasCost::ExtCodeSize { target_is_cold } => { - costs::address_access_cost(target_is_cold, self.config.gas_ext_code, self.config) - } - GasCost::ExtCodeCopy { - target_is_cold, - len, - } => costs::extcodecopy_cost(len, target_is_cold, self.config)?, - GasCost::Balance { target_is_cold } => { - costs::address_access_cost(target_is_cold, self.config.gas_balance, self.config) - } - GasCost::BlockHash => consts::G_BLOCKHASH, - GasCost::ExtCodeHash { target_is_cold } => costs::address_access_cost( - target_is_cold, - self.config.gas_ext_code_hash, - self.config, - ), - }) - } - - fn gas_refund(&self, cost: GasCost) -> i64 { - match cost { - _ if self.config.estimate => 0, - - GasCost::SStore { - original, - current, - new, - .. - } => costs::sstore_refund(original, current, new, self.config), - GasCost::Suicide { - already_removed, .. - } if !self.config.decrease_clears_refund => costs::suicide_refund(already_removed), - _ => 0, - } - } -} - -/// Gas cost. -#[derive(Debug, Clone, Copy)] -pub enum GasCost { - /// Zero gas cost. - Zero, - /// Base gas cost. - Base, - /// Very low gas cost. - VeryLow, - /// Low gas cost. - Low, - /// Fail the gasometer. - Invalid(Opcode), - - /// Gas cost for `EXTCODESIZE`. - ExtCodeSize { - /// True if address has not been previously accessed in this transaction - target_is_cold: bool, - }, - /// Gas cost for `BALANCE`. - Balance { - /// True if address has not been previously accessed in this transaction - target_is_cold: bool, - }, - /// Gas cost for `BLOCKHASH`. - BlockHash, - /// Gas cost for `EXTBLOCKHASH`. - ExtCodeHash { - /// True if address has not been previously accessed in this transaction - target_is_cold: bool, - }, - - /// Gas cost for `CALL`. - Call { - /// Call value. - value: U256, - /// Call gas. - gas: U256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Whether the target exists. - target_exists: bool, - }, - /// Gas cost for `CALLCODE. - CallCode { - /// Call value. - value: U256, - /// Call gas. - gas: U256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Whether the target exists. - target_exists: bool, - }, - /// Gas cost for `DELEGATECALL`. - DelegateCall { - /// Call gas. - gas: U256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Whether the target exists. - target_exists: bool, - }, - /// Gas cost for `STATICCALL`. - StaticCall { - /// Call gas. - gas: U256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Whether the target exists. - target_exists: bool, - }, - /// Gas cost for `SUICIDE`. - Suicide { - /// Value. - value: U256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Whether the target exists. - target_exists: bool, - /// Whether the target has already been removed. - already_removed: bool, - }, - /// Gas cost for `SSTORE`. - SStore { - /// Original value. - original: H256, - /// Current value. - current: H256, - /// New value. - new: H256, - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - }, - /// Gas cost for `SHA3`. - Sha3 { - /// Length of the data. - len: U256, - }, - /// Gas cost for `LOG`. - Log { - /// Topic length. - n: u8, - /// Data length. - len: U256, - }, - /// Gas cost for `EXTCODECOPY`. - ExtCodeCopy { - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - /// Length. - len: U256, - }, - /// Gas cost for some copy opcodes that is documented as `VERYLOW`. - VeryLowCopy { - /// Length. - len: U256, - }, - /// Gas cost for `EXP`. - Exp { - /// Power of `EXP`. - power: U256, - }, - /// Gas cost for `CREATE`. - Create, - /// Gas cost for `CREATE2`. - Create2 { - /// Length. - len: U256, - }, - /// Gas cost for `SLOAD`. - SLoad { - /// True if target has not been previously accessed in this transaction - target_is_cold: bool, - }, -} - -/// Storage opcode will access. Used for tracking accessed storage (EIP-2929). -#[derive(Debug, Clone, Copy)] -pub enum StorageTarget { - /// No storage access - None, - /// Accessing address - Address(H160), - /// Accessing storage slot within an address - Slot(H160, H256), -} - -/// Memory cost. -#[derive(Debug, Clone, Copy)] -pub struct MemoryCost { - /// Affected memory offset. - pub offset: U256, - /// Affected length. - pub len: U256, -} - -/// Transaction cost. -#[derive(Debug, Clone, Copy)] -pub enum TransactionCost { - /// Call transaction cost. - Call { - /// Length of zeros in transaction data. - zero_data_len: usize, - /// Length of non-zeros in transaction data. - non_zero_data_len: usize, - /// Number of addresses in transaction access list (see EIP-2930) - access_list_address_len: usize, - /// Total number of storage keys in transaction access list (see EIP-2930) - access_list_storage_len: usize, - }, - /// Create transaction cost. - Create { - /// Length of zeros in transaction data. - zero_data_len: usize, - /// Length of non-zeros in transaction data. - non_zero_data_len: usize, - /// Number of addresses in transaction access list (see EIP-2930) - access_list_address_len: usize, - /// Total number of storage keys in transaction access list (see EIP-2930) - access_list_storage_len: usize, - /// Cost of initcode = 2 * ceil(len(initcode) / 32) (see EIP-3860) - initcode_cost: u64, - }, -} - -impl MemoryCost { - /// Join two memory cost together. - pub fn join(self, other: MemoryCost) -> MemoryCost { - if self.len == U256::zero() { - return other; - } - - if other.len == U256::zero() { - return self; - } - - let self_end = self.offset.saturating_add(self.len); - let other_end = other.offset.saturating_add(other.len); - - if self_end >= other_end { - self - } else { - other - } - } -} diff --git a/gasometer/src/memory.rs b/gasometer/src/memory.rs deleted file mode 100644 index a875b250b..000000000 --- a/gasometer/src/memory.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::consts::*; -use evm_core::ExitError; - -pub fn memory_gas(a: usize) -> Result { - let a = a as u64; - G_MEMORY - .checked_mul(a) - .ok_or(ExitError::OutOfGas)? - .checked_add(a.checked_mul(a).ok_or(ExitError::OutOfGas)? / 512) - .ok_or(ExitError::OutOfGas) -} diff --git a/gasometer/src/tracing.rs b/gasometer/src/tracing.rs deleted file mode 100644 index 214375b2f..000000000 --- a/gasometer/src/tracing.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Allows to listen to gasometer events. - -use super::Snapshot; - -environmental::environmental!(listener: dyn EventListener + 'static); - -pub trait EventListener { - fn event(&mut self, event: Event); -} - -impl Snapshot { - pub fn gas(&self) -> u64 { - self.gas_limit - self.used_gas - self.memory_gas - } -} - -#[derive(Debug, Copy, Clone)] -pub enum Event { - RecordCost { - cost: u64, - snapshot: Option, - }, - RecordRefund { - refund: i64, - snapshot: Option, - }, - RecordStipend { - stipend: u64, - snapshot: Option, - }, - RecordDynamicCost { - gas_cost: u64, - memory_gas: u64, - gas_refund: i64, - snapshot: Option, - }, - RecordTransaction { - cost: u64, - snapshot: Option, - }, -} - -// Expose `listener::with` to the crate only. -pub(crate) fn with(f: F) { - listener::with(f); -} - -/// Run closure with provided listener. -pub fn using R>(new: &mut (dyn EventListener + 'static), f: F) -> R { - listener::using(new, f) -} diff --git a/core/Cargo.toml b/interpreter/Cargo.toml similarity index 74% rename from core/Cargo.toml rename to interpreter/Cargo.toml index 0dbe05683..84ba097b4 100644 --- a/core/Cargo.toml +++ b/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "evm-core" -version = "0.41.0" +name = "evm-interpreter" +version = "0.2.0-dev" license = "Apache-2.0" authors = ["Wei Tang ", "Parity Technologies "] description = "Portable Ethereum Virtual Machine implementation written in pure Rust." @@ -10,10 +10,14 @@ edition = "2018" [dependencies] log = { version = "0.4", optional = true } -primitive-types = { version = "0.12", default-features = false } +primitive-types = { version = "0.12", default-features = false, features = ["rlp"] } scale-codec = { package = "parity-scale-codec", version = "3.2", default-features = false, features = ["derive", "full"], optional = true } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } +bytes = { version = "1.5", default-features = false } +auto_impl = "1.0" +sha3 = { version = "0.10", default-features = false } +rlp = { version = "0.5", default-features = false } [dev-dependencies] hex = "0.4" @@ -25,6 +29,8 @@ std = [ "serde/std", "scale-codec/std", "scale-info/std", + "bytes/std", + "rlp/std", ] with-codec = [ "scale-codec", @@ -35,6 +41,3 @@ with-serde = [ "serde", "primitive-types/impl-serde", ] -force-debug = [ - "log", -] diff --git a/interpreter/src/call_create.rs b/interpreter/src/call_create.rs new file mode 100644 index 000000000..7fc853ae0 --- /dev/null +++ b/interpreter/src/call_create.rs @@ -0,0 +1,455 @@ +use crate::utils::{h256_to_u256, u256_to_usize}; +use crate::{ + Context, ExitError, ExitException, ExitResult, Machine, Memory, Opcode, RuntimeBackend, + RuntimeState, Transfer, +}; +use core::cmp::{max, min}; +use primitive_types::{H160, H256, U256}; +use sha3::{Digest, Keccak256}; + +/// Create scheme. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum CreateScheme { + /// Legacy create scheme of `CREATE`. + Legacy { + /// Caller of the create. + caller: H160, + }, + /// Create scheme of `CREATE2`. + Create2 { + /// Caller of the create. + caller: H160, + /// Code hash. + code_hash: H256, + /// Salt. + salt: H256, + }, +} + +impl CreateScheme { + pub fn address(&self, handler: &H) -> H160 { + match self { + CreateScheme::Create2 { + caller, + code_hash, + salt, + } => { + let mut hasher = Keccak256::new(); + hasher.update([0xff]); + hasher.update(&caller[..]); + hasher.update(&salt[..]); + hasher.update(&code_hash[..]); + H256::from_slice(hasher.finalize().as_slice()).into() + } + CreateScheme::Legacy { caller } => { + let nonce = handler.nonce(*caller); + let mut stream = rlp::RlpStream::new_list(2); + stream.append(caller); + stream.append(&nonce); + H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() + } + } + } + + pub fn caller(&self) -> H160 { + match self { + Self::Create2 { caller, .. } => *caller, + Self::Legacy { caller } => *caller, + } + } +} + +/// Call scheme. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum CallScheme { + /// `CALL` + Call, + /// `CALLCODE` + CallCode, + /// `DELEGATECALL` + DelegateCall, + /// `STATICCALL` + StaticCall, +} + +pub enum CallCreateTrapData { + Call(CallTrapData), + Create(CreateTrapData), +} + +impl CallCreateTrapData { + pub fn target_gas(&self) -> Option { + match self { + Self::Call(CallTrapData { gas, .. }) => Some(*gas), + Self::Create(_) => None, + } + } + + pub fn new_from + AsMut>( + opcode: Opcode, + machine: &mut Machine, + ) -> Result { + match opcode { + Opcode::CREATE => Ok(Self::Create(CreateTrapData::new_create_from(machine)?)), + Opcode::CREATE2 => Ok(Self::Create(CreateTrapData::new_create2_from(machine)?)), + Opcode::CALL => Ok(Self::Call(CallTrapData::new_from( + CallScheme::Call, + machine, + )?)), + Opcode::CALLCODE => Ok(Self::Call(CallTrapData::new_from( + CallScheme::CallCode, + machine, + )?)), + Opcode::DELEGATECALL => Ok(Self::Call(CallTrapData::new_from( + CallScheme::DelegateCall, + machine, + )?)), + Opcode::STATICCALL => Ok(Self::Call(CallTrapData::new_from( + CallScheme::StaticCall, + machine, + )?)), + _ => Err(ExitException::InvalidOpcode(opcode).into()), + } + } + + pub fn code(&self, handler: &H) -> Vec { + match self { + Self::Call(trap) => handler.code(trap.target), + Self::Create(trap) => trap.code.clone(), + } + } +} + +pub struct CallTrapData { + pub target: H160, + pub transfer: Option, + pub input: Vec, + pub gas: U256, + pub is_static: bool, + pub out_offset: U256, + pub out_len: U256, + pub context: Context, +} + +impl CallTrapData { + fn new_from_params + AsMut>( + scheme: CallScheme, + memory: &mut Memory, + state: &mut S, + gas: &H256, + to: &H256, + value: Option<&H256>, + in_offset: &H256, + in_len: &H256, + out_offset: &H256, + out_len: &H256, + ) -> Result<((), Self), ExitError> { + let gas = h256_to_u256(*gas); + let value = value.map(|v| h256_to_u256(*v)).unwrap_or(U256::zero()); + let in_offset = h256_to_u256(*in_offset); + let in_len = h256_to_u256(*in_len); + let out_offset = h256_to_u256(*out_offset); + let out_len = h256_to_u256(*out_len); + + let in_end = in_offset + .checked_add(in_len) + .ok_or(ExitException::InvalidRange)?; + let out_end = out_offset + .checked_add(out_len) + .ok_or(ExitException::InvalidRange)?; + + let in_offset_len = if in_len == U256::zero() { + None + } else { + Some((u256_to_usize(in_offset)?, u256_to_usize(in_len)?)) + }; + + memory.resize_end(max(in_end, out_end))?; + + let input = in_offset_len + .map(|(in_offset, in_len)| memory.get(in_offset, in_len)) + .unwrap_or(Vec::new()); + + let context = match scheme { + CallScheme::Call | CallScheme::StaticCall => Context { + address: (*to).into(), + caller: state.as_ref().context.address, + apparent_value: value, + }, + CallScheme::CallCode => Context { + address: state.as_ref().context.address, + caller: state.as_ref().context.address, + apparent_value: value, + }, + CallScheme::DelegateCall => Context { + address: state.as_ref().context.address, + caller: state.as_ref().context.caller, + apparent_value: state.as_ref().context.apparent_value, + }, + }; + + let transfer = if scheme == CallScheme::Call { + Some(Transfer { + source: state.as_ref().context.address, + target: (*to).into(), + value, + }) + } else if scheme == CallScheme::CallCode { + Some(Transfer { + source: state.as_ref().context.address, + target: state.as_ref().context.address, + value, + }) + } else { + None + }; + + state.as_mut().retbuf = Vec::new(); + + Ok(( + (), + Self { + target: (*to).into(), + transfer, + input, + gas, + is_static: scheme == CallScheme::StaticCall, + context, + out_offset, + out_len, + }, + )) + } + + pub fn new_from + AsMut>( + scheme: CallScheme, + machine: &mut Machine, + ) -> Result { + let stack = &mut machine.stack; + let memory = &mut machine.memory; + let state = &mut machine.state; + + match scheme { + CallScheme::Call | CallScheme::CallCode => stack.perform_pop7_push0( + |gas, to, value, in_offset, in_len, out_offset, out_len| { + Self::new_from_params( + scheme, + memory, + state, + gas, + to, + Some(value), + in_offset, + in_len, + out_offset, + out_len, + ) + }, + ), + CallScheme::DelegateCall | CallScheme::StaticCall => { + stack.perform_pop6_push0(|gas, to, in_offset, in_len, out_offset, out_len| { + Self::new_from_params( + scheme, memory, state, gas, to, None, in_offset, in_len, out_offset, + out_len, + ) + }) + } + } + } + + pub fn feedback + AsMut>( + self, + reason: ExitResult, + retbuf: Vec, + machine: &mut Machine, + ) -> Result<(), ExitError> { + let target_len = min(self.out_len, U256::from(retbuf.len())); + let out_offset = self.out_offset; + + let ret = machine.perform(|machine| match reason { + Ok(_) => { + match machine + .memory + .copy_large(out_offset, U256::zero(), target_len, &retbuf[..]) + { + Ok(()) => { + let mut value = H256::default(); + U256::one().to_big_endian(&mut value[..]); + machine.stack.push(value)?; + + Ok(()) + } + Err(_) => { + machine.stack.push(H256::default())?; + + Ok(()) + } + } + } + Err(ExitError::Reverted) => { + machine.stack.push(H256::default())?; + + let _ = + machine + .memory + .copy_large(out_offset, U256::zero(), target_len, &retbuf[..]); + + Ok(()) + } + Err(ExitError::Exception(_)) => { + machine.stack.push(H256::default())?; + + Ok(()) + } + Err(ExitError::Fatal(e)) => { + machine.stack.push(H256::default())?; + + Err(e.into()) + } + }); + + match ret { + Ok(()) => { + machine.state.as_mut().retbuf = retbuf; + + machine.advance(); + Ok(()) + } + Err(e) => Err(e), + } + } + + pub fn has_value(&self) -> bool { + self.transfer + .as_ref() + .map(|t| t.value != U256::zero()) + .unwrap_or(false) + } +} + +#[derive(Clone, Debug)] +pub struct CreateTrapData { + pub scheme: CreateScheme, + pub value: U256, + pub code: Vec, +} + +impl CreateTrapData { + pub fn new_create_from + AsMut>( + machine: &mut Machine, + ) -> Result { + let stack = &mut machine.stack; + let memory = &mut machine.memory; + let state = &mut machine.state; + + stack.perform_pop3_push0(|value, code_offset, code_len| { + let value = h256_to_u256(*value); + let code_offset = h256_to_u256(*code_offset); + let code_len = h256_to_u256(*code_len); + + let code_offset_len = if code_len == U256::zero() { + None + } else { + Some((u256_to_usize(code_offset)?, u256_to_usize(code_len)?)) + }; + + let code = code_offset_len + .map(|(code_offset, code_len)| memory.get(code_offset, code_len)) + .unwrap_or(Vec::new()); + + let scheme = CreateScheme::Legacy { + caller: state.as_ref().context.address, + }; + + state.as_mut().retbuf = Vec::new(); + + Ok(( + (), + Self { + scheme, + value, + code, + }, + )) + }) + } + + pub fn new_create2_from + AsMut>( + machine: &mut Machine, + ) -> Result { + let stack = &mut machine.stack; + let memory = &mut machine.memory; + let state = &mut machine.state; + + stack.perform_pop4_push0(|value, code_offset, code_len, salt| { + let value = h256_to_u256(*value); + let code_offset = h256_to_u256(*code_offset); + let code_len = h256_to_u256(*code_len); + + let code_offset_len = if code_len == U256::zero() { + None + } else { + Some((u256_to_usize(code_offset)?, u256_to_usize(code_len)?)) + }; + + let code = code_offset_len + .map(|(code_offset, code_len)| memory.get(code_offset, code_len)) + .unwrap_or(Vec::new()); + + let code_hash = H256::from_slice(Keccak256::digest(&code).as_slice()); + + let scheme = CreateScheme::Create2 { + caller: state.as_ref().context.address, + salt: *salt, + code_hash, + }; + + state.as_mut().retbuf = Vec::new(); + + Ok(( + (), + Self { + scheme, + value, + code, + }, + )) + }) + } + + pub fn feedback + AsMut>( + self, + reason: Result, + retbuf: Vec, + machine: &mut Machine, + ) -> Result<(), ExitError> { + let ret = machine.perform(|machine| match reason { + Ok(address) => { + machine.stack.push(address.into())?; + Ok(()) + } + Err(ExitError::Reverted) => { + machine.stack.push(H256::default())?; + Ok(()) + } + Err(ExitError::Exception(_)) => { + machine.stack.push(H256::default())?; + Ok(()) + } + Err(ExitError::Fatal(e)) => { + machine.stack.push(H256::default())?; + Err(e.into()) + } + }); + + match ret { + Ok(()) => { + machine.state.as_mut().retbuf = retbuf; + + machine.advance(); + Ok(()) + } + Err(e) => Err(e), + } + } +} diff --git a/core/src/error.rs b/interpreter/src/error.rs similarity index 73% rename from core/src/error.rs rename to interpreter/src/error.rs index 9c6dd34e2..cf4db0152 100644 --- a/core/src/error.rs +++ b/interpreter/src/error.rs @@ -1,9 +1,6 @@ use crate::Opcode; use alloc::borrow::Cow; -/// Trap which indicates that an `ExternalOpcode` has to be handled. -pub type Trap = Opcode; - /// Capture represents the result of execution. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Capture { @@ -14,6 +11,24 @@ pub enum Capture { Trap(T), } +impl Capture { + pub fn exit(self) -> Option { + match self { + Self::Exit(e) => Some(e), + Self::Trap(_) => None, + } + } + + pub fn trap(self) -> Option { + match self { + Self::Exit(_) => None, + Self::Trap(t) => Some(t), + } + } +} + +pub type ExitResult = Result; + /// Exit reason. #[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr( @@ -21,37 +36,37 @@ pub enum Capture { derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) )] #[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub enum ExitReason { - /// Machine has succeeded. - Succeed(ExitSucceed), +pub enum ExitError { /// Machine returns a normal EVM error. - Error(ExitError), + Exception(ExitException), /// Machine encountered an explicit revert. - Revert(ExitRevert), + Reverted, /// Machine encountered an error that is not supposed to be normal EVM /// errors, such as requiring too much memory to execute. Fatal(ExitFatal), } -impl ExitReason { - /// Whether the exit is succeeded. - pub fn is_succeed(&self) -> bool { - matches!(self, Self::Succeed(_)) - } - - /// Whether the exit is error. - pub fn is_error(&self) -> bool { - matches!(self, Self::Error(_)) +impl From for ExitResult { + fn from(s: ExitError) -> Self { + Err(s) } +} - /// Whether the exit is revert. - pub fn is_revert(&self) -> bool { - matches!(self, Self::Revert(_)) +#[cfg(feature = "std")] +impl std::error::Error for ExitError { + fn description(&self) -> &str { + match self { + Self::Exception(_) => "EVM exit exception", + Self::Reverted => "EVM internal revert", + Self::Fatal(_) => "EVM fatal error", + } } +} - /// Whether the exit is fatal. - pub fn is_fatal(&self) -> bool { - matches!(self, Self::Fatal(_)) +#[cfg(feature = "std")] +impl std::fmt::Display for ExitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) } } @@ -71,27 +86,9 @@ pub enum ExitSucceed { Suicided, } -impl From for ExitReason { +impl From for ExitResult { fn from(s: ExitSucceed) -> Self { - Self::Succeed(s) - } -} - -/// Exit revert reason. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr( - feature = "with-codec", - derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) -)] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub enum ExitRevert { - /// Machine encountered an explicit revert. - Reverted, -} - -impl From for ExitReason { - fn from(s: ExitRevert) -> Self { - Self::Revert(s) + Ok(s) } } @@ -102,7 +99,7 @@ impl From for ExitReason { derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) )] #[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub enum ExitError { +pub enum ExitException { /// Trying to pop from an empty stack. #[cfg_attr(feature = "with-codec", codec(index = 0))] StackUnderflow, @@ -128,9 +125,9 @@ pub enum ExitError { #[cfg_attr(feature = "with-codec", codec(index = 7))] CreateContractLimit, - /// Invalid opcode during execution or starting byte is 0xef. See [EIP-3541](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3541.md). + /// Invalid opcode during execution or starting byte is 0xef ([EIP-3541](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3541.md)). #[cfg_attr(feature = "with-codec", codec(index = 15))] - InvalidCode(Opcode), + InvalidOpcode(Opcode), /// An opcode accesses external information, but the request is off offset /// limit (runtime). @@ -152,19 +149,25 @@ pub enum ExitError { #[cfg_attr(feature = "with-codec", codec(index = 12))] CreateEmpty, - /// Other normal errors. - #[cfg_attr(feature = "with-codec", codec(index = 13))] - Other(Cow<'static, str>), - /// Nonce reached maximum value of 2^64-1 /// https://eips.ethereum.org/EIPS/eip-2681 #[cfg_attr(feature = "with-codec", codec(index = 14))] MaxNonce, + + /// Other normal errors. + #[cfg_attr(feature = "with-codec", codec(index = 13))] + Other(Cow<'static, str>), } -impl From for ExitReason { - fn from(s: ExitError) -> Self { - Self::Error(s) +impl From for ExitResult { + fn from(s: ExitException) -> Self { + Err(ExitError::Exception(s)) + } +} + +impl From for ExitError { + fn from(s: ExitException) -> Self { + ExitError::Exception(s) } } @@ -181,14 +184,24 @@ pub enum ExitFatal { /// The trap (interrupt) is unhandled. UnhandledInterrupt, /// The environment explicitly set call errors as fatal error. - CallErrorAsFatal(ExitError), + ExceptionAsFatal(ExitException), + /// Already exited. + AlreadyExited, + /// Unfinished execution. + Unfinished, /// Other fatal errors. Other(Cow<'static, str>), } -impl From for ExitReason { +impl From for ExitResult { + fn from(s: ExitFatal) -> Self { + Err(ExitError::Fatal(s)) + } +} + +impl From for ExitError { fn from(s: ExitFatal) -> Self { - Self::Fatal(s) + ExitError::Fatal(s) } } diff --git a/core/src/eval/arithmetic.rs b/interpreter/src/eval/arithmetic.rs similarity index 100% rename from core/src/eval/arithmetic.rs rename to interpreter/src/eval/arithmetic.rs diff --git a/core/src/eval/bitwise.rs b/interpreter/src/eval/bitwise.rs similarity index 100% rename from core/src/eval/bitwise.rs rename to interpreter/src/eval/bitwise.rs diff --git a/core/src/eval/macros.rs b/interpreter/src/eval/macros.rs similarity index 66% rename from core/src/eval/macros.rs rename to interpreter/src/eval/macros.rs index 1e1533a93..ded57e6db 100644 --- a/core/src/eval/macros.rs +++ b/interpreter/src/eval/macros.rs @@ -1,15 +1,5 @@ -#[cfg(feature = "force-debug")] -macro_rules! trace_op { - ($($arg:tt)*) => (log::trace!(target: "evm", "OpCode {}", format_args!($($arg)*))); -} - -#[cfg(not(feature = "force-debug"))] -macro_rules! trace_op { - ($($arg:tt)*) => {}; -} - macro_rules! try_or_fail { - ( $e:expr ) => { + ($e:expr) => { match $e { Ok(v) => v, Err(e) => return Control::Exit(e.into()), @@ -64,73 +54,67 @@ macro_rules! push_u256 { } macro_rules! op1_u256_fn { - ( $machine:expr, $op:path ) => {{ + ($machine:expr, $op:path) => {{ pop_u256!($machine, op1); let ret = $op(op1); push_u256!($machine, ret); - trace_op!("{} {}: {}", stringify!($op), op1, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! op2_u256_bool_ref { - ( $machine:expr, $op:ident ) => {{ + ($machine:expr, $op:ident) => {{ pop_u256!($machine, op1, op2); let ret = op1.$op(&op2); push_u256!($machine, if ret { U256::one() } else { U256::zero() }); - trace_op!("{} {}, {}: {}", stringify!($op), op1, op2, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! op2_u256 { - ( $machine:expr, $op:ident ) => {{ + ($machine:expr, $op:ident) => {{ pop_u256!($machine, op1, op2); let ret = op1.$op(op2); push_u256!($machine, ret); - trace_op!("{} {}, {}: {}", stringify!($op), op1, op2, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! op2_u256_tuple { - ( $machine:expr, $op:ident ) => {{ + ($machine:expr, $op:ident) => {{ pop_u256!($machine, op1, op2); let (ret, ..) = op1.$op(op2); push_u256!($machine, ret); - trace_op!("{} {}, {}: {}", stringify!($op), op1, op2, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! op2_u256_fn { - ( $machine:expr, $op:path ) => {{ + ($machine:expr, $op:path) => {{ pop_u256!($machine, op1, op2); let ret = $op(op1, op2); push_u256!($machine, ret); - trace_op!("{} {}, {}: {}", stringify!($op), op1, op2, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! op3_u256_fn { - ( $machine:expr, $op:path ) => {{ + ($machine:expr, $op:path) => {{ pop_u256!($machine, op1, op2, op3); let ret = $op(op1, op2, op3); push_u256!($machine, ret); - trace_op!("{} {}, {}, {}: {}", stringify!($op), op1, op2, op3, ret); - Control::Continue(1) + Control::Continue }}; } macro_rules! as_usize_or_fail { - ( $v:expr ) => {{ + ($v:expr) => {{ if $v > U256::from(usize::MAX) { return Control::Exit(ExitFatal::NotSupported.into()); } @@ -138,7 +122,7 @@ macro_rules! as_usize_or_fail { $v.as_usize() }}; - ( $v:expr, $reason:expr ) => {{ + ($v:expr, $reason:expr) => {{ if $v > U256::from(usize::MAX) { return Control::Exit($reason.into()); } diff --git a/core/src/eval/misc.rs b/interpreter/src/eval/misc.rs similarity index 57% rename from core/src/eval/misc.rs rename to interpreter/src/eval/misc.rs index 398bcc34c..32a642def 100644 --- a/core/src/eval/misc.rs +++ b/interpreter/src/eval/misc.rs @@ -1,33 +1,39 @@ use super::Control; -use crate::{ExitError, ExitFatal, ExitRevert, ExitSucceed, Machine}; +use crate::utils::u256_to_h256; +use crate::{ExitError, ExitException, ExitFatal, ExitSucceed, Machine}; use core::cmp::min; use primitive_types::{H256, U256}; #[inline] -pub fn codesize(state: &mut Machine) -> Control { - let size = U256::from(state.code.len()); - trace_op!("CodeSize: {}", size); - push_u256!(state, size); - Control::Continue(1) +pub fn codesize(state: &mut Machine) -> Control { + let stack = &mut state.stack; + let code = &state.code; + + match stack.perform_pop0_push1(|| { + let size = U256::from(code.len()); + Ok((u256_to_h256(size), ())) + }) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(Err(e)), + } } #[inline] -pub fn codecopy(state: &mut Machine) -> Control { +pub fn codecopy(state: &mut Machine) -> Control { pop_u256!(state, memory_offset, code_offset, len); - trace_op!("CodeCopy: {}", len); try_or_fail!(state.memory.resize_offset(memory_offset, len)); match state .memory .copy_large(memory_offset, code_offset, len, &state.code) { - Ok(()) => Control::Continue(1), + Ok(()) => Control::Continue, Err(e) => Control::Exit(e.into()), } } #[inline] -pub fn calldataload(state: &mut Machine) -> Control { +pub fn calldataload(state: &mut Machine) -> Control { pop_u256!(state, index); let mut load = [0u8; 32]; @@ -44,126 +50,117 @@ pub fn calldataload(state: &mut Machine) -> Control { } push!(state, H256::from(load)); - Control::Continue(1) + Control::Continue } #[inline] -pub fn calldatasize(state: &mut Machine) -> Control { +pub fn calldatasize(state: &mut Machine) -> Control { let len = U256::from(state.data.len()); - trace_op!("CallDataSize: {}", len); push_u256!(state, len); - Control::Continue(1) + Control::Continue } #[inline] -pub fn calldatacopy(state: &mut Machine) -> Control { +pub fn calldatacopy(state: &mut Machine) -> Control { pop_u256!(state, memory_offset, data_offset, len); - trace_op!("CallDataCopy: {}", len); try_or_fail!(state.memory.resize_offset(memory_offset, len)); if len == U256::zero() { - return Control::Continue(1); + return Control::Continue; } match state .memory .copy_large(memory_offset, data_offset, len, &state.data) { - Ok(()) => Control::Continue(1), + Ok(()) => Control::Continue, Err(e) => Control::Exit(e.into()), } } #[inline] -pub fn pop(state: &mut Machine) -> Control { +pub fn pop(state: &mut Machine) -> Control { pop!(state, _val); - trace_op!("Pop [@{}]: {}", state.stack.len(), _val); - Control::Continue(1) + Control::Continue } #[inline] -pub fn mload(state: &mut Machine) -> Control { +pub fn mload(state: &mut Machine) -> Control { pop_u256!(state, index); - trace_op!("MLoad: {}", index); try_or_fail!(state.memory.resize_offset(index, U256::from(32))); let index = as_usize_or_fail!(index); let value = H256::from_slice(&state.memory.get(index, 32)[..]); push!(state, value); - Control::Continue(1) + Control::Continue } #[inline] -pub fn mstore(state: &mut Machine) -> Control { +pub fn mstore(state: &mut Machine) -> Control { pop_u256!(state, index); pop!(state, value); - trace_op!("MStore: {}, {}", index, value); try_or_fail!(state.memory.resize_offset(index, U256::from(32))); let index = as_usize_or_fail!(index); match state.memory.set(index, &value[..], Some(32)) { - Ok(()) => Control::Continue(1), + Ok(()) => Control::Continue, Err(e) => Control::Exit(e.into()), } } #[inline] -pub fn mstore8(state: &mut Machine) -> Control { +pub fn mstore8(state: &mut Machine) -> Control { pop_u256!(state, index, value); try_or_fail!(state.memory.resize_offset(index, U256::one())); let index = as_usize_or_fail!(index); let value = (value.low_u32() & 0xff) as u8; match state.memory.set(index, &[value], Some(1)) { - Ok(()) => Control::Continue(1), + Ok(()) => Control::Continue, Err(e) => Control::Exit(e.into()), } } #[inline] -pub fn jump(state: &mut Machine) -> Control { +pub fn jump(state: &mut Machine) -> Control { pop_u256!(state, dest); - let dest = as_usize_or_fail!(dest, ExitError::InvalidJump); - trace_op!("Jump: {}", dest); + let dest = as_usize_or_fail!(dest, ExitException::InvalidJump); if state.valids.is_valid(dest) { Control::Jump(dest) } else { - Control::Exit(ExitError::InvalidJump.into()) + Control::Exit(ExitException::InvalidJump.into()) } } #[inline] -pub fn jumpi(state: &mut Machine) -> Control { +pub fn jumpi(state: &mut Machine) -> Control { pop_u256!(state, dest); pop!(state, value); if value != H256::zero() { - trace_op!("JumpI: {}", dest); - let dest = as_usize_or_fail!(dest, ExitError::InvalidJump); + let dest = as_usize_or_fail!(dest, ExitException::InvalidJump); if state.valids.is_valid(dest) { Control::Jump(dest) } else { - Control::Exit(ExitError::InvalidJump.into()) + Control::Exit(ExitException::InvalidJump.into()) } } else { - trace_op!("JumpI: skipped"); - Control::Continue(1) + Control::Continue } } #[inline] -pub fn pc(state: &mut Machine, position: usize) -> Control { - trace_op!("PC"); +pub fn pc(state: &mut Machine, position: usize) -> Control { push_u256!(state, U256::from(position)); - Control::Continue(1) + Control::Continue } #[inline] -pub fn msize(state: &mut Machine) -> Control { +pub fn msize(state: &mut Machine) -> Control { push_u256!(state, state.memory.effective_len()); - Control::Continue(1) + Control::Continue } #[inline] -pub fn push(state: &mut Machine, n: usize, position: usize) -> Control { +pub fn push(state: &mut Machine, n: usize, position: usize) -> Control { let end = min(position + 1 + n, state.code.len()); let slice = &state.code[(position + 1)..end]; let mut val = [0u8; 32]; @@ -171,23 +168,21 @@ pub fn push(state: &mut Machine, n: usize, position: usize) -> Control { let result = H256(val); push!(state, result); - trace_op!("Push [@{}]: {}", state.stack.len() - 1, result); - Control::Continue(1 + n) + Control::ContinueN(1 + n) } #[inline] -pub fn dup(state: &mut Machine, n: usize) -> Control { +pub fn dup(state: &mut Machine, n: usize) -> Control { let value = match state.stack.peek(n - 1) { Ok(value) => value, Err(e) => return Control::Exit(e.into()), }; - trace_op!("Dup{} [@{}]: {}", n, state.stack.len(), value); push!(state, value); - Control::Continue(1) + Control::Continue } #[inline] -pub fn swap(state: &mut Machine, n: usize) -> Control { +pub fn swap(state: &mut Machine, n: usize) -> Control { let val1 = match state.stack.peek(0) { Ok(value) => value, Err(e) => return Control::Exit(e.into()), @@ -204,24 +199,23 @@ pub fn swap(state: &mut Machine, n: usize) -> Control { Ok(()) => (), Err(e) => return Control::Exit(e.into()), } - trace_op!("Swap [@0:@{}]: {}, {}", n, val1, val2); - Control::Continue(1) + Control::Continue } #[inline] -pub fn ret(state: &mut Machine) -> Control { - trace_op!("Return"); +pub fn ret(state: &mut Machine) -> Control { pop_u256!(state, start, len); try_or_fail!(state.memory.resize_offset(start, len)); - state.return_range = start..(start + len); + state.memory.resize_to_range(start..(start + len)); + state.memory.swap_and_clear(&mut state.retval); Control::Exit(ExitSucceed::Returned.into()) } #[inline] -pub fn revert(state: &mut Machine) -> Control { - trace_op!("Revert"); +pub fn revert(state: &mut Machine) -> Control { pop_u256!(state, start, len); try_or_fail!(state.memory.resize_offset(start, len)); - state.return_range = start..(start + len); - Control::Exit(ExitRevert::Reverted.into()) + state.memory.resize_to_range(start..(start + len)); + state.memory.swap_and_clear(&mut state.retval); + Control::Exit(ExitError::Reverted.into()) } diff --git a/interpreter/src/eval/mod.rs b/interpreter/src/eval/mod.rs new file mode 100644 index 000000000..57ebbcd22 --- /dev/null +++ b/interpreter/src/eval/mod.rs @@ -0,0 +1,1502 @@ +#[macro_use] +mod macros; +mod arithmetic; +mod bitwise; +mod misc; +mod system; + +use crate::{ + CallCreateTrap, ExitException, ExitResult, ExitSucceed, Machine, Opcode, RuntimeBackend, + RuntimeEnvironment, RuntimeState, +}; +use core::marker::PhantomData; +use core::ops::{BitAnd, BitOr, BitXor, Deref, DerefMut}; +use primitive_types::{H256, U256}; + +/// Evaluation function type. +pub type Efn = fn(&mut Machine, &mut H, Opcode, usize) -> Control; + +/// The evaluation table for the EVM. +pub struct Etable>([F; 256], PhantomData<(S, H, Tr)>); + +unsafe impl Send for Etable {} +unsafe impl Sync for Etable {} + +impl Deref for Etable { + type Target = [F; 256]; + + fn deref(&self) -> &[F; 256] { + &self.0 + } +} + +impl DerefMut for Etable { + fn deref_mut(&mut self) -> &mut [F; 256] { + &mut self.0 + } +} + +impl Etable +where + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, +{ + /// Wrap to create a new Etable. + pub fn wrap(self, wrapper: FW) -> Etable + where + FW: Fn(F, Opcode) -> FR, + FR: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, + { + let mut current_opcode = Opcode(0); + Etable( + self.0.map(|f| { + let fr = wrapper(f, current_opcode); + if current_opcode != Opcode(255) { + current_opcode.0 = current_opcode.0 + 1; + } + fr + }), + PhantomData, + ) + } +} + +impl Etable { + /// Default core value for Etable. + pub const fn core() -> Etable { + let mut table = [eval_unknown as _; 256]; + + table[Opcode::STOP.as_usize()] = eval_stop as _; + table[Opcode::ADD.as_usize()] = eval_add as _; + table[Opcode::MUL.as_usize()] = eval_mul as _; + table[Opcode::SUB.as_usize()] = eval_sub as _; + table[Opcode::DIV.as_usize()] = eval_div as _; + table[Opcode::SDIV.as_usize()] = eval_sdiv as _; + table[Opcode::MOD.as_usize()] = eval_mod as _; + table[Opcode::SMOD.as_usize()] = eval_smod as _; + table[Opcode::ADDMOD.as_usize()] = eval_addmod as _; + table[Opcode::MULMOD.as_usize()] = eval_mulmod as _; + table[Opcode::EXP.as_usize()] = eval_exp as _; + table[Opcode::SIGNEXTEND.as_usize()] = eval_signextend as _; + table[Opcode::LT.as_usize()] = eval_lt as _; + table[Opcode::GT.as_usize()] = eval_gt as _; + table[Opcode::SLT.as_usize()] = eval_slt as _; + table[Opcode::SGT.as_usize()] = eval_sgt as _; + table[Opcode::EQ.as_usize()] = eval_eq as _; + table[Opcode::ISZERO.as_usize()] = eval_iszero as _; + table[Opcode::AND.as_usize()] = eval_and as _; + table[Opcode::OR.as_usize()] = eval_or as _; + table[Opcode::XOR.as_usize()] = eval_xor as _; + table[Opcode::NOT.as_usize()] = eval_not as _; + table[Opcode::BYTE.as_usize()] = eval_byte as _; + table[Opcode::SHL.as_usize()] = eval_shl as _; + table[Opcode::SHR.as_usize()] = eval_shr as _; + table[Opcode::SAR.as_usize()] = eval_sar as _; + table[Opcode::CODESIZE.as_usize()] = eval_codesize as _; + table[Opcode::CODECOPY.as_usize()] = eval_codecopy as _; + table[Opcode::CALLDATALOAD.as_usize()] = eval_calldataload as _; + table[Opcode::CALLDATASIZE.as_usize()] = eval_calldatasize as _; + table[Opcode::CALLDATACOPY.as_usize()] = eval_calldatacopy as _; + table[Opcode::POP.as_usize()] = eval_pop as _; + table[Opcode::MLOAD.as_usize()] = eval_mload as _; + table[Opcode::MSTORE.as_usize()] = eval_mstore as _; + table[Opcode::MSTORE8.as_usize()] = eval_mstore8 as _; + table[Opcode::JUMP.as_usize()] = eval_jump as _; + table[Opcode::JUMPI.as_usize()] = eval_jumpi as _; + table[Opcode::PC.as_usize()] = eval_pc as _; + table[Opcode::MSIZE.as_usize()] = eval_msize as _; + table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; + + table[Opcode::PUSH0.as_usize()] = eval_push0 as _; + table[Opcode::PUSH1.as_usize()] = eval_push1 as _; + table[Opcode::PUSH2.as_usize()] = eval_push2 as _; + table[Opcode::PUSH3.as_usize()] = eval_push3 as _; + table[Opcode::PUSH4.as_usize()] = eval_push4 as _; + table[Opcode::PUSH5.as_usize()] = eval_push5 as _; + table[Opcode::PUSH6.as_usize()] = eval_push6 as _; + table[Opcode::PUSH7.as_usize()] = eval_push7 as _; + table[Opcode::PUSH8.as_usize()] = eval_push8 as _; + table[Opcode::PUSH9.as_usize()] = eval_push9 as _; + table[Opcode::PUSH10.as_usize()] = eval_push10 as _; + table[Opcode::PUSH11.as_usize()] = eval_push11 as _; + table[Opcode::PUSH12.as_usize()] = eval_push12 as _; + table[Opcode::PUSH13.as_usize()] = eval_push13 as _; + table[Opcode::PUSH14.as_usize()] = eval_push14 as _; + table[Opcode::PUSH15.as_usize()] = eval_push15 as _; + table[Opcode::PUSH16.as_usize()] = eval_push16 as _; + table[Opcode::PUSH17.as_usize()] = eval_push17 as _; + table[Opcode::PUSH18.as_usize()] = eval_push18 as _; + table[Opcode::PUSH19.as_usize()] = eval_push19 as _; + table[Opcode::PUSH20.as_usize()] = eval_push20 as _; + table[Opcode::PUSH21.as_usize()] = eval_push21 as _; + table[Opcode::PUSH22.as_usize()] = eval_push22 as _; + table[Opcode::PUSH23.as_usize()] = eval_push23 as _; + table[Opcode::PUSH24.as_usize()] = eval_push24 as _; + table[Opcode::PUSH25.as_usize()] = eval_push25 as _; + table[Opcode::PUSH26.as_usize()] = eval_push26 as _; + table[Opcode::PUSH27.as_usize()] = eval_push27 as _; + table[Opcode::PUSH28.as_usize()] = eval_push28 as _; + table[Opcode::PUSH29.as_usize()] = eval_push29 as _; + table[Opcode::PUSH30.as_usize()] = eval_push30 as _; + table[Opcode::PUSH31.as_usize()] = eval_push31 as _; + table[Opcode::PUSH32.as_usize()] = eval_push32 as _; + + table[Opcode::DUP1.as_usize()] = eval_dup1 as _; + table[Opcode::DUP2.as_usize()] = eval_dup2 as _; + table[Opcode::DUP3.as_usize()] = eval_dup3 as _; + table[Opcode::DUP4.as_usize()] = eval_dup4 as _; + table[Opcode::DUP5.as_usize()] = eval_dup5 as _; + table[Opcode::DUP6.as_usize()] = eval_dup6 as _; + table[Opcode::DUP7.as_usize()] = eval_dup7 as _; + table[Opcode::DUP8.as_usize()] = eval_dup8 as _; + table[Opcode::DUP9.as_usize()] = eval_dup9 as _; + table[Opcode::DUP10.as_usize()] = eval_dup10 as _; + table[Opcode::DUP11.as_usize()] = eval_dup11 as _; + table[Opcode::DUP12.as_usize()] = eval_dup12 as _; + table[Opcode::DUP13.as_usize()] = eval_dup13 as _; + table[Opcode::DUP14.as_usize()] = eval_dup14 as _; + table[Opcode::DUP15.as_usize()] = eval_dup15 as _; + table[Opcode::DUP16.as_usize()] = eval_dup16 as _; + + table[Opcode::SWAP1.as_usize()] = eval_swap1 as _; + table[Opcode::SWAP2.as_usize()] = eval_swap2 as _; + table[Opcode::SWAP3.as_usize()] = eval_swap3 as _; + table[Opcode::SWAP4.as_usize()] = eval_swap4 as _; + table[Opcode::SWAP5.as_usize()] = eval_swap5 as _; + table[Opcode::SWAP6.as_usize()] = eval_swap6 as _; + table[Opcode::SWAP7.as_usize()] = eval_swap7 as _; + table[Opcode::SWAP8.as_usize()] = eval_swap8 as _; + table[Opcode::SWAP9.as_usize()] = eval_swap9 as _; + table[Opcode::SWAP10.as_usize()] = eval_swap10 as _; + table[Opcode::SWAP11.as_usize()] = eval_swap11 as _; + table[Opcode::SWAP12.as_usize()] = eval_swap12 as _; + table[Opcode::SWAP13.as_usize()] = eval_swap13 as _; + table[Opcode::SWAP14.as_usize()] = eval_swap14 as _; + table[Opcode::SWAP15.as_usize()] = eval_swap15 as _; + table[Opcode::SWAP16.as_usize()] = eval_swap16 as _; + + table[Opcode::RETURN.as_usize()] = eval_return as _; + table[Opcode::REVERT.as_usize()] = eval_revert as _; + table[Opcode::INVALID.as_usize()] = eval_invalid as _; + + Self(table, PhantomData) + } +} + +impl, H: RuntimeEnvironment + RuntimeBackend, Tr: CallCreateTrap> + Etable +{ + /// Runtime Etable. + pub const fn runtime() -> Etable { + let mut table = Self::core(); + + table.0[Opcode::SHA3.as_usize()] = eval_sha3 as _; + table.0[Opcode::ADDRESS.as_usize()] = eval_address as _; + table.0[Opcode::BALANCE.as_usize()] = eval_balance as _; + table.0[Opcode::SELFBALANCE.as_usize()] = eval_selfbalance as _; + table.0[Opcode::ORIGIN.as_usize()] = eval_origin as _; + table.0[Opcode::CALLER.as_usize()] = eval_caller as _; + table.0[Opcode::CALLVALUE.as_usize()] = eval_callvalue as _; + table.0[Opcode::GASPRICE.as_usize()] = eval_gasprice as _; + table.0[Opcode::EXTCODESIZE.as_usize()] = eval_extcodesize as _; + table.0[Opcode::EXTCODEHASH.as_usize()] = eval_extcodehash as _; + table.0[Opcode::EXTCODECOPY.as_usize()] = eval_extcodecopy as _; + table.0[Opcode::RETURNDATASIZE.as_usize()] = eval_returndatasize as _; + table.0[Opcode::RETURNDATACOPY.as_usize()] = eval_returndatacopy as _; + table.0[Opcode::BLOCKHASH.as_usize()] = eval_blockhash as _; + table.0[Opcode::COINBASE.as_usize()] = eval_coinbase as _; + table.0[Opcode::TIMESTAMP.as_usize()] = eval_timestamp as _; + table.0[Opcode::NUMBER.as_usize()] = eval_number as _; + table.0[Opcode::DIFFICULTY.as_usize()] = eval_difficulty as _; + table.0[Opcode::GASLIMIT.as_usize()] = eval_gaslimit as _; + table.0[Opcode::SLOAD.as_usize()] = eval_sload as _; + table.0[Opcode::SSTORE.as_usize()] = eval_sstore as _; + table.0[Opcode::GAS.as_usize()] = eval_gas as _; + table.0[Opcode::LOG0.as_usize()] = eval_log0 as _; + table.0[Opcode::LOG1.as_usize()] = eval_log1 as _; + table.0[Opcode::LOG2.as_usize()] = eval_log2 as _; + table.0[Opcode::LOG3.as_usize()] = eval_log3 as _; + table.0[Opcode::LOG4.as_usize()] = eval_log4 as _; + table.0[Opcode::SUICIDE.as_usize()] = eval_suicide as _; + table.0[Opcode::CHAINID.as_usize()] = eval_chainid as _; + table.0[Opcode::BASEFEE.as_usize()] = eval_basefee as _; + + table.0[Opcode::CREATE.as_usize()] = eval_call_create_trap as _; + table.0[Opcode::CREATE2.as_usize()] = eval_call_create_trap as _; + table.0[Opcode::CALL.as_usize()] = eval_call_create_trap as _; + table.0[Opcode::CALLCODE.as_usize()] = eval_call_create_trap as _; + table.0[Opcode::DELEGATECALL.as_usize()] = eval_call_create_trap as _; + table.0[Opcode::STATICCALL.as_usize()] = eval_call_create_trap as _; + + table + } +} + +/// Control state. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum Control { + Continue, + ContinueN(usize), + Exit(ExitResult), + Jump(usize), + Trap(Trap), +} + +fn eval_stop( + _machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + Control::Exit(ExitSucceed::Stopped.into()) +} + +fn eval_add( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_tuple!(machine, overflowing_add) +} + +fn eval_mul( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_tuple!(machine, overflowing_mul) +} + +fn eval_sub( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_tuple!(machine, overflowing_sub) +} + +fn eval_div( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::div) +} + +fn eval_sdiv( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::sdiv) +} + +fn eval_mod( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::rem) +} + +fn eval_smod( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::srem) +} + +fn eval_addmod( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op3_u256_fn!(machine, self::arithmetic::addmod) +} + +fn eval_mulmod( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op3_u256_fn!(machine, self::arithmetic::mulmod) +} + +fn eval_exp( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::exp) +} + +fn eval_signextend( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::arithmetic::signextend) +} + +fn eval_lt( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_bool_ref!(machine, lt) +} + +fn eval_gt( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_bool_ref!(machine, gt) +} + +fn eval_slt( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::slt) +} + +fn eval_sgt( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::sgt) +} + +fn eval_eq( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_bool_ref!(machine, eq) +} + +fn eval_iszero( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op1_u256_fn!(machine, self::bitwise::iszero) +} + +fn eval_and( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256!(machine, bitand) +} + +fn eval_or( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256!(machine, bitor) +} + +fn eval_xor( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256!(machine, bitxor) +} + +fn eval_not( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op1_u256_fn!(machine, self::bitwise::not) +} + +fn eval_byte( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::byte) +} + +fn eval_shl( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::shl) +} + +fn eval_shr( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::shr) +} + +fn eval_sar( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + op2_u256_fn!(machine, self::bitwise::sar) +} + +fn eval_codesize( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::codesize(machine) +} + +fn eval_codecopy( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::codecopy(machine) +} + +fn eval_calldataload( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::calldataload(machine) +} + +fn eval_calldatasize( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::calldatasize(machine) +} + +fn eval_calldatacopy( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::calldatacopy(machine) +} + +fn eval_pop( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::pop(machine) +} + +fn eval_mload( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::mload(machine) +} + +fn eval_mstore( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::mstore(machine) +} + +fn eval_mstore8( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::mstore8(machine) +} + +fn eval_jump( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::jump(machine) +} + +fn eval_jumpi( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::jumpi(machine) +} + +fn eval_pc( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::pc(machine, position) +} + +fn eval_msize( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::msize(machine) +} + +fn eval_jumpdest( + _machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + Control::Continue +} + +fn eval_push0( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 0, position) +} + +fn eval_push1( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 1, position) +} + +fn eval_push2( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 2, position) +} + +fn eval_push3( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 3, position) +} + +fn eval_push4( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 4, position) +} + +fn eval_push5( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 5, position) +} + +fn eval_push6( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 6, position) +} + +fn eval_push7( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 7, position) +} + +fn eval_push8( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 8, position) +} + +fn eval_push9( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 9, position) +} + +fn eval_push10( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 10, position) +} + +fn eval_push11( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 11, position) +} + +fn eval_push12( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 12, position) +} + +fn eval_push13( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 13, position) +} + +fn eval_push14( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 14, position) +} + +fn eval_push15( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 15, position) +} + +fn eval_push16( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 16, position) +} + +fn eval_push17( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 17, position) +} + +fn eval_push18( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 18, position) +} + +fn eval_push19( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 19, position) +} + +fn eval_push20( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 20, position) +} + +fn eval_push21( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 21, position) +} + +fn eval_push22( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 22, position) +} + +fn eval_push23( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 23, position) +} + +fn eval_push24( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 24, position) +} + +fn eval_push25( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 25, position) +} + +fn eval_push26( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 26, position) +} + +fn eval_push27( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 27, position) +} + +fn eval_push28( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 28, position) +} + +fn eval_push29( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 29, position) +} + +fn eval_push30( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 30, position) +} + +fn eval_push31( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 31, position) +} + +fn eval_push32( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + position: usize, +) -> Control { + self::misc::push(machine, 32, position) +} + +fn eval_dup1( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 1) +} + +fn eval_dup2( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 2) +} + +fn eval_dup3( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 3) +} + +fn eval_dup4( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 4) +} + +fn eval_dup5( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 5) +} + +fn eval_dup6( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 6) +} + +fn eval_dup7( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 7) +} + +fn eval_dup8( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 8) +} + +fn eval_dup9( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 9) +} + +fn eval_dup10( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 10) +} + +fn eval_dup11( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 11) +} + +fn eval_dup12( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 12) +} + +fn eval_dup13( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 13) +} + +fn eval_dup14( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 14) +} + +fn eval_dup15( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 15) +} + +fn eval_dup16( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::dup(machine, 16) +} + +fn eval_swap1( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 1) +} + +fn eval_swap2( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 2) +} + +fn eval_swap3( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 3) +} + +fn eval_swap4( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 4) +} + +fn eval_swap5( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 5) +} + +fn eval_swap6( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 6) +} + +fn eval_swap7( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 7) +} + +fn eval_swap8( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 8) +} + +fn eval_swap9( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 9) +} + +fn eval_swap10( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 10) +} + +fn eval_swap11( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 11) +} + +fn eval_swap12( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 12) +} + +fn eval_swap13( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 13) +} + +fn eval_swap14( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 14) +} + +fn eval_swap15( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 15) +} + +fn eval_swap16( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::swap(machine, 16) +} + +fn eval_return( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::ret(machine) +} + +fn eval_revert( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::revert(machine) +} + +fn eval_invalid( + _machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + Control::Exit(ExitException::DesignatedInvalid.into()) +} + +fn eval_unknown( + _machine: &mut Machine, + _handle: &mut H, + opcode: Opcode, + _position: usize, +) -> Control { + Control::Exit(ExitException::InvalidOpcode(opcode).into()) +} + +fn eval_sha3, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::sha3(machine) +} + +fn eval_address, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::address(machine) +} + +fn eval_balance, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::balance(machine, handle) +} + +fn eval_selfbalance, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::selfbalance(machine, handle) +} + +fn eval_origin, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::origin(machine, handle) +} + +fn eval_caller, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::caller(machine) +} + +fn eval_callvalue, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::callvalue(machine) +} + +fn eval_gasprice, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::gasprice(machine, handle) +} + +fn eval_extcodesize, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::extcodesize(machine, handle) +} + +fn eval_extcodehash, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::extcodehash(machine, handle) +} + +fn eval_extcodecopy, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::extcodecopy(machine, handle) +} + +fn eval_returndatasize, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::returndatasize(machine) +} + +fn eval_returndatacopy, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::returndatacopy(machine) +} + +fn eval_blockhash, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::blockhash(machine, handle) +} + +fn eval_coinbase, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::coinbase(machine, handle) +} + +fn eval_timestamp, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::timestamp(machine, handle) +} + +fn eval_number, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::number(machine, handle) +} + +fn eval_difficulty, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::prevrandao(machine, handle) +} + +fn eval_gaslimit, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::gaslimit(machine, handle) +} + +fn eval_sload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::sload(machine, handle) +} + +fn eval_sstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::sstore(machine, handle) +} + +fn eval_gas, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::gas(machine, handle) +} + +fn eval_log0, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::log(machine, 0, handle) +} + +fn eval_log1, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::log(machine, 1, handle) +} + +fn eval_log2, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::log(machine, 2, handle) +} + +fn eval_log3, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::log(machine, 3, handle) +} + +fn eval_log4, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::log(machine, 4, handle) +} + +fn eval_suicide, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::suicide(machine, handle) +} + +fn eval_chainid, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::chainid(machine, handle) +} + +fn eval_basefee, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::basefee(machine, handle) +} + +fn eval_call_create_trap( + _machine: &mut Machine, + _handle: &mut H, + opcode: Opcode, + _position: usize, +) -> Control { + Control::Trap(Tr::call_create_trap(opcode)) +} diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs new file mode 100644 index 000000000..c74dd0e87 --- /dev/null +++ b/interpreter/src/eval/system.rs @@ -0,0 +1,357 @@ +use super::Control; +use crate::{ + ExitException, ExitFatal, ExitSucceed, Log, Machine, RuntimeBackend, RuntimeEnvironment, + RuntimeState, Transfer, +}; +use alloc::vec::Vec; +use primitive_types::{H256, U256}; +use sha3::{Digest, Keccak256}; + +pub fn sha3, Tr>(machine: &mut Machine) -> Control { + pop_u256!(machine, from, len); + + try_or_fail!(machine.memory.resize_offset(from, len)); + let data = if len == U256::zero() { + Vec::new() + } else { + let from = as_usize_or_fail!(from); + let len = as_usize_or_fail!(len); + + machine.memory.get(from, len) + }; + + let ret = Keccak256::digest(data.as_slice()); + push!(machine, H256::from_slice(ret.as_slice())); + + Control::Continue +} + +pub fn chainid, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!(machine, handler.chain_id()); + + Control::Continue +} + +pub fn address, Tr>(machine: &mut Machine) -> Control { + let ret = H256::from(machine.state.as_ref().context.address); + push!(machine, ret); + + Control::Continue +} + +pub fn balance, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, address); + handler.mark_hot(address.into(), None); + push_u256!(machine, handler.balance(address.into())); + + Control::Continue +} + +pub fn selfbalance, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!( + machine, + handler.balance(machine.state.as_ref().context.address) + ); + + Control::Continue +} + +pub fn origin, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handler: &H, +) -> Control { + let ret = H256::from(machine.state.as_ref().transaction_context.origin); + push!(machine, ret); + + Control::Continue +} + +pub fn caller, Tr>(machine: &mut Machine) -> Control { + let ret = H256::from(machine.state.as_ref().context.caller); + push!(machine, ret); + + Control::Continue +} + +pub fn callvalue, Tr>(machine: &mut Machine) -> Control { + let mut ret = H256::default(); + machine + .state + .as_ref() + .context + .apparent_value + .to_big_endian(&mut ret[..]); + push!(machine, ret); + + Control::Continue +} + +pub fn gasprice, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handler: &H, +) -> Control { + let mut ret = H256::default(); + machine + .state + .as_ref() + .transaction_context + .gas_price + .to_big_endian(&mut ret[..]); + push!(machine, ret); + + Control::Continue +} + +pub fn basefee, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + let mut ret = H256::default(); + handler.block_base_fee_per_gas().to_big_endian(&mut ret[..]); + push!(machine, ret); + + Control::Continue +} + +pub fn extcodesize, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, address); + handler.mark_hot(address.into(), None); + let code_size = handler.code_size(address.into()); + push_u256!(machine, code_size); + + Control::Continue +} + +pub fn extcodehash, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, address); + handler.mark_hot(address.into(), None); + let code_hash = handler.code_hash(address.into()); + push!(machine, code_hash); + + Control::Continue +} + +pub fn extcodecopy, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, address); + pop_u256!(machine, memory_offset, code_offset, len); + + handler.mark_hot(address.into(), None); + try_or_fail!(machine.memory.resize_offset(memory_offset, len)); + + let code = handler.code(address.into()); + match machine + .memory + .copy_large(memory_offset, code_offset, len, &code) + { + Ok(()) => (), + Err(e) => return Control::Exit(e.into()), + }; + + Control::Continue +} + +pub fn returndatasize, Tr>(machine: &mut Machine) -> Control { + let size = U256::from(machine.state.as_ref().retbuf.len()); + push_u256!(machine, size); + + Control::Continue +} + +pub fn returndatacopy, Tr>(machine: &mut Machine) -> Control { + pop_u256!(machine, memory_offset, data_offset, len); + + try_or_fail!(machine.memory.resize_offset(memory_offset, len)); + if data_offset + .checked_add(len) + .map(|l| l > U256::from(machine.state.as_ref().retbuf.len())) + .unwrap_or(true) + { + return Control::Exit(ExitException::OutOfOffset.into()); + } + + match machine.memory.copy_large( + memory_offset, + data_offset, + len, + &machine.state.as_ref().retbuf, + ) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + +pub fn blockhash, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + pop_u256!(machine, number); + push!(machine, handler.block_hash(number)); + + Control::Continue +} + +pub fn coinbase, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push!(machine, handler.block_coinbase().into()); + Control::Continue +} + +pub fn timestamp, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!(machine, handler.block_timestamp()); + Control::Continue +} + +pub fn number, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!(machine, handler.block_number()); + Control::Continue +} + +pub fn difficulty, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!(machine, handler.block_difficulty()); + Control::Continue +} + +pub fn prevrandao, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + if let Some(rand) = handler.block_randomness() { + push!(machine, rand); + Control::Continue + } else { + difficulty(machine, handler) + } +} + +pub fn gaslimit, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &H, +) -> Control { + push_u256!(machine, handler.block_gas_limit()); + Control::Continue +} + +pub fn sload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index); + handler.mark_hot(machine.state.as_ref().context.address, Some(index)); + let value = handler.storage(machine.state.as_ref().context.address, index); + push!(machine, value); + + Control::Continue +} + +pub fn sstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index, value); + handler.mark_hot(machine.state.as_ref().context.address, Some(index)); + + match handler.set_storage(machine.state.as_ref().context.address, index, value) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + +pub fn gas, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + _handler: &H, +) -> Control { + push_u256!(machine, machine.state.as_ref().gas); + + Control::Continue +} + +pub fn log, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + n: u8, + handler: &mut H, +) -> Control { + pop_u256!(machine, offset, len); + + try_or_fail!(machine.memory.resize_offset(offset, len)); + let data = if len == U256::zero() { + Vec::new() + } else { + let offset = as_usize_or_fail!(offset); + let len = as_usize_or_fail!(len); + + machine.memory.get(offset, len) + }; + + let mut topics = Vec::new(); + for _ in 0..(n as usize) { + match machine.stack.pop() { + Ok(value) => { + topics.push(value); + } + Err(e) => return Control::Exit(e.into()), + } + } + + match handler.log(Log { + address: machine.state.as_ref().context.address, + topics, + data, + }) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + +pub fn suicide, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + let address = machine.state.as_ref().context.address; + + match machine.stack.perform_pop1_push0(|target| { + let balance = handler.balance(address); + + handler.transfer(Transfer { + source: address, + target: (*target).into(), + value: balance, + })?; + + handler.mark_delete(address); + handler.reset_balance(address); + + Ok(((), ())) + }) { + Ok(()) => Control::Exit(ExitSucceed::Suicided.into()), + Err(e) => Control::Exit(Err(e)), + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs new file mode 100644 index 000000000..965b3d771 --- /dev/null +++ b/interpreter/src/lib.rs @@ -0,0 +1,212 @@ +//! Core layer for EVM. + +// #![deny(warnings)] +// #![forbid(unsafe_code, unused_variables, unused_imports)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +pub mod call_create; +mod error; +mod eval; +mod memory; +mod opcode; +mod runtime; +mod stack; +pub mod utils; +mod valids; + +pub use crate::error::{Capture, ExitError, ExitException, ExitFatal, ExitResult, ExitSucceed}; +pub use crate::eval::{Control, Efn, Etable}; +pub use crate::memory::Memory; +pub use crate::opcode::Opcode; +pub use crate::runtime::{ + CallCreateTrap, Context, Log, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, + RuntimeState, TransactionContext, Transfer, +}; +pub use crate::stack::Stack; +pub use crate::valids::Valids; + +use alloc::rc::Rc; +use alloc::vec::Vec; + +/// Core execution layer for EVM. +pub struct Machine { + /// Program data. + data: Rc>, + /// Program code. + code: Rc>, + /// Program counter. + position: usize, + /// Code validity maps. + valids: Valids, + /// Return value. Note the difference between `retbuf`. + /// A `retval` holds what's returned by the current machine, with `RETURN` or `REVERT` opcode. + /// A `retbuf` holds the buffer of returned value by sub-calls. + pub retval: Vec, + /// Memory. + pub memory: Memory, + /// Stack. + pub stack: Stack, + /// Extra state, + pub state: S, +} + +impl Machine { + /// Return a reference of the program counter. + pub fn position(&self) -> usize { + self.position + } + + pub fn code(&self) -> &[u8] { + &self.code + } + + /// Create a new machine with given code and data. + pub fn new( + code: Rc>, + data: Rc>, + stack_limit: usize, + memory_limit: usize, + state: S, + ) -> Self { + let valids = Valids::new(&code[..]); + + Self { + data, + code, + position: 0, + valids, + retval: Vec::new(), + memory: Memory::new(memory_limit), + stack: Stack::new(stack_limit), + state, + } + } + + pub fn perform Result>( + &mut self, + f: F, + ) -> Result { + match f(self) { + Ok(r) => Ok(r), + Err(e) => { + self.exit(); + Err(e) + } + } + } + + /// Explicit exit of the machine. Further step will return error. + pub fn exit(&mut self) { + self.position = self.code.len(); + } + + /// Inspect the machine's next opcode and current stack. + pub fn inspect(&self) -> Option<(Opcode, &Stack)> { + self.code + .get(self.position) + .map(|v| (Opcode(*v), &self.stack)) + } + + /// Loop stepping the machine, until it stops. + pub fn run( + &mut self, + handle: &mut H, + etable: &Etable, + ) -> Capture + where + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, + { + loop { + match self.step(handle, etable) { + Ok(()) => (), + Err(res) => return res, + } + } + } + + #[inline] + /// Step the machine N times. + pub fn stepn( + &mut self, + n: usize, + handle: &mut H, + etable: &Etable, + ) -> Result<(), Capture> + where + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, + { + for _ in 0..n { + match self.step(handle, etable) { + Ok(()) => (), + Err(res) => return Err(res), + } + } + + Ok(()) + } + + #[inline] + /// Step the machine, executing one opcode. It then returns. + pub fn step( + &mut self, + handle: &mut H, + etable: &Etable, + ) -> Result<(), Capture> + where + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, + { + if self.is_empty() { + return Err(Capture::Exit(ExitSucceed::Stopped.into())); + } + + let position = self.position; + if position >= self.code.len() { + return Err(Capture::Exit(ExitFatal::AlreadyExited.into())); + } + + let opcode = Opcode(self.code[position]); + let control = etable[opcode.as_usize()](self, handle, opcode, self.position); + + match control { + Control::Continue => { + self.position += 1; + } + Control::ContinueN(p) => { + self.position = position + p; + } + Control::Exit(e) => { + self.position = self.code.len(); + return Err(Capture::Exit(e)); + } + Control::Jump(p) => { + self.position = p; + } + Control::Trap(opcode) => return Err(Capture::Trap(opcode)), + }; + + if self.position >= self.code.len() { + return Err(Capture::Exit(ExitSucceed::Stopped.into())); + } + + Ok(()) + } + + /// Pick the next opcode. + pub fn peek_opcode(&self) -> Option { + self.code.get(self.position).map(|opcode| Opcode(*opcode)) + } + + pub fn is_empty(&self) -> bool { + self.code.is_empty() + } + + pub fn advance(&mut self) { + if self.position == self.code.len() { + return; + } + + self.position += 1; + } +} diff --git a/core/src/memory.rs b/interpreter/src/memory.rs similarity index 79% rename from core/src/memory.rs rename to interpreter/src/memory.rs index 7bb09629b..c363a37ed 100644 --- a/core/src/memory.rs +++ b/interpreter/src/memory.rs @@ -1,7 +1,7 @@ -use crate::{ExitError, ExitFatal}; +use crate::{ExitException, ExitFatal}; use alloc::vec::Vec; -use core::cmp::min; -use core::ops::{BitAnd, Not}; +use core::ops::{BitAnd, Not, Range}; +use core::{cmp::min, mem}; use primitive_types::U256; /// A sequencial memory. It uses Rust's `Vec` for internal @@ -48,10 +48,16 @@ impl Memory { &self.data } + pub(crate) fn swap_and_clear(&mut self, other: &mut Vec) { + mem::swap(&mut self.data, other); + self.data = Vec::new(); + self.effective_len = U256::zero(); + } + /// Resize the memory, making it cover the memory region of `offset..(offset /// + len)`, with 32 bytes as the step. If the length is zero, this function /// does nothing. - pub fn resize_offset(&mut self, offset: U256, len: U256) -> Result<(), ExitError> { + pub fn resize_offset(&mut self, offset: U256, len: U256) -> Result<(), ExitException> { if len == U256::zero() { return Ok(()); } @@ -59,20 +65,45 @@ impl Memory { if let Some(end) = offset.checked_add(len) { self.resize_end(end) } else { - Err(ExitError::InvalidRange) + Err(ExitException::InvalidRange) } } /// Resize the memory, making it cover to `end`, with 32 bytes as the step. - pub fn resize_end(&mut self, end: U256) -> Result<(), ExitError> { + pub fn resize_end(&mut self, end: U256) -> Result<(), ExitException> { if end > self.effective_len { - let new_end = next_multiple_of_32(end).ok_or(ExitError::InvalidRange)?; + let new_end = next_multiple_of_32(end).ok_or(ExitException::InvalidRange)?; self.effective_len = new_end; } Ok(()) } + /// Resize to range. Used for return value. + pub fn resize_to_range(&mut self, return_range: Range) { + let ret = if return_range.start > U256::from(usize::MAX) { + let mut ret = Vec::new(); + ret.resize((return_range.end - return_range.start).as_usize(), 0); + ret + } else if return_range.end > U256::from(usize::MAX) { + let mut ret = self.get( + return_range.start.as_usize(), + usize::MAX - return_range.start.as_usize(), + ); + while ret.len() < (return_range.end - return_range.start).as_usize() { + ret.push(0); + } + ret + } else { + self.get( + return_range.start.as_usize(), + (return_range.end - return_range.start).as_usize(), + ) + }; + self.data = ret; + self.effective_len = return_range.end - return_range.start; + } + /// Get memory region at given offset. /// /// ## Panics @@ -85,7 +116,12 @@ impl Memory { #[allow(clippy::needless_range_loop)] for index in 0..size { - let position = offset + index; + let position = if let Some(position) = offset.checked_add(index) { + position + } else { + break; + }; + if position >= self.data.len() { break; } diff --git a/core/src/opcode.rs b/interpreter/src/opcode.rs similarity index 100% rename from core/src/opcode.rs rename to interpreter/src/opcode.rs diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs new file mode 100644 index 000000000..a5cfbb3aa --- /dev/null +++ b/interpreter/src/runtime.rs @@ -0,0 +1,159 @@ +use crate::{ExitError, Opcode}; +use alloc::rc::Rc; +use primitive_types::{H160, H256, U256}; +use sha3::{Digest, Keccak256}; + +/// Runtime state. +#[derive(Clone, Debug)] +pub struct RuntimeState { + /// Runtime context. + pub context: Context, + pub transaction_context: Rc, + /// Return data buffer. + pub retbuf: Vec, + /// Current gas. + pub gas: U256, +} + +impl AsRef for RuntimeState { + fn as_ref(&self) -> &Self { + self + } +} + +impl AsMut for RuntimeState { + fn as_mut(&mut self) -> &mut Self { + self + } +} + +/// Context of the runtime. +#[derive(Clone, Debug)] +pub struct Context { + /// Execution address. + pub address: H160, + /// Caller of the EVM. + pub caller: H160, + /// Apparent value of the EVM. + pub apparent_value: U256, +} + +#[derive(Clone, Debug)] +pub struct TransactionContext { + /// Gas price. + pub gas_price: U256, + /// Origin. + pub origin: H160, +} + +/// Transfer from source to target, with given value. +#[derive(Clone, Debug)] +pub struct Transfer { + /// Source address. + pub source: H160, + /// Target address. + pub target: H160, + /// Transfer value. + pub value: U256, +} + +/// Log +#[derive(Clone, Debug)] +pub struct Log { + pub address: H160, + pub topics: Vec, + pub data: Vec, +} + +pub trait CallCreateTrap: Sized { + fn call_create_trap(opcode: Opcode) -> Self; +} + +impl CallCreateTrap for Opcode { + fn call_create_trap(opcode: Opcode) -> Self { + opcode + } +} + +pub trait RuntimeEnvironment { + /// Get environmental block hash. + fn block_hash(&self, number: U256) -> H256; + /// Get environmental block number. + fn block_number(&self) -> U256; + /// Get environmental coinbase. + fn block_coinbase(&self) -> H160; + /// Get environmental block timestamp. + fn block_timestamp(&self) -> U256; + /// Get environmental block difficulty. + fn block_difficulty(&self) -> U256; + /// Get environmental block randomness. + fn block_randomness(&self) -> Option; + /// Get environmental gas limit. + fn block_gas_limit(&self) -> U256; + /// Environmental block base fee. + fn block_base_fee_per_gas(&self) -> U256; + /// Get environmental chain ID. + fn chain_id(&self) -> U256; +} + +pub trait RuntimeBaseBackend { + /// Get balance of address. + fn balance(&self, address: H160) -> U256; + /// Get code size of address. + fn code_size(&self, address: H160) -> U256 { + U256::from(self.code(address).len()) + } + /// Get code hash of address. + fn code_hash(&self, address: H160) -> H256 { + H256::from_slice(&Keccak256::digest(&self.code(address)[..])) + } + /// Get code of address. + fn code(&self, address: H160) -> Vec; + /// Get storage value of address at index. + fn storage(&self, address: H160, index: H256) -> H256; + + /// Check whether an address exists. + fn exists(&self, address: H160) -> bool; + + /// Get the current nonce of an account. + fn nonce(&self, address: H160) -> U256; +} + +/// The distinguish between `RuntimeBaseBackend` and `RuntimeBackend` is for the implementation of +/// overlays. +pub trait RuntimeBackend: RuntimeBaseBackend { + /// Get original storage value of address at index. + fn original_storage(&self, address: H160, index: H256) -> H256; + /// Check whether an address has already been deleted. + fn deleted(&self, address: H160) -> bool; + /// Checks if the address or (address, index) pair has been previously accessed. + fn is_cold(&self, address: H160, index: Option) -> bool; + fn is_hot(&self, address: H160, index: Option) -> bool { + !self.is_cold(address, index) + } + + /// Mark an address or (address, index) pair as hot. + fn mark_hot(&mut self, address: H160, index: Option); + /// Set storage value of address at index. + fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError>; + /// Create a log owned by address with given topics and data. + fn log(&mut self, log: Log) -> Result<(), ExitError>; + /// Mark an address to be deleted. + fn mark_delete(&mut self, address: H160); + /// Fully delete storages of an account. + fn reset_storage(&mut self, address: H160); + /// Set code of an account. + fn set_code(&mut self, address: H160, code: Vec) -> Result<(), ExitError>; + /// Reset balance of an account. + fn reset_balance(&mut self, address: H160); + fn deposit(&mut self, target: H160, value: U256); + fn withdrawal(&mut self, source: H160, value: U256) -> Result<(), ExitError>; + /// Initiate a transfer. + fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { + self.withdrawal(transfer.source, transfer.value)?; + self.deposit(transfer.target, transfer.value); + Ok(()) + } + /// Increase the nonce value. + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError>; +} diff --git a/interpreter/src/stack.rs b/interpreter/src/stack.rs new file mode 100644 index 000000000..426f03c01 --- /dev/null +++ b/interpreter/src/stack.rs @@ -0,0 +1,183 @@ +use crate::{ExitError, ExitException}; +use alloc::vec::Vec; +use primitive_types::H256; + +/// EVM stack. +#[derive(Clone, Debug)] +pub struct Stack { + data: Vec, + limit: usize, +} + +macro_rules! impl_perform_popn_pushn { + ( + $name:ident, + $pop_len:expr, + $push_len:expr, + ($($peek_pop:expr),*), + ($($peek_push:expr),*), + $pop_pushn_f:ident + ) => { + #[allow(unused_parens)] + pub fn $name(&mut self, f: F) -> Result where + F: FnOnce( + $(impl_perform_popn_pushn!(INTERNAL_TYPE_RH256, $peek_pop)),* + ) -> Result<(($(impl_perform_popn_pushn!(INTERNAL_TYPE_H256, $peek_push)),*), R), ExitError> + { + match self.check_pop_push($pop_len, $push_len) { + Ok(()) => (), + Err(e) => return Err(e.into()), + } + + let (p, ret) = match f($(self.unchecked_peek($peek_pop)),*) { + Ok(p1) => p1, + Err(e) => return Err(e.into()), + }; + self.$pop_pushn_f($pop_len, p); + + Ok(ret) + } + }; + (INTERNAL_TYPE_RH256, $e:expr) => { &H256 }; + (INTERNAL_TYPE_H256, $e:expr) => { H256 }; +} + +impl Stack { + /// Create a new stack with given limit. + pub fn new(limit: usize) -> Self { + Self { + data: Vec::new(), + limit, + } + } + + #[inline] + /// Stack limit. + pub fn limit(&self) -> usize { + self.limit + } + + #[inline] + /// Stack length. + pub fn len(&self) -> usize { + self.data.len() + } + + #[inline] + /// Whether the stack is empty. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + #[inline] + /// Stack data. + pub fn data(&self) -> &Vec { + &self.data + } + + /// Clear the stack. + pub fn clear(&mut self) { + self.data.clear() + } + + #[inline] + /// Pop a value from the stack. If the stack is already empty, returns the + /// `StackUnderflow` error. + pub fn pop(&mut self) -> Result { + self.data.pop().ok_or(ExitException::StackUnderflow) + } + + #[inline] + /// Push a new value into the stack. If it will exceed the stack limit, + /// returns `StackOverflow` error and leaves the stack unchanged. + pub fn push(&mut self, value: H256) -> Result<(), ExitException> { + if self.data.len() + 1 > self.limit { + return Err(ExitException::StackOverflow); + } + self.data.push(value); + Ok(()) + } + + pub fn check_pop_push(&self, pop: usize, push: usize) -> Result<(), ExitException> { + if self.data.len() < pop { + return Err(ExitException::StackUnderflow); + } + if self.data.len() - pop + push + 1 > self.limit { + return Err(ExitException::StackOverflow); + } + Ok(()) + } + + fn unchecked_peek(&self, no_from_top: usize) -> &H256 { + &self.data[self.data.len() - no_from_top - 1] + } + + fn unchecked_pop_push1(&mut self, pop: usize, p1: H256) { + for _ in 0..pop { + self.data.pop(); + } + self.data.push(p1); + } + + fn unchecked_pop_push0(&mut self, pop: usize, _p1: ()) { + for _ in 0..pop { + self.data.pop(); + } + } + + #[inline] + /// Peek a value at given index for the stack, where the top of + /// the stack is at index `0`. If the index is too large, + /// `StackError::Underflow` is returned. + pub fn peek(&self, no_from_top: usize) -> Result { + if self.data.len() > no_from_top { + Ok(self.data[self.data.len() - no_from_top - 1]) + } else { + Err(ExitException::StackUnderflow) + } + } + + #[inline] + /// Set a value at given index for the stack, where the top of the + /// stack is at index `0`. If the index is too large, + /// `StackError::Underflow` is returned. + pub fn set(&mut self, no_from_top: usize, val: H256) -> Result<(), ExitException> { + if self.data.len() > no_from_top { + let len = self.data.len(); + self.data[len - no_from_top - 1] = val; + Ok(()) + } else { + Err(ExitException::StackUnderflow) + } + } + + impl_perform_popn_pushn!(perform_pop0_push1, 0, 1, (), (0), unchecked_pop_push1); + impl_perform_popn_pushn!(perform_pop1_push0, 1, 0, (0), (), unchecked_pop_push0); + impl_perform_popn_pushn!(perform_pop1_push1, 1, 1, (0), (0), unchecked_pop_push1); + impl_perform_popn_pushn!(perform_pop2_push1, 2, 1, (0, 1), (0), unchecked_pop_push1); + impl_perform_popn_pushn!(perform_pop3_push0, 3, 0, (0, 1, 2), (), unchecked_pop_push0); + impl_perform_popn_pushn!( + perform_pop4_push0, + 4, + 0, + (0, 1, 2, 3), + (), + unchecked_pop_push0 + ); + impl_perform_popn_pushn!( + perform_pop6_push0, + 6, + 0, + (0, 1, 2, 3, 4, 5), + (), + unchecked_pop_push0 + ); + impl_perform_popn_pushn!( + perform_pop7_push0, + 7, + 0, + (0, 1, 2, 3, 4, 5, 6), + (), + unchecked_pop_push0 + ); +} diff --git a/core/src/utils.rs b/interpreter/src/utils.rs similarity index 90% rename from core/src/utils.rs rename to interpreter/src/utils.rs index 7dc2bd5ce..d3d4ce695 100644 --- a/core/src/utils.rs +++ b/interpreter/src/utils.rs @@ -1,6 +1,24 @@ +use crate::{ExitError, ExitFatal}; use core::cmp::Ordering; use core::ops::{Div, Rem}; -use primitive_types::U256; +use primitive_types::{H256, U256}; + +pub fn u256_to_h256(v: U256) -> H256 { + let mut r = H256::default(); + v.to_big_endian(&mut r[..]); + r +} + +pub fn h256_to_u256(v: H256) -> U256 { + U256::from_big_endian(&v[..]) +} + +pub fn u256_to_usize(v: U256) -> Result { + if v > U256::from(usize::MAX) { + return Err(ExitFatal::NotSupported.into()); + } + Ok(v.as_usize()) +} #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Sign { diff --git a/core/src/valids.rs b/interpreter/src/valids.rs similarity index 100% rename from core/src/valids.rs rename to interpreter/src/valids.rs diff --git a/core/tests/performance.rs b/interpreter/tests/performance.rs similarity index 89% rename from core/tests/performance.rs rename to interpreter/tests/performance.rs index ae56f5d5f..acbb7cebf 100644 --- a/core/tests/performance.rs +++ b/interpreter/tests/performance.rs @@ -1,16 +1,21 @@ -use evm_core::{Capture, ExitSucceed, Machine}; +use evm_interpreter::{Capture, Etable, ExitSucceed, Machine}; use std::rc::Rc; +static ETABLE: Etable<(), (), ()> = Etable::core(); + macro_rules! ret_test { - ( $name:ident, $code:expr, $data:expr, $ret:expr ) => { + ($name:ident, $code:expr, $data:expr, $ret:expr) => { #[test] fn $name() { let code = hex::decode($code).unwrap(); let data = hex::decode($data).unwrap(); - let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000); - assert_eq!(vm.run(), Capture::Exit(ExitSucceed::Returned.into())); - assert_eq!(vm.return_value(), hex::decode($ret).unwrap()); + let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, ()); + assert_eq!( + vm.run(&mut (), &ETABLE), + Capture::Exit(Ok(ExitSucceed::Returned.into())) + ); + assert_eq!(vm.retval, hex::decode($ret).unwrap()); } }; } diff --git a/interpreter/tests/usability.rs b/interpreter/tests/usability.rs new file mode 100644 index 000000000..181bd2044 --- /dev/null +++ b/interpreter/tests/usability.rs @@ -0,0 +1,198 @@ +use evm_interpreter::{ + Capture, Context, Control, Etable, ExitError, ExitSucceed, Log, Machine, Opcode, + RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, RuntimeState, TransactionContext, +}; +use primitive_types::{H160, H256, U256}; +use std::rc::Rc; + +const CODE1: &str = "60e060020a6000350480632839e92814601e57806361047ff414603457005b602a6004356024356047565b8060005260206000f35b603d6004356099565b8060005260206000f35b600082600014605457605e565b8160010190506093565b81600014606957607b565b60756001840360016047565b90506093565b609060018403608c85600186036047565b6047565b90505b92915050565b6000816000148060a95750816001145b60b05760b7565b81905060cf565b60c1600283036099565b60cb600184036099565b0190505b91905056"; +const DATA1: &str = "2839e92800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001"; +const RET1: &str = "000000000000000000000000000000000000000000000000000000000000000d"; + +#[test] +fn etable_wrap() { + let code = hex::decode(&CODE1).unwrap(); + let data = hex::decode(&DATA1).unwrap(); + + let wrapped_etable = Etable::<_, _, Opcode>::core().wrap(|f, opcode_t| { + move |machine, handle, opcode, position| { + assert_eq!(opcode_t, opcode); + println!("opcode: {:?}", opcode); + f(machine, handle, opcode, position) + } + }); + + let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, ()); + let result = vm.run(&mut (), &wrapped_etable); + assert_eq!(result, Capture::Exit(Ok(ExitSucceed::Returned.into()))); + assert_eq!(vm.retval, hex::decode(&RET1).unwrap()); +} + +#[test] +fn etable_wrap2() { + let code = hex::decode(&CODE1).unwrap(); + let data = hex::decode(&DATA1).unwrap(); + + let wrapped_etable = Etable::core().wrap( + |f, opcode_t| -> Box, &mut (), Opcode, usize) -> Control> { + if opcode_t != Opcode(0x50) { + Box::new(move |machine, handle, opcode, position| { + assert_eq!(opcode_t, opcode); + println!("opcode: {:?}", opcode); + f(machine, handle, opcode, position) + }) + } else { + Box::new(|_machine, _handle, opcode, _position| { + println!("disabled!"); + Control::Trap(opcode) + }) + } + }, + ); + + let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, ()); + let result = vm.run(&mut (), &wrapped_etable); + assert_eq!(result, Capture::Trap(Opcode(0x50))); +} + +pub struct UnimplementedHandler; + +impl RuntimeEnvironment for UnimplementedHandler { + fn block_hash(&self, _number: U256) -> H256 { + unimplemented!() + } + fn block_number(&self) -> U256 { + unimplemented!() + } + fn block_coinbase(&self) -> H160 { + unimplemented!() + } + fn block_timestamp(&self) -> U256 { + unimplemented!() + } + fn block_difficulty(&self) -> U256 { + unimplemented!() + } + fn block_randomness(&self) -> Option { + unimplemented!() + } + fn block_gas_limit(&self) -> U256 { + unimplemented!() + } + fn block_base_fee_per_gas(&self) -> U256 { + unimplemented!() + } + fn chain_id(&self) -> U256 { + unimplemented!() + } +} + +impl<'a> RuntimeBaseBackend for UnimplementedHandler { + fn balance(&self, _address: H160) -> U256 { + unimplemented!() + } + fn code_size(&self, _address: H160) -> U256 { + unimplemented!() + } + fn code_hash(&self, _address: H160) -> H256 { + unimplemented!() + } + fn code(&self, _address: H160) -> Vec { + unimplemented!() + } + fn storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } + + fn exists(&self, _address: H160) -> bool { + unimplemented!() + } + + fn nonce(&self, _address: H160) -> U256 { + unimplemented!() + } +} + +impl<'a> RuntimeBackend for UnimplementedHandler { + fn original_storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } + + fn deleted(&self, _address: H160) -> bool { + unimplemented!() + } + fn is_cold(&self, _address: H160, _index: Option) -> bool { + unimplemented!() + } + + fn mark_hot(&mut self, _address: H160, _index: Option) { + unimplemented!() + } + + fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> { + unimplemented!() + } + fn log(&mut self, _log: Log) -> Result<(), ExitError> { + unimplemented!() + } + fn mark_delete(&mut self, _address: H160) { + unimplemented!() + } + + fn reset_storage(&mut self, _address: H160) { + unimplemented!() + } + + fn set_code(&mut self, _address: H160, _code: Vec) -> Result<(), ExitError> { + unimplemented!() + } + + fn reset_balance(&mut self, _address: H160) { + unimplemented!() + } + + fn deposit(&mut self, _address: H160, _value: U256) { + unimplemented!() + } + fn withdrawal(&mut self, _address: H160, _value: U256) -> Result<(), ExitError> { + unimplemented!() + } + + fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> { + unimplemented!() + } +} + +static RUNTIME_ETABLE: Etable = Etable::runtime(); + +#[test] +fn etable_runtime() { + let code = hex::decode(&CODE1).unwrap(); + let data = hex::decode(&DATA1).unwrap(); + let mut handler = UnimplementedHandler; + + let mut vm = Machine::new( + Rc::new(code), + Rc::new(data), + 1024, + 10000, + RuntimeState { + context: Context { + address: H160::default(), + caller: H160::default(), + apparent_value: U256::default(), + }, + transaction_context: TransactionContext { + gas_price: U256::default(), + origin: H160::default(), + } + .into(), + retbuf: Vec::new(), + gas: U256::zero(), + }, + ); + + let res = vm.run(&mut handler, &RUNTIME_ETABLE).exit().unwrap(); + assert_eq!(res, Ok(ExitSucceed::Returned.into())); + assert_eq!(vm.retval, hex::decode(&RET1).unwrap()); +} diff --git a/jsontests/Cargo.toml b/jsontests/Cargo.toml new file mode 100644 index 000000000..0f2fb9092 --- /dev/null +++ b/jsontests/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "jsontests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +evm = { path = ".." } +precompiles = { path = "../precompiles" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +primitive-types = { version = "0.12", features = ["rlp", "serde"] } +clap = { version = "4", features = ["derive"] } +thiserror = "1" +hex = { version = "0.4", features = ["serde"] } +ethereum = "0.15.0" +rlp = "0.5" +sha3 = "0.10" diff --git a/jsontests/src/error.rs b/jsontests/src/error.rs new file mode 100644 index 000000000..e10e31b33 --- /dev/null +++ b/jsontests/src/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TestError { + #[error("state root is different")] + StateMismatch, + #[error("expect error, but got okay")] + ExpectException, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("io error")] + IO(#[from] std::io::Error), + #[error("json error")] + JSON(#[from] serde_json::Error), + #[error("evm error")] + EVM(#[from] evm::ExitError), + #[error("unsupported fork")] + UnsupportedFork, + #[error("non-utf8 filename")] + NonUtf8Filename, + #[error("test error")] + Test(#[from] TestError), +} diff --git a/jsontests/src/hash.rs b/jsontests/src/hash.rs new file mode 100644 index 000000000..43ca35b21 --- /dev/null +++ b/jsontests/src/hash.rs @@ -0,0 +1,94 @@ +use evm::backend::in_memory::InMemoryBackend; +use evm::utils::h256_to_u256; +use primitive_types::{H256, U256}; +use sha3::{Digest, Keccak256}; + +/// Basic account type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TrieAccount { + /// Nonce of the account. + pub nonce: U256, + /// Balance of the account. + pub balance: U256, + /// Storage root of the account. + pub storage_root: H256, + /// Code hash of the account. + pub code_hash: H256, + /// Code version of the account. + pub code_version: U256, +} + +impl rlp::Encodable for TrieAccount { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + let use_short_version = self.code_version == U256::zero(); + + match use_short_version { + true => { + stream.begin_list(4); + } + false => { + stream.begin_list(5); + } + } + + stream.append(&self.nonce); + stream.append(&self.balance); + stream.append(&self.storage_root); + stream.append(&self.code_hash); + + if !use_short_version { + stream.append(&self.code_version); + } + } +} + +impl rlp::Decodable for TrieAccount { + fn decode(rlp: &rlp::Rlp) -> Result { + let use_short_version = match rlp.item_count()? { + 4 => true, + 5 => false, + _ => return Err(rlp::DecoderError::RlpIncorrectListLen), + }; + + Ok(TrieAccount { + nonce: rlp.val_at(0)?, + balance: rlp.val_at(1)?, + storage_root: rlp.val_at(2)?, + code_hash: rlp.val_at(3)?, + code_version: if use_short_version { + U256::zero() + } else { + rlp.val_at(4)? + }, + }) + } +} + +pub fn state_root(backend: &InMemoryBackend) -> H256 { + let tree = backend + .current_layer() + .state + .iter() + .map(|(address, account)| { + let storage_root = ethereum::util::sec_trie_root( + account + .storage + .iter() + .map(|(k, v)| (k, rlp::encode(&h256_to_u256(*v)))), + ); + + let code_hash = H256::from_slice(&Keccak256::digest(&account.code)); + let account = TrieAccount { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash, + code_version: U256::zero(), + }; + + (address, rlp::encode(&account)) + }) + .collect::>(); + + ethereum::util::sec_trie_root(tree) +} diff --git a/jsontests/src/main.rs b/jsontests/src/main.rs new file mode 100644 index 000000000..c40418762 --- /dev/null +++ b/jsontests/src/main.rs @@ -0,0 +1,80 @@ +mod error; +mod hash; +mod run; +mod types; + +use crate::error::Error; +use crate::types::*; +use clap::Parser; +use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::io::BufReader; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + filenames: Vec, + + #[arg(short, long, default_value_t = false)] + debug: bool, +} + +fn run_file(filename: &str, debug: bool) -> Result<(), Error> { + let test_multi: BTreeMap = + serde_json::from_reader(BufReader::new(File::open(filename)?))?; + + for (test_name, test_multi) in test_multi { + let tests = test_multi.tests(); + + for test in tests { + if debug { + println!( + "{}/{}/{:?}/{} ===>", + filename, test_name, test.fork, test.index + ); + } else { + print!( + "{}/{}/{:?}/{}: ", + filename, test_name, test.fork, test.index + ); + } + match crate::run::run_test(filename, &test_name, test, debug) { + Ok(()) => println!("okay"), + Err(Error::UnsupportedFork) => println!("skipped"), + Err(err) => { + println!("err {:?}", err); + return Err(err); + } + } + if debug { + println!(""); + } + } + } + + Ok(()) +} + +fn run_single(filename: &str, debug: bool) -> Result<(), Error> { + if fs::metadata(&filename)?.is_dir() { + for filename in fs::read_dir(&filename)? { + let filepath = filename?.path(); + let filename = filepath.to_str().ok_or(Error::NonUtf8Filename)?; + run_file(filename, debug)?; + } + } else { + run_file(&filename, debug)?; + } + + Ok(()) +} + +fn main() -> Result<(), Error> { + let cli = Cli::parse(); + + for filename in cli.filenames { + run_single(&filename, cli.debug)?; + } + + Ok(()) +} diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs new file mode 100644 index 000000000..873956e91 --- /dev/null +++ b/jsontests/src/run.rs @@ -0,0 +1,152 @@ +use crate::error::{Error, TestError}; +use crate::types::*; +use evm::backend::in_memory::{ + InMemoryAccount, InMemoryBackend, InMemoryEnvironment, InMemoryLayer, +}; +use evm::standard::{Config, Etable, EtableResolver, Gasometer, Invoker, TransactArgs}; +use evm::utils::u256_to_h256; +use evm::Capture; +use precompiles::StandardPrecompileSet; +use primitive_types::U256; +use std::collections::{BTreeMap, BTreeSet}; + +pub fn run_test(_filename: &str, _test_name: &str, test: Test, debug: bool) -> Result<(), Error> { + let config = match test.fork { + Fork::Berlin => Config::berlin(), + _ => return Err(Error::UnsupportedFork), + }; + + if test.post.expect_exception == Some(TestExpectException::TR_TypeNotSupported) { + // The `evm` crate does not understand transaction format, only the `ethereum` crate. So + // there's nothing for us to test here for `TR_TypeNotSupported`. + return Ok(()); + } + + let env = InMemoryEnvironment { + block_hashes: BTreeMap::new(), // TODO: fill in this field. + block_number: test.env.current_number, + block_coinbase: test.env.current_coinbase, + block_timestamp: test.env.current_timestamp, + block_difficulty: test.env.current_difficulty, + block_randomness: Some(test.env.current_random), + block_gas_limit: test.env.current_gas_limit, + block_base_fee_per_gas: U256::zero(), // TODO: fill in this field. + chain_id: U256::zero(), // TODO: fill in this field. + }; + + let state = test + .pre + .clone() + .into_iter() + .map(|(address, account)| { + let storage = account + .storage + .into_iter() + .filter(|(_, value)| *value != U256::zero()) + .map(|(key, value)| (u256_to_h256(key), u256_to_h256(value))) + .collect::>(); + + ( + address, + InMemoryAccount { + balance: account.balance, + code: account.code.0, + nonce: account.nonce, + original_storage: storage.clone(), + storage, + }, + ) + }) + .collect::>(); + + let etable = Etable::runtime(); + let precompiles = StandardPrecompileSet::new(&config); + let resolver = EtableResolver::new(&config, &precompiles, &etable); + let invoker = Invoker::<_, Gasometer, _, _, _>::new(&config, &resolver); + let args = TransactArgs::Call { + caller: test.transaction.sender, + address: test.transaction.to, + value: test.transaction.value, + data: test.transaction.data, + gas_limit: test.transaction.gas_limit, + gas_price: test.transaction.gas_price, + access_list: test + .transaction + .access_list + .into_iter() + .map(|access| (access.address, access.storage_keys)) + .collect(), + }; + + let mut run_backend = InMemoryBackend { + environment: env, + layers: vec![InMemoryLayer { + state, + logs: Vec::new(), + suicides: Vec::new(), + hots: { + let mut hots = BTreeSet::new(); + for i in 1..10 { + hots.insert((u256_to_h256(U256::from(i)).into(), None)); + } + hots + }, + }], + }; + let mut step_backend = run_backend.clone(); + + // Run + let run_result = evm::transact(args.clone(), Some(4), &mut run_backend, &invoker); + run_backend.layers[0].clear_pending(); + + // Step + if debug { + let _step_result = evm::HeapTransact::new(args, &invoker, &mut step_backend).and_then( + |mut stepper| loop { + { + if let Some(machine) = stepper.last_machine() { + println!( + "pc: {}, opcode: {:?}, gas: 0x{:x}", + machine.machine.position(), + machine.machine.peek_opcode(), + machine.gasometer.gas(), + ); + } + } + if let Err(Capture::Exit(result)) = stepper.step() { + break result; + } + }, + ); + step_backend.layers[0].clear_pending(); + } + + let state_root = crate::hash::state_root(&run_backend); + + if test.post.expect_exception.is_some() { + if run_result.is_err() { + return Ok(()); + } else { + return Err(TestError::ExpectException.into()); + } + } + + if state_root != test.post.hash { + if debug { + for (address, account) in &run_backend.layers[0].state { + println!( + "address: {:?}, balance: {}, nonce: {}, code: 0x{}, storage: {:?}", + address, + account.balance, + account.nonce, + hex::encode(&account.code), + account.storage + ); + } + } + + return Err(TestError::StateMismatch.into()); + } + + Ok(()) +} diff --git a/jsontests/src/types.rs b/jsontests/src/types.rs new file mode 100644 index 000000000..0d3d317dc --- /dev/null +++ b/jsontests/src/types.rs @@ -0,0 +1,221 @@ +use hex::FromHex; +use primitive_types::{H160, H256, U256}; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, +}; +use std::collections::BTreeMap; +use std::fmt; + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +pub struct TestMulti { + #[serde(rename = "_info")] + pub info: TestInfo, + pub env: TestEnv, + pub post: BTreeMap>, + pub pre: BTreeMap, + pub transaction: TestMultiTransaction, +} + +impl TestMulti { + pub fn tests(&self) -> Vec { + let mut tests = Vec::new(); + + for (fork, post_states) in &self.post { + for (index, post_state) in post_states.iter().enumerate() { + tests.push(Test { + info: self.info.clone(), + env: self.env.clone(), + fork: fork.clone(), + index, + post: post_state.clone(), + pre: self.pre.clone(), + transaction: TestTransaction { + data: self.transaction.data[post_state.indexes.data].0.clone(), + gas_limit: self.transaction.gas_limit[post_state.indexes.gas], + gas_price: self.transaction.gas_price.unwrap_or(U256::zero()), + nonce: self.transaction.nonce, + secret_key: self.transaction.secret_key, + sender: self.transaction.sender, + to: self.transaction.to, + value: self.transaction.value[post_state.indexes.value], + access_list: match &self.transaction.access_lists { + Some(access_lists) => access_lists[post_state.indexes.data].clone(), + None => Vec::new(), + }, + }, + }); + } + } + + tests + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Test { + pub info: TestInfo, + pub env: TestEnv, + pub fork: Fork, + pub index: usize, + pub post: TestPostState, + pub pre: BTreeMap, + pub transaction: TestTransaction, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestInfo { + pub comment: String, + #[serde(rename = "filling-rpc-server")] + pub filling_rpc_server: String, + #[serde(rename = "filling-tool-version")] + pub filling_tool_version: String, + pub generated_test_hash: String, + pub lllcversion: String, + pub solidity: String, + pub source: String, + pub source_hash: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestEnv { + pub current_base_fee: U256, + pub current_beacon_root: H256, + pub current_coinbase: H160, + pub current_difficulty: U256, + pub current_gas_limit: U256, + pub current_number: U256, + pub current_random: H256, + pub current_timestamp: U256, + pub current_withdrawals_root: H256, + pub previous_hash: H256, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize)] +pub enum Fork { + Berlin, + Cancun, + London, + Merge, + Shanghai, + Byzantium, + Constantinople, + ConstantinopleFix, + EIP150, + EIP158, + Frontier, + Homestead, + Istanbul, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestPostState { + pub hash: H256, + pub indexes: TestPostStateIndexes, + pub logs: H256, + pub txbytes: HexBytes, + pub expect_exception: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[allow(non_camel_case_types)] +pub enum TestExpectException { + TR_TypeNotSupported, + TR_IntrinsicGas, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +pub struct TestPostStateIndexes { + pub data: usize, + pub gas: usize, + pub value: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +pub struct TestPreState { + pub balance: U256, + pub code: HexBytes, + pub nonce: U256, + pub storage: BTreeMap, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestMultiTransaction { + pub data: Vec, + pub gas_limit: Vec, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: U256, + pub secret_key: H256, + pub sender: H160, + pub to: H160, + pub value: Vec, + pub access_lists: Option>>, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestAccessListItem { + pub address: H160, + pub storage_keys: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TestTransaction { + pub data: Vec, + pub gas_limit: U256, + pub gas_price: U256, + pub nonce: U256, + pub secret_key: H256, + pub sender: H160, + pub to: H160, + pub value: U256, + pub access_list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +pub struct HexBytes(#[serde(deserialize_with = "deserialize_hex_bytes")] pub Vec); + +fn deserialize_hex_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct HexStrVisitor; + + impl<'de> Visitor<'de> for HexStrVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a hex encoded string") + } + + fn visit_str(self, data: &str) -> Result + where + E: Error, + { + if &data[0..2] != "0x" { + return Err(Error::custom("should start with 0x")); + } + + FromHex::from_hex(&data[2..]).map_err(Error::custom) + } + + fn visit_borrowed_str(self, data: &'de str) -> Result + where + E: Error, + { + if &data[0..2] != "0x" { + return Err(Error::custom("should start with 0x")); + } + + FromHex::from_hex(&data[2..]).map_err(Error::custom) + } + } + + deserializer.deserialize_str(HexStrVisitor) +} diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml new file mode 100644 index 000000000..c6bbb4e3e --- /dev/null +++ b/precompiles/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "precompiles" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "Standard EVM precompiles." + +[dependencies] +evm = { path = "..", default-features = false } +primitive-types = { version = "0.12", default-features = false, features = ["rlp"] } +k256 = { version = "0.13", features = ["ecdsa"], default-features = false } +sha3 = { version = "0.10", default-features = false } +sha2 = { version = "0.10", default-features = false } +ripemd = { version = "0.1", default-features = false } +num = { version = "0.4", default-features = false, features = ["alloc"] } +bn = { package = "substrate-bn", version = "0.6", default-features = false } + +[features] +default = ["std"] +std = [ + "evm/std", + "primitive-types/std", + "sha3/std", + "k256/std", + "sha2/std", + "ripemd/std", + "num/std", +] diff --git a/precompiles/src/blake2/eip152.rs b/precompiles/src/blake2/eip152.rs new file mode 100644 index 000000000..9501cf6cd --- /dev/null +++ b/precompiles/src/blake2/eip152.rs @@ -0,0 +1,75 @@ +/// The precomputed values for BLAKE2b [from the spec](https://tools.ietf.org/html/rfc7693#section-2.7) +/// There are 10 16-byte arrays - one for each round +/// the entries are calculated from the sigma constants. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// IV is the initialization vector for BLAKE2b. See https://tools.ietf.org/html/rfc7693#section-2.6 +/// for details. +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +#[inline(always)] +/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1 +fn g(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +/// The Blake2 compression function F. See https://tools.ietf.org/html/rfc7693#section-3.2 +/// Takes as an argument the state vector `h`, message block vector `m`, offset counter `t`, final +/// block indicator flag `f`, and number of rounds `rounds`. The state vector provided as the first +/// parameter is modified by the function. +pub fn compress(h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool, rounds: usize) { + let mut v = [0u64; 16]; + v[..h.len()].copy_from_slice(h); // First half from state. + v[h.len()..].copy_from_slice(&IV); // Second half from IV. + + v[12] ^= t[0]; + v[13] ^= t[1]; + + if f { + v[14] = !v[14] // Invert all bits if the last-block-flag is set. + } + for i in 0..rounds { + // Message word selection permutation for this round. + let s = &SIGMA[i % 10]; + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} diff --git a/precompiles/src/blake2/mod.rs b/precompiles/src/blake2/mod.rs new file mode 100644 index 000000000..8b0c6c370 --- /dev/null +++ b/precompiles/src/blake2/mod.rs @@ -0,0 +1,91 @@ +mod eip152; + +use crate::PurePrecompile; +use evm::{ExitException, ExitResult, ExitSucceed, RuntimeState, StaticGasometer}; + +pub struct Blake2F; + +impl Blake2F { + const GAS_COST_PER_ROUND: u64 = 1; // https://eips.ethereum.org/EIPS/eip-152#gas-costs-and-benchmarks +} + +impl PurePrecompile for Blake2F { + /// Format of `input`: + /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f] + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + const BLAKE2_F_ARG_LEN: usize = 213; + + if input.len() != BLAKE2_F_ARG_LEN { + return ( + ExitException::Other( + "input length for Blake2 F precompile should be exactly 213 bytes".into(), + ) + .into(), + Vec::new(), + ); + } + + let mut rounds_buf: [u8; 4] = [0; 4]; + rounds_buf.copy_from_slice(&input[0..4]); + let rounds: u32 = u32::from_be_bytes(rounds_buf); + + let gas_cost: u64 = (rounds as u64) * Blake2F::GAS_COST_PER_ROUND; + try_some!(gasometer.record_cost(gas_cost.into())); + + // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE + let mut h_buf: [u8; 64] = [0; 64]; + h_buf.copy_from_slice(&input[4..68]); + let mut h = [0u64; 8]; + let mut ctr = 0; + for state_word in &mut h { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&h_buf[(ctr * 8)..(ctr + 1) * 8]); + *state_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut m_buf: [u8; 128] = [0; 128]; + m_buf.copy_from_slice(&input[68..196]); + let mut m = [0u64; 16]; + ctr = 0; + for msg_word in &mut m { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&m_buf[(ctr * 8)..(ctr + 1) * 8]); + *msg_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut t_0_buf: [u8; 8] = [0; 8]; + t_0_buf.copy_from_slice(&input[196..204]); + let t_0 = u64::from_le_bytes(t_0_buf); + + let mut t_1_buf: [u8; 8] = [0; 8]; + t_1_buf.copy_from_slice(&input[204..212]); + let t_1 = u64::from_le_bytes(t_1_buf); + + let f = if input[212] == 1 { + true + } else if input[212] == 0 { + false + } else { + return ( + ExitException::Other("incorrect final block indicator flag".into()).into(), + Vec::new(), + ); + }; + + eip152::compress(&mut h, m, [t_0, t_1], f, rounds as usize); + + let mut output_buf = [0u8; u64::BITS as usize]; + for (i, state_word) in h.iter().enumerate() { + output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); + } + + (ExitSucceed::Returned.into(), output_buf.to_vec()) + } +} diff --git a/precompiles/src/bn128.rs b/precompiles/src/bn128.rs new file mode 100644 index 000000000..4bd97eadb --- /dev/null +++ b/precompiles/src/bn128.rs @@ -0,0 +1,228 @@ +use crate::PurePrecompile; +use alloc::vec::Vec; +use evm::{ExitError, ExitException, ExitResult, ExitSucceed, RuntimeState, StaticGasometer}; +use primitive_types::U256; + +/// Copy bytes from input to target. +fn read_input(source: &[u8], target: &mut [u8], offset: usize) { + // Out of bounds, nothing to copy. + if source.len() <= offset { + return; + } + + // Find len to copy up to target len, but not out of bounds. + let len = core::cmp::min(target.len(), source.len() - offset); + target[..len].copy_from_slice(&source[offset..][..len]); +} + +fn read_fr(input: &[u8], start_inx: usize) -> Result { + let mut buf = [0u8; 32]; + read_input(input, &mut buf, start_inx); + + let ret = bn::Fr::from_slice(&buf) + .map_err(|_| ExitException::Other("Invalid field element".into()))?; + Ok(ret) +} + +fn read_point(input: &[u8], start_inx: usize) -> Result { + use bn::{AffineG1, Fq, Group, G1}; + + let mut px_buf = [0u8; 32]; + let mut py_buf = [0u8; 32]; + read_input(input, &mut px_buf, start_inx); + read_input(input, &mut py_buf, start_inx + 32); + + let px = Fq::from_slice(&px_buf) + .map_err(|_| ExitException::Other("Invalid point x coordinate".into()))?; + + let py = Fq::from_slice(&py_buf) + .map_err(|_| ExitException::Other("Invalid point y coordinate".into()))?; + + Ok(if px == Fq::zero() && py == Fq::zero() { + G1::zero() + } else { + AffineG1::new(px, py) + .map_err(|_| ExitException::Other("Invalid curve point".into()))? + .into() + }) +} + +/// The Bn128Add builtin +pub struct Bn128Add; + +impl Bn128Add { + const GAS_COST: u64 = 150; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl PurePrecompile for Bn128Add { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + use bn::AffineG1; + + try_some!(gasometer.record_cost(Bn128Add::GAS_COST.into())); + + let p1 = try_some!(read_point(input, 0)); + let p2 = try_some!(read_point(input, 64)); + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { + // point not at infinity + try_some!(sum + .x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| ExitException::Other( + "Cannot fail since 0..32 is 32-byte length".into() + ))); + try_some!(sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| { + ExitException::Other("Cannot fail since 32..64 is 32-byte length".into()) + })); + } + + (ExitSucceed::Returned.into(), buf.to_vec()) + } +} + +/// The Bn128Mul builtin +pub struct Bn128Mul; + +impl Bn128Mul { + const GAS_COST: u64 = 6_000; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl PurePrecompile for Bn128Mul { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + use bn::AffineG1; + + try_some!(gasometer.record_cost(Bn128Mul::GAS_COST.into())); + + let p = try_some!(read_point(input, 0)); + let fr = try_some!(read_fr(input, 64)); + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p * fr) { + // point not at infinity + try_some!(sum + .x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| ExitException::Other( + "Cannot fail since 0..32 is 32-byte length".into() + ))); + try_some!(sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| { + ExitException::Other("Cannot fail since 32..64 is 32-byte length".into()) + })); + } + + (ExitSucceed::Returned.into(), buf.to_vec()) + } +} + +/// The Bn128Pairing builtin +pub struct Bn128Pairing; + +impl Bn128Pairing { + // https://eips.ethereum.org/EIPS/eip-1108 + const BASE_GAS_COST: u64 = 45_000; + const GAS_COST_PER_PAIRING: u64 = 34_000; +} + +impl PurePrecompile for Bn128Pairing { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; + + let ret_val = if input.is_empty() { + try_some!(gasometer.record_cost(Bn128Pairing::BASE_GAS_COST.into())); + U256::one() + } else { + if input.len() % 192 > 0 { + return ( + ExitException::Other("bad elliptic curve pairing size".into()).into(), + Vec::new(), + ); + } + + // (a, b_a, b_b - each 64-byte affine coordinates) + let elements = input.len() / 192; + + let gas_cost: u64 = Bn128Pairing::BASE_GAS_COST + + (elements as u64 * Bn128Pairing::GAS_COST_PER_PAIRING); + + try_some!(gasometer.record_cost(gas_cost.into())); + + let mut vals = Vec::new(); + for idx in 0..elements { + let a_x = try_some!(Fq::from_slice(&input[idx * 192..idx * 192 + 32]) + .map_err(|_| ExitException::Other("Invalid a argument x coordinate".into()))); + + let a_y = try_some!(Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]) + .map_err(|_| ExitException::Other("Invalid a argument y coordinate".into(),))); + + let b_a_y = try_some!(Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]) + .map_err(|_| { + ExitException::Other( + "Invalid b argument imaginary coeff x coordinate".into(), + ) + })); + + let b_a_x = try_some!(Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]) + .map_err(|_| ExitException::Other( + "Invalid b argument imaginary coeff y coordinate".into(), + ))); + + let b_b_y = try_some!(Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]) + .map_err(|_| { + ExitException::Other("Invalid b argument real coeff x coordinate".into()) + })); + + let b_b_x = try_some!(Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]) + .map_err(|_| { + ExitException::Other("Invalid b argument real coeff y coordinate".into()) + })); + + let b_a = Fq2::new(b_a_x, b_a_y); + let b_b = Fq2::new(b_b_x, b_b_y); + let b = if b_a.is_zero() && b_b.is_zero() { + G2::zero() + } else { + G2::from(try_some!(AffineG2::new(b_a, b_b).map_err(|_| { + ExitException::Other("Invalid b argument - not on curve".into()) + }))) + }; + let a = if a_x.is_zero() && a_y.is_zero() { + G1::zero() + } else { + G1::from(try_some!(AffineG1::new(a_x, a_y).map_err(|_| { + ExitException::Other("Invalid a argument - not on curve".into()) + },))) + }; + vals.push((a, b)); + } + + let mul = pairing_batch(&vals); + + if mul == Gt::one() { + U256::one() + } else { + U256::zero() + } + }; + + let mut buf = [0u8; 32]; + ret_val.to_big_endian(&mut buf); + + (ExitSucceed::Returned.into(), buf.to_vec()) + } +} diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs new file mode 100644 index 000000000..c519a0f44 --- /dev/null +++ b/precompiles/src/lib.rs @@ -0,0 +1,106 @@ +//! Standard EVM precompiles. + +// #![deny(warnings)] +// #![forbid(unsafe_code, unused_variables)] +#![cfg_attr(not(feature = "std"), no_std)] + +macro_rules! try_some { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(err) => return (Err(err.into()), Vec::new()), + } + }; +} + +extern crate alloc; + +mod blake2; +mod bn128; +mod modexp; +mod simple; + +pub use crate::blake2::Blake2F; +pub use crate::bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +pub use crate::modexp::Modexp; +pub use crate::simple::{ECRecover, Identity, Ripemd160, Sha256}; + +use alloc::vec::Vec; +use evm::standard::{Config, PrecompileSet}; +use evm::{ExitError, ExitException, ExitResult, RuntimeState, StaticGasometer}; + +use primitive_types::H160; + +pub trait PurePrecompile { + fn execute( + &self, + input: &[u8], + state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec); +} + +pub struct StandardPrecompileSet<'config> { + _config: &'config Config, +} + +impl<'config> StandardPrecompileSet<'config> { + pub fn new(config: &'config Config) -> Self { + Self { _config: config } + } +} + +impl<'config, S: AsRef, G: StaticGasometer, H> PrecompileSet + for StandardPrecompileSet<'config> +{ + fn execute( + &self, + code_address: H160, + input: &[u8], + _is_static: bool, + state: &mut S, + gasometer: &mut G, + _handler: &mut H, + ) -> Option<(ExitResult, Vec)> { + // TODO: selectively disable precompiles based on config. + + if code_address == address(1) { + Some(ECRecover.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(2) { + Some(Sha256.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(3) { + Some(Ripemd160.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(4) { + Some(Identity.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(5) { + Some(Modexp.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(6) { + Some(Bn128Add.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(7) { + Some(Bn128Mul.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(8) { + Some(Bn128Pairing.execute(input, state.as_ref(), gasometer)) + } else if code_address == address(9) { + Some(Blake2F.execute(input, state.as_ref(), gasometer)) + } else { + None + } + } +} + +fn linear_cost(len: u64, base: u64, word: u64) -> Result { + let cost = base + .checked_add( + word.checked_mul(len.saturating_add(31) / 32) + .ok_or(ExitException::OutOfGas)?, + ) + .ok_or(ExitException::OutOfGas)?; + + Ok(cost) +} + +const fn address(last: u8) -> H160 { + H160([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, last, + ]) +} diff --git a/precompiles/src/modexp.rs b/precompiles/src/modexp.rs new file mode 100644 index 000000000..d002da153 --- /dev/null +++ b/precompiles/src/modexp.rs @@ -0,0 +1,193 @@ +use crate::PurePrecompile; +use alloc::{vec, vec::Vec}; +use core::cmp::max; +use evm::{ExitException, ExitResult, ExitSucceed, RuntimeState, StaticGasometer}; +use num::{BigUint, FromPrimitive, Integer, One, ToPrimitive, Zero}; + +pub struct Modexp; + +const MIN_GAS_COST: u64 = 200; + +// Calculate gas cost according to EIP 2565: +// https://eips.ethereum.org/EIPS/eip-2565 +fn calculate_gas_cost( + base_length: u64, + mod_length: u64, + exponent: &BigUint, + exponent_bytes: &[u8], + mod_is_even: bool, +) -> u64 { + fn calculate_multiplication_complexity(base_length: u64, mod_length: u64) -> u64 { + let max_length = max(base_length, mod_length); + let mut words = max_length / 8; + if max_length % 8 > 0 { + words += 1; + } + + // Note: can't overflow because we take words to be some u64 value / 8, which is + // necessarily less than sqrt(u64::MAX). + // Additionally, both base_length and mod_length are bounded to 1024, so this has + // an upper bound of roughly (1024 / 8) squared + words * words + } + + fn calculate_iteration_count(exponent: &BigUint, exponent_bytes: &[u8]) -> u64 { + let mut iteration_count: u64 = 0; + let exp_length = exponent_bytes.len() as u64; + + if exp_length <= 32 && exponent.is_zero() { + iteration_count = 0; + } else if exp_length <= 32 { + iteration_count = exponent.bits() - 1; + } else if exp_length > 32 { + // from the EIP spec: + // (8 * (exp_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1) + // + // Notes: + // * exp_length is bounded to 1024 and is > 32 + // * exponent can be zero, so we subtract 1 after adding the other terms (whose sum + // must be > 0) + // * the addition can't overflow because the terms are both capped at roughly + // 8 * max size of exp_length (1024) + // * the EIP spec is written in python, in which (exponent & (2**256 - 1)) takes the + // FIRST 32 bytes. However this `BigUint` `&` operator takes the LAST 32 bytes. + // We thus instead take the bytes manually. + let exponent_head = BigUint::from_bytes_be(&exponent_bytes[..32]); + + iteration_count = (8 * (exp_length - 32)) + exponent_head.bits() - 1; + } + + max(iteration_count, 1) + } + + let multiplication_complexity = calculate_multiplication_complexity(base_length, mod_length); + let iteration_count = calculate_iteration_count(exponent, exponent_bytes); + max( + MIN_GAS_COST, + multiplication_complexity * iteration_count / 3, + ) + .saturating_mul(if mod_is_even { 20 } else { 1 }) +} + +/// Copy bytes from input to target. +fn read_input(source: &[u8], target: &mut [u8], source_offset: &mut usize) { + // We move the offset by the len of the target, regardless of what we + // actually copy. + let offset = *source_offset; + *source_offset += target.len(); + + // Out of bounds, nothing to copy. + if source.len() <= offset { + return; + } + + // Find len to copy up to target len, but not out of bounds. + let len = core::cmp::min(target.len(), source.len() - offset); + target[..len].copy_from_slice(&source[offset..][..len]); +} + +impl PurePrecompile for Modexp { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + let mut input_offset = 0; + + // Yellowpaper: whenever the input is too short, the missing bytes are + // considered to be zero. + let mut base_len_buf = [0u8; 32]; + read_input(input, &mut base_len_buf, &mut input_offset); + let mut exp_len_buf = [0u8; 32]; + read_input(input, &mut exp_len_buf, &mut input_offset); + let mut mod_len_buf = [0u8; 32]; + read_input(input, &mut mod_len_buf, &mut input_offset); + + // reasonable assumption: this must fit within the Ethereum EVM's max stack size + let max_size_big = BigUint::from_u32(1024).expect("can't create BigUint"); + + let base_len_big = BigUint::from_bytes_be(&base_len_buf); + if base_len_big > max_size_big { + try_some!(Err(ExitException::Other( + "unreasonably large base length".into() + ))); + } + + let exp_len_big = BigUint::from_bytes_be(&exp_len_buf); + if exp_len_big > max_size_big { + try_some!(Err(ExitException::Other( + "unreasonably large exponent length".into() + ))); + } + + let mod_len_big = BigUint::from_bytes_be(&mod_len_buf); + if mod_len_big > max_size_big { + try_some!(Err(ExitException::Other( + "unreasonably large modulus length".into() + ))); + } + + // bounds check handled above + let base_len = base_len_big.to_usize().expect("base_len out of bounds"); + let exp_len = exp_len_big.to_usize().expect("exp_len out of bounds"); + let mod_len = mod_len_big.to_usize().expect("mod_len out of bounds"); + + // if mod_len is 0 output must be empty + if mod_len == 0 { + return (ExitSucceed::Returned.into(), Vec::new()); + } + + // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first. + let r = if base_len == 0 && mod_len == 0 { + try_some!(gasometer.record_cost(MIN_GAS_COST.into())); + BigUint::zero() + } else { + // read the numbers themselves. + let mut base_buf = vec![0u8; base_len]; + read_input(input, &mut base_buf, &mut input_offset); + let base = BigUint::from_bytes_be(&base_buf); + + let mut exp_buf = vec![0u8; exp_len]; + read_input(input, &mut exp_buf, &mut input_offset); + let exponent = BigUint::from_bytes_be(&exp_buf); + + let mut mod_buf = vec![0u8; mod_len]; + read_input(input, &mut mod_buf, &mut input_offset); + let modulus = BigUint::from_bytes_be(&mod_buf); + + // do our gas accounting + let gas_cost = calculate_gas_cost( + base_len as u64, + mod_len as u64, + &exponent, + &exp_buf, + modulus.is_even(), + ); + + try_some!(gasometer.record_cost(gas_cost.into())); + + if modulus.is_zero() || modulus.is_one() { + BigUint::zero() + } else { + base.modpow(&exponent, &modulus) + } + }; + + // write output to given memory, left padded and same length as the modulus. + let bytes = r.to_bytes_be(); + + // always true except in the case of zero-length modulus, which leads to + // output of length and value 1. + if bytes.len() == mod_len { + (ExitSucceed::Returned.into(), bytes.to_vec()) + } else if bytes.len() < mod_len { + let mut ret = Vec::with_capacity(mod_len); + ret.extend(core::iter::repeat(0).take(mod_len - bytes.len())); + ret.extend_from_slice(&bytes[..]); + (ExitSucceed::Returned.into(), ret.to_vec()) + } else { + return (ExitException::Other("failed".into()).into(), Vec::new()); + } + } +} diff --git a/precompiles/src/simple.rs b/precompiles/src/simple.rs new file mode 100644 index 000000000..114edfc4c --- /dev/null +++ b/precompiles/src/simple.rs @@ -0,0 +1,117 @@ +use crate::{linear_cost, PurePrecompile}; +use core::cmp::min; +use evm::{ExitException, ExitResult, ExitSucceed, RuntimeState, StaticGasometer}; +use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; +use primitive_types::{H256, U256}; +use sha3::{Digest, Keccak256}; + +pub struct ECRecover; + +impl PurePrecompile for ECRecover { + fn execute(&self, i: &[u8], _state: &RuntimeState, gasometer: &mut G) -> (ExitResult, Vec) { + const COST_BASE: u64 = 3000; + const COST_WORD: u64 = 0; + try_some!(gasometer.record_cost(U256::from(try_some!(linear_cost( + i.len() as u64, + COST_BASE, + COST_WORD + ))))); + + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + // v can only be 27 or 28 on the full 32 bytes value. + // https://github.com/ethereum/go-ethereum/blob/a907d7e81aaeea15d80b2d3209ad8e08e3bf49e0/core/vm/contracts.go#L177 + if input[32..63] != [0u8; 31] || ![27, 28].contains(&input[63]) { + return (ExitSucceed::Returned.into(), Vec::new()); + } + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 64]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); // r + sig[32..64].copy_from_slice(&input[96..128]); // s + let sig = try_some!(Signature::from_bytes((&sig[..]).into()) + .map_err(|_| ExitException::Other("invalid ecdsa sig".into()))); + let recid = try_some!(RecoveryId::from_byte(input[63] - 27) + .ok_or(ExitException::Other("invalid recoverty id".into()))); // v + + let pubkey = try_some!(VerifyingKey::recover_from_prehash(&msg[..], &sig, recid) + .map_err(|_| ExitException::Other("recover key failed".into()))); + let mut address = + H256::from_slice(Keccak256::digest(&pubkey.to_sec1_bytes()[..]).as_slice()); + address.0[0..12].copy_from_slice(&[0u8; 12]); + + (ExitSucceed::Returned.into(), address.0.to_vec()) + } +} + +pub struct Sha256; + +impl PurePrecompile for Sha256 { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + const COST_BASE: u64 = 600; + const COST_WORD: u64 = 120; + try_some!(gasometer.record_cost(U256::from(try_some!(linear_cost( + input.len() as u64, + COST_BASE, + COST_WORD + ))))); + + let mut ret = [0u8; 32]; + let hash = ripemd::Ripemd160::digest(&input[..]); + ret[12..32].copy_from_slice(&hash); + + (ExitSucceed::Returned.into(), ret.to_vec()) + } +} + +pub struct Ripemd160; + +impl PurePrecompile for Ripemd160 { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + const COST_BASE: u64 = 60; + const COST_WORD: u64 = 12; + try_some!(gasometer.record_cost(U256::from(try_some!(linear_cost( + input.len() as u64, + COST_BASE, + COST_WORD + ))))); + + let hash = sha2::Sha256::digest(&input[..]); + + (ExitSucceed::Returned.into(), hash.to_vec()) + } +} + +pub struct Identity; + +impl PurePrecompile for Identity { + fn execute( + &self, + input: &[u8], + _state: &RuntimeState, + gasometer: &mut G, + ) -> (ExitResult, Vec) { + const COST_BASE: u64 = 15; + const COST_WORD: u64 = 3; + try_some!(gasometer.record_cost(U256::from(try_some!(linear_cost( + input.len() as u64, + COST_BASE, + COST_WORD + ))))); + + (ExitSucceed::Returned.into(), input.to_vec()) + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml deleted file mode 100644 index e999fd490..000000000 --- a/runtime/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "evm-runtime" -version = "0.41.0" -license = "Apache-2.0" -authors = ["Wei Tang ", "Parity Technologies "] -description = "SputnikVM - a Portable Blockchain Virtual Machine" -repository = "https://github.com/sorpaas/rust-evm" -keywords = ["no_std", "ethereum"] -edition = "2018" - -[dependencies] -auto_impl = "1.0" -environmental = { version = "1.1.2", default-features = false, optional = true } -primitive-types = { version = "0.12", default-features = false } -sha3 = { version = "0.10", default-features = false } - -evm-core = { version = "0.41", path = "../core", default-features = false } - -[features] -default = ["std"] -std = [ - "environmental/std", - "primitive-types/std", - "sha3/std", - "evm-core/std", -] -tracing = [ - "environmental", -] diff --git a/runtime/src/context.rs b/runtime/src/context.rs deleted file mode 100644 index ce6708b05..000000000 --- a/runtime/src/context.rs +++ /dev/null @@ -1,46 +0,0 @@ -use primitive_types::{H160, H256, U256}; - -/// Create scheme. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum CreateScheme { - /// Legacy create scheme of `CREATE`. - Legacy { - /// Caller of the create. - caller: H160, - }, - /// Create scheme of `CREATE2`. - Create2 { - /// Caller of the create. - caller: H160, - /// Code hash. - code_hash: H256, - /// Salt. - salt: H256, - }, - /// Create at a fixed location. - Fixed(H160), -} - -/// Call scheme. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum CallScheme { - /// `CALL` - Call, - /// `CALLCODE` - CallCode, - /// `DELEGATECALL` - DelegateCall, - /// `STATICCALL` - StaticCall, -} - -/// Context of the runtime. -#[derive(Clone, Debug)] -pub struct Context { - /// Execution address. - pub address: H160, - /// Caller of the EVM. - pub caller: H160, - /// Apparent value of the EVM. - pub apparent_value: U256, -} diff --git a/runtime/src/eval/macros.rs b/runtime/src/eval/macros.rs deleted file mode 100644 index 076866fba..000000000 --- a/runtime/src/eval/macros.rs +++ /dev/null @@ -1,72 +0,0 @@ -macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return Control::Exit(e.into()), - } - }; -} - -macro_rules! pop { - ( $machine:expr, $( $x:ident ),* ) => ( - $( - let $x = match $machine.machine.stack_mut().pop() { - Ok(value) => value, - Err(e) => return Control::Exit(e.into()), - }; - )* - ); -} - -macro_rules! pop_u256 { - ( $machine:expr, $( $x:ident ),* ) => ( - $( - let $x = match $machine.machine.stack_mut().pop() { - Ok(value) => U256::from_big_endian(&value[..]), - Err(e) => return Control::Exit(e.into()), - }; - )* - ); -} - -macro_rules! push { - ( $machine:expr, $( $x:expr ),* ) => ( - $( - match $machine.machine.stack_mut().push($x) { - Ok(()) => (), - Err(e) => return Control::Exit(e.into()), - } - )* - ) -} - -macro_rules! push_u256 { - ( $machine:expr, $( $x:expr ),* ) => ( - $( - let mut value = H256::default(); - $x.to_big_endian(&mut value[..]); - match $machine.machine.stack_mut().push(value) { - Ok(()) => (), - Err(e) => return Control::Exit(e.into()), - } - )* - ) -} - -macro_rules! as_usize_or_fail { - ( $v:expr ) => {{ - if $v > U256::from(usize::MAX) { - return Control::Exit(ExitFatal::NotSupported.into()); - } - - $v.as_usize() - }}; - - ( $v:expr, $reason:expr ) => {{ - if $v > U256::from(usize::MAX) { - return Control::Exit($reason.into()); - } - - $v.as_usize() - }}; -} diff --git a/runtime/src/eval/mod.rs b/runtime/src/eval/mod.rs deleted file mode 100644 index b2965335f..000000000 --- a/runtime/src/eval/mod.rs +++ /dev/null @@ -1,148 +0,0 @@ -#[macro_use] -mod macros; -mod system; - -use crate::{CallScheme, ExitReason, Handler, Opcode, Runtime}; -use alloc::vec::Vec; -use core::cmp::min; -use primitive_types::{H160, H256, U256}; - -pub enum Control { - Continue, - CallInterrupt(H::CallInterrupt), - CreateInterrupt(H::CreateInterrupt), - Exit(ExitReason), -} - -fn handle_other(state: &mut Runtime, opcode: Opcode, handler: &mut H) -> Control { - match handler.other(opcode, &mut state.machine) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e.into()), - } -} - -pub fn eval(state: &mut Runtime, opcode: Opcode, handler: &mut H) -> Control { - match opcode { - Opcode::SHA3 => system::sha3(state), - Opcode::ADDRESS => system::address(state), - Opcode::BALANCE => system::balance(state, handler), - Opcode::SELFBALANCE => system::selfbalance(state, handler), - Opcode::ORIGIN => system::origin(state, handler), - Opcode::CALLER => system::caller(state), - Opcode::CALLVALUE => system::callvalue(state), - Opcode::GASPRICE => system::gasprice(state, handler), - Opcode::EXTCODESIZE => system::extcodesize(state, handler), - Opcode::EXTCODEHASH => system::extcodehash(state, handler), - Opcode::EXTCODECOPY => system::extcodecopy(state, handler), - Opcode::RETURNDATASIZE => system::returndatasize(state), - Opcode::RETURNDATACOPY => system::returndatacopy(state), - Opcode::BLOCKHASH => system::blockhash(state, handler), - Opcode::COINBASE => system::coinbase(state, handler), - Opcode::TIMESTAMP => system::timestamp(state, handler), - Opcode::NUMBER => system::number(state, handler), - Opcode::DIFFICULTY => system::prevrandao(state, handler), - Opcode::GASLIMIT => system::gaslimit(state, handler), - Opcode::SLOAD => system::sload(state, handler), - Opcode::SSTORE => system::sstore(state, handler), - Opcode::GAS => system::gas(state, handler), - Opcode::LOG0 => system::log(state, 0, handler), - Opcode::LOG1 => system::log(state, 1, handler), - Opcode::LOG2 => system::log(state, 2, handler), - Opcode::LOG3 => system::log(state, 3, handler), - Opcode::LOG4 => system::log(state, 4, handler), - Opcode::SUICIDE => system::suicide(state, handler), - Opcode::CREATE => system::create(state, false, handler), - Opcode::CREATE2 => system::create(state, true, handler), - Opcode::CALL => system::call(state, CallScheme::Call, handler), - Opcode::CALLCODE => system::call(state, CallScheme::CallCode, handler), - Opcode::DELEGATECALL => system::call(state, CallScheme::DelegateCall, handler), - Opcode::STATICCALL => system::call(state, CallScheme::StaticCall, handler), - Opcode::CHAINID => system::chainid(state, handler), - Opcode::BASEFEE => system::base_fee(state, handler), - _ => handle_other(state, opcode, handler), - } -} - -pub fn finish_create( - runtime: &mut Runtime, - reason: ExitReason, - address: Option, - return_data: Vec, -) -> Result<(), ExitReason> { - runtime.return_data_buffer = return_data; - let create_address: H256 = address.map(|a| a.into()).unwrap_or_default(); - - match reason { - ExitReason::Succeed(_) => { - runtime.machine.stack_mut().push(create_address)?; - Ok(()) - } - ExitReason::Revert(_) => { - runtime.machine.stack_mut().push(H256::default())?; - Ok(()) - } - ExitReason::Error(_) => { - runtime.machine.stack_mut().push(H256::default())?; - Ok(()) - } - ExitReason::Fatal(e) => { - runtime.machine.stack_mut().push(H256::default())?; - Err(e.into()) - } - } -} - -pub fn finish_call( - runtime: &mut Runtime, - out_len: U256, - out_offset: U256, - reason: ExitReason, - return_data: Vec, -) -> Result<(), ExitReason> { - runtime.return_data_buffer = return_data; - let target_len = min(out_len, U256::from(runtime.return_data_buffer.len())); - - match reason { - ExitReason::Succeed(_) => { - match runtime.machine.memory_mut().copy_large( - out_offset, - U256::zero(), - target_len, - &runtime.return_data_buffer[..], - ) { - Ok(()) => { - let mut value = H256::default(); - U256::one().to_big_endian(&mut value[..]); - runtime.machine.stack_mut().push(value)?; - Ok(()) - } - Err(_) => { - runtime.machine.stack_mut().push(H256::default())?; - Ok(()) - } - } - } - ExitReason::Revert(_) => { - runtime.machine.stack_mut().push(H256::default())?; - - let _ = runtime.machine.memory_mut().copy_large( - out_offset, - U256::zero(), - target_len, - &runtime.return_data_buffer[..], - ); - - Ok(()) - } - ExitReason::Error(_) => { - runtime.machine.stack_mut().push(H256::default())?; - - Ok(()) - } - ExitReason::Fatal(e) => { - runtime.machine.stack_mut().push(H256::default())?; - - Err(e.into()) - } - } -} diff --git a/runtime/src/eval/system.rs b/runtime/src/eval/system.rs deleted file mode 100644 index adf6bffd1..000000000 --- a/runtime/src/eval/system.rs +++ /dev/null @@ -1,428 +0,0 @@ -use super::Control; -use crate::{ - CallScheme, Capture, Context, CreateScheme, ExitError, ExitFatal, ExitSucceed, Handler, - Runtime, Transfer, -}; -use alloc::vec::Vec; -use primitive_types::{H256, U256}; -use sha3::{Digest, Keccak256}; - -pub fn sha3(runtime: &mut Runtime) -> Control { - pop_u256!(runtime, from, len); - - try_or_fail!(runtime.machine.memory_mut().resize_offset(from, len)); - let data = if len == U256::zero() { - Vec::new() - } else { - let from = as_usize_or_fail!(from); - let len = as_usize_or_fail!(len); - - runtime.machine.memory_mut().get(from, len) - }; - - let ret = Keccak256::digest(data.as_slice()); - push!(runtime, H256::from_slice(ret.as_slice())); - - Control::Continue -} - -pub fn chainid(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.chain_id()); - - Control::Continue -} - -pub fn address(runtime: &mut Runtime) -> Control { - let ret = H256::from(runtime.context.address); - push!(runtime, ret); - - Control::Continue -} - -pub fn balance(runtime: &mut Runtime, handler: &H) -> Control { - pop!(runtime, address); - push_u256!(runtime, handler.balance(address.into())); - - Control::Continue -} - -pub fn selfbalance(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.balance(runtime.context.address)); - - Control::Continue -} - -pub fn origin(runtime: &mut Runtime, handler: &H) -> Control { - let ret = H256::from(handler.origin()); - push!(runtime, ret); - - Control::Continue -} - -pub fn caller(runtime: &mut Runtime) -> Control { - let ret = H256::from(runtime.context.caller); - push!(runtime, ret); - - Control::Continue -} - -pub fn callvalue(runtime: &mut Runtime) -> Control { - let mut ret = H256::default(); - runtime.context.apparent_value.to_big_endian(&mut ret[..]); - push!(runtime, ret); - - Control::Continue -} - -pub fn gasprice(runtime: &mut Runtime, handler: &H) -> Control { - let mut ret = H256::default(); - handler.gas_price().to_big_endian(&mut ret[..]); - push!(runtime, ret); - - Control::Continue -} - -pub fn base_fee(runtime: &mut Runtime, handler: &H) -> Control { - let mut ret = H256::default(); - handler.block_base_fee_per_gas().to_big_endian(&mut ret[..]); - push!(runtime, ret); - - Control::Continue -} - -pub fn extcodesize(runtime: &mut Runtime, handler: &mut H) -> Control { - pop!(runtime, address); - if let Err(e) = - handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) - { - return Control::Exit(e.into()); - } - let code_size = handler.code_size(address.into()); - push_u256!(runtime, code_size); - - Control::Continue -} - -pub fn extcodehash(runtime: &mut Runtime, handler: &mut H) -> Control { - pop!(runtime, address); - if let Err(e) = - handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) - { - return Control::Exit(e.into()); - } - let code_hash = handler.code_hash(address.into()); - push!(runtime, code_hash); - - Control::Continue -} - -pub fn extcodecopy(runtime: &mut Runtime, handler: &mut H) -> Control { - pop!(runtime, address); - pop_u256!(runtime, memory_offset, code_offset, len); - - try_or_fail!(runtime - .machine - .memory_mut() - .resize_offset(memory_offset, len)); - - if let Err(e) = - handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) - { - return Control::Exit(e.into()); - } - let code = handler.code(address.into()); - match runtime - .machine - .memory_mut() - .copy_large(memory_offset, code_offset, len, &code) - { - Ok(()) => (), - Err(e) => return Control::Exit(e.into()), - }; - - Control::Continue -} - -pub fn returndatasize(runtime: &mut Runtime) -> Control { - let size = U256::from(runtime.return_data_buffer.len()); - push_u256!(runtime, size); - - Control::Continue -} - -pub fn returndatacopy(runtime: &mut Runtime) -> Control { - pop_u256!(runtime, memory_offset, data_offset, len); - - try_or_fail!(runtime - .machine - .memory_mut() - .resize_offset(memory_offset, len)); - if data_offset - .checked_add(len) - .map(|l| l > U256::from(runtime.return_data_buffer.len())) - .unwrap_or(true) - { - return Control::Exit(ExitError::OutOfOffset.into()); - } - - match runtime.machine.memory_mut().copy_large( - memory_offset, - data_offset, - len, - &runtime.return_data_buffer, - ) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e.into()), - } -} - -pub fn blockhash(runtime: &mut Runtime, handler: &H) -> Control { - pop_u256!(runtime, number); - push!(runtime, handler.block_hash(number)); - - Control::Continue -} - -pub fn coinbase(runtime: &mut Runtime, handler: &H) -> Control { - push!(runtime, handler.block_coinbase().into()); - Control::Continue -} - -pub fn timestamp(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.block_timestamp()); - Control::Continue -} - -pub fn number(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.block_number()); - Control::Continue -} - -pub fn difficulty(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.block_difficulty()); - Control::Continue -} - -pub fn prevrandao(runtime: &mut Runtime, handler: &H) -> Control { - if let Some(rand) = handler.block_randomness() { - push!(runtime, rand); - Control::Continue - } else { - difficulty(runtime, handler) - } -} - -pub fn gaslimit(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.block_gas_limit()); - Control::Continue -} - -pub fn sload(runtime: &mut Runtime, handler: &H) -> Control { - pop!(runtime, index); - let value = handler.storage(runtime.context.address, index); - push!(runtime, value); - - event!(SLoad { - address: runtime.context.address, - index, - value - }); - - Control::Continue -} - -pub fn sstore(runtime: &mut Runtime, handler: &mut H) -> Control { - pop!(runtime, index, value); - - event!(SStore { - address: runtime.context.address, - index, - value - }); - - match handler.set_storage(runtime.context.address, index, value) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e.into()), - } -} - -pub fn gas(runtime: &mut Runtime, handler: &H) -> Control { - push_u256!(runtime, handler.gas_left()); - - Control::Continue -} - -pub fn log(runtime: &mut Runtime, n: u8, handler: &mut H) -> Control { - pop_u256!(runtime, offset, len); - - try_or_fail!(runtime.machine.memory_mut().resize_offset(offset, len)); - let data = if len == U256::zero() { - Vec::new() - } else { - let offset = as_usize_or_fail!(offset); - let len = as_usize_or_fail!(len); - - runtime.machine.memory().get(offset, len) - }; - - let mut topics = Vec::new(); - for _ in 0..(n as usize) { - match runtime.machine.stack_mut().pop() { - Ok(value) => { - topics.push(value); - } - Err(e) => return Control::Exit(e.into()), - } - } - - match handler.log(runtime.context.address, topics, data) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e.into()), - } -} - -pub fn suicide(runtime: &mut Runtime, handler: &mut H) -> Control { - pop!(runtime, target); - - match handler.mark_delete(runtime.context.address, target.into()) { - Ok(()) => (), - Err(e) => return Control::Exit(e.into()), - } - - Control::Exit(ExitSucceed::Suicided.into()) -} - -pub fn create(runtime: &mut Runtime, is_create2: bool, handler: &mut H) -> Control { - runtime.return_data_buffer = Vec::new(); - - pop_u256!(runtime, value, code_offset, len); - - try_or_fail!(runtime.machine.memory_mut().resize_offset(code_offset, len)); - let code = if len == U256::zero() { - Vec::new() - } else { - let code_offset = as_usize_or_fail!(code_offset); - let len = as_usize_or_fail!(len); - - runtime.machine.memory().get(code_offset, len) - }; - - let scheme = if is_create2 { - pop!(runtime, salt); - let code_hash = H256::from_slice(Keccak256::digest(&code).as_slice()); - CreateScheme::Create2 { - caller: runtime.context.address, - salt, - code_hash, - } - } else { - CreateScheme::Legacy { - caller: runtime.context.address, - } - }; - - match handler.create(runtime.context.address, scheme, value, code, None) { - Capture::Exit((reason, address, return_data)) => { - match super::finish_create(runtime, reason, address, return_data) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e), - } - } - Capture::Trap(interrupt) => Control::CreateInterrupt(interrupt), - } -} - -pub fn call(runtime: &mut Runtime, scheme: CallScheme, handler: &mut H) -> Control { - runtime.return_data_buffer = Vec::new(); - - pop_u256!(runtime, gas); - pop!(runtime, to); - let gas = if gas > U256::from(u64::MAX) { - None - } else { - Some(gas.as_u64()) - }; - - let value = match scheme { - CallScheme::Call | CallScheme::CallCode => { - pop_u256!(runtime, value); - value - } - CallScheme::DelegateCall | CallScheme::StaticCall => U256::zero(), - }; - - pop_u256!(runtime, in_offset, in_len, out_offset, out_len); - - try_or_fail!(runtime - .machine - .memory_mut() - .resize_offset(in_offset, in_len)); - try_or_fail!(runtime - .machine - .memory_mut() - .resize_offset(out_offset, out_len)); - - let input = if in_len == U256::zero() { - Vec::new() - } else { - let in_offset = as_usize_or_fail!(in_offset); - let in_len = as_usize_or_fail!(in_len); - - runtime.machine.memory().get(in_offset, in_len) - }; - - let context = match scheme { - CallScheme::Call | CallScheme::StaticCall => Context { - address: to.into(), - caller: runtime.context.address, - apparent_value: value, - }, - CallScheme::CallCode => Context { - address: runtime.context.address, - caller: runtime.context.address, - apparent_value: value, - }, - CallScheme::DelegateCall => Context { - address: runtime.context.address, - caller: runtime.context.caller, - apparent_value: runtime.context.apparent_value, - }, - }; - - let transfer = if scheme == CallScheme::Call { - Some(Transfer { - source: runtime.context.address, - target: to.into(), - value, - }) - } else if scheme == CallScheme::CallCode { - Some(Transfer { - source: runtime.context.address, - target: runtime.context.address, - value, - }) - } else { - None - }; - - match handler.call( - to.into(), - transfer, - input, - gas, - scheme == CallScheme::StaticCall, - context, - ) { - Capture::Exit((reason, return_data)) => { - match super::finish_call(runtime, out_len, out_offset, reason, return_data) { - Ok(()) => Control::Continue, - Err(e) => Control::Exit(e), - } - } - Capture::Trap(interrupt) => { - runtime.return_data_len = out_len; - runtime.return_data_offset = out_offset; - Control::CallInterrupt(interrupt) - } - } -} diff --git a/runtime/src/handler.rs b/runtime/src/handler.rs deleted file mode 100644 index 10eb976c7..000000000 --- a/runtime/src/handler.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::{Capture, Context, CreateScheme, ExitError, ExitReason, Machine, Opcode, Stack}; -use alloc::vec::Vec; -use primitive_types::{H160, H256, U256}; - -/// Transfer from source to target, with given value. -#[derive(Clone, Debug)] -pub struct Transfer { - /// Source address. - pub source: H160, - /// Target address. - pub target: H160, - /// Transfer value. - pub value: U256, -} - -/// EVM context handler. -#[auto_impl::auto_impl(&mut, Box)] -pub trait Handler { - /// Type of `CREATE` interrupt. - type CreateInterrupt; - /// Feedback value for `CREATE` interrupt. - type CreateFeedback; - /// Type of `CALL` interrupt. - type CallInterrupt; - /// Feedback value of `CALL` interrupt. - type CallFeedback; - - /// Get balance of address. - fn balance(&self, address: H160) -> U256; - /// Get code size of address. - fn code_size(&self, address: H160) -> U256; - /// Get code hash of address. - fn code_hash(&self, address: H160) -> H256; - /// Get code of address. - fn code(&self, address: H160) -> Vec; - /// Get storage value of address at index. - fn storage(&self, address: H160, index: H256) -> H256; - /// Get original storage value of address at index. - fn original_storage(&self, address: H160, index: H256) -> H256; - - /// Get the gas left value. - fn gas_left(&self) -> U256; - /// Get the gas price value. - fn gas_price(&self) -> U256; - /// Get execution origin. - fn origin(&self) -> H160; - /// Get environmental block hash. - fn block_hash(&self, number: U256) -> H256; - /// Get environmental block number. - fn block_number(&self) -> U256; - /// Get environmental coinbase. - fn block_coinbase(&self) -> H160; - /// Get environmental block timestamp. - fn block_timestamp(&self) -> U256; - /// Get environmental block difficulty. - fn block_difficulty(&self) -> U256; - /// Get environmental block randomness. - fn block_randomness(&self) -> Option; - /// Get environmental gas limit. - fn block_gas_limit(&self) -> U256; - /// Environmental block base fee. - fn block_base_fee_per_gas(&self) -> U256; - /// Get environmental chain ID. - fn chain_id(&self) -> U256; - - /// Check whether an address exists. - fn exists(&self, address: H160) -> bool; - /// Check whether an address has already been deleted. - fn deleted(&self, address: H160) -> bool; - /// Checks if the address or (address, index) pair has been previously accessed - /// (or set in `accessed_addresses` / `accessed_storage_keys` via an access list - /// transaction). - /// References: - /// * - /// * - fn is_cold(&mut self, address: H160, index: Option) -> Result; - - /// Set storage value of address at index. - fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError>; - /// Create a log owned by address with given topics and data. - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError>; - /// Mark an address to be deleted, with funds transferred to target. - fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError>; - /// Invoke a create operation. - fn create( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt>; - /// Feed in create feedback. - fn create_feedback(&mut self, _feedback: Self::CreateFeedback) -> Result<(), ExitError> { - Ok(()) - } - /// Invoke a call operation. - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), Self::CallInterrupt>; - /// Feed in call feedback. - fn call_feedback(&mut self, _feedback: Self::CallFeedback) -> Result<(), ExitError> { - Ok(()) - } - - /// Pre-validation step for the runtime. - fn pre_validate( - &mut self, - context: &Context, - opcode: Opcode, - stack: &Stack, - ) -> Result<(), ExitError>; - /// Handle other unknown external opcodes. - fn other(&mut self, opcode: Opcode, _stack: &mut Machine) -> Result<(), ExitError> { - Err(ExitError::InvalidCode(opcode)) - } - - /// Records some associated `ExternalOperation`. - fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError>; -} diff --git a/runtime/src/interrupt.rs b/runtime/src/interrupt.rs deleted file mode 100644 index 188fc5127..000000000 --- a/runtime/src/interrupt.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{Handler, Runtime}; - -/// Interrupt resolution. -pub enum Resolve<'a, H: Handler> { - /// Create interrupt resolution. - Create(H::CreateInterrupt, ResolveCreate<'a>), - /// Call interrupt resolution. - Call(H::CallInterrupt, ResolveCall<'a>), -} - -/// Create interrupt resolution. -pub struct ResolveCreate<'a> { - _runtime: &'a mut Runtime, -} - -impl<'a> ResolveCreate<'a> { - pub(crate) fn new(runtime: &'a mut Runtime) -> Self { - Self { _runtime: runtime } - } -} - -/// Call interrupt resolution. -pub struct ResolveCall<'a> { - _runtime: &'a mut Runtime, -} - -impl<'a> ResolveCall<'a> { - pub(crate) fn new(runtime: &'a mut Runtime) -> Self { - Self { _runtime: runtime } - } -} diff --git a/runtime/src/tracing.rs b/runtime/src/tracing.rs deleted file mode 100644 index f9fed1961..000000000 --- a/runtime/src/tracing.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Allows to listen to runtime events. - -use crate::{Capture, Context, ExitReason, Memory, Opcode, Stack, Trap}; -use primitive_types::{H160, H256}; - -environmental::environmental!(listener: dyn EventListener + 'static); - -pub trait EventListener { - fn event(&mut self, event: Event<'_>); -} - -#[derive(Debug, Copy, Clone)] -pub enum Event<'a> { - Step { - context: &'a Context, - opcode: Opcode, - position: &'a Result, - stack: &'a Stack, - memory: &'a Memory, - }, - StepResult { - result: &'a Result<(), Capture>, - return_value: &'a [u8], - }, - SLoad { - address: H160, - index: H256, - value: H256, - }, - SStore { - address: H160, - index: H256, - value: H256, - }, -} - -// Expose `listener::with` to the crate only. -pub(crate) fn with(f: F) { - listener::with(f); -} - -/// Run closure with provided listener. -pub fn using R>(new: &mut (dyn EventListener + 'static), f: F) -> R { - listener::using(new, f) -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 294c8a2ca..1a15b5bce 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.68.2" +channel = "nightly" profile = "minimal" components = [ "rustfmt", "clippy" ] diff --git a/src/backend/in_memory.rs b/src/backend/in_memory.rs new file mode 100644 index 000000000..fe696dd55 --- /dev/null +++ b/src/backend/in_memory.rs @@ -0,0 +1,279 @@ +use crate::{ + ExitError, ExitException, Log, MergeStrategy, RuntimeBackend, RuntimeBaseBackend, + RuntimeEnvironment, TransactionalBackend, +}; +use alloc::collections::{BTreeMap, BTreeSet}; +use primitive_types::{H160, H256, U256}; +use std::mem; + +#[derive(Clone, Debug)] +pub struct InMemoryEnvironment { + pub block_hashes: BTreeMap, + pub block_number: U256, + pub block_coinbase: H160, + pub block_timestamp: U256, + pub block_difficulty: U256, + pub block_randomness: Option, + pub block_gas_limit: U256, + pub block_base_fee_per_gas: U256, + pub chain_id: U256, +} + +#[derive(Clone, Debug, Default)] +pub struct InMemoryAccount { + pub balance: U256, + pub code: Vec, + pub nonce: U256, + pub storage: BTreeMap, + pub original_storage: BTreeMap, +} + +#[derive(Clone, Debug)] +pub struct InMemorySuicideInfo { + pub address: H160, +} + +#[derive(Clone, Debug)] +pub struct InMemoryLayer { + pub state: BTreeMap, + pub logs: Vec, + pub suicides: Vec, + pub hots: BTreeSet<(H160, Option)>, +} + +impl InMemoryLayer { + pub fn clear_pending(&mut self) { + self.hots.clear(); + + let mut suicides = Vec::new(); + mem::swap(&mut suicides, &mut self.suicides); + for suicide in suicides { + self.state.remove(&suicide.address); + } + } +} + +#[derive(Clone, Debug)] +pub struct InMemoryBackend { + pub environment: InMemoryEnvironment, + pub layers: Vec, +} + +impl InMemoryBackend { + pub fn current_layer(&self) -> &InMemoryLayer { + self.layers.last().expect("current layer exists") + } + + pub fn current_layer_mut(&mut self) -> &mut InMemoryLayer { + self.layers.last_mut().expect("current layer exists") + } +} + +impl RuntimeEnvironment for InMemoryBackend { + fn block_hash(&self, number: U256) -> H256 { + self.environment + .block_hashes + .get(&number) + .cloned() + .unwrap_or(H256::default()) + } + + fn block_number(&self) -> U256 { + self.environment.block_number + } + + fn block_coinbase(&self) -> H160 { + self.environment.block_coinbase + } + + fn block_timestamp(&self) -> U256 { + self.environment.block_timestamp + } + + fn block_difficulty(&self) -> U256 { + self.environment.block_difficulty + } + + fn block_randomness(&self) -> Option { + self.environment.block_randomness + } + + fn block_gas_limit(&self) -> U256 { + self.environment.block_gas_limit + } + + fn block_base_fee_per_gas(&self) -> U256 { + self.environment.block_base_fee_per_gas + } + + fn chain_id(&self) -> U256 { + self.environment.chain_id + } +} + +impl RuntimeBaseBackend for InMemoryBackend { + fn balance(&self, address: H160) -> U256 { + self.current_layer() + .state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .balance + } + + fn code(&self, address: H160) -> Vec { + self.current_layer() + .state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .code + } + + fn exists(&self, address: H160) -> bool { + self.current_layer().state.get(&address).is_some() + } + + fn storage(&self, address: H160, index: H256) -> H256 { + self.current_layer() + .state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .storage + .get(&index) + .cloned() + .unwrap_or(H256::default()) + } + + fn nonce(&self, address: H160) -> U256 { + self.current_layer() + .state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .nonce + } +} + +impl RuntimeBackend for InMemoryBackend { + fn original_storage(&self, address: H160, index: H256) -> H256 { + self.current_layer() + .state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .original_storage + .get(&index) + .cloned() + .unwrap_or(H256::default()) + } + + fn deleted(&self, address: H160) -> bool { + self.current_layer() + .suicides + .iter() + .any(|suicide| suicide.address == address) + } + + fn is_cold(&self, address: H160, index: Option) -> bool { + !self.current_layer().hots.contains(&(address, index)) + } + + fn mark_hot(&mut self, address: H160, index: Option) { + self.current_layer_mut().hots.insert((address, index)); + } + + fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> { + let entry = self.current_layer_mut().state.entry(address).or_default(); + + if value == H256::default() { + entry.storage.remove(&index); + } else { + entry.storage.insert(index, value); + } + Ok(()) + } + + fn log(&mut self, log: Log) -> Result<(), ExitError> { + self.current_layer_mut().logs.push(log); + Ok(()) + } + + fn mark_delete(&mut self, address: H160) { + self.current_layer_mut() + .suicides + .push(InMemorySuicideInfo { address }); + } + + fn reset_storage(&mut self, address: H160) { + self.current_layer_mut() + .state + .entry(address) + .or_default() + .storage = Default::default(); + } + + fn set_code(&mut self, address: H160, code: Vec) -> Result<(), ExitError> { + self.current_layer_mut() + .state + .entry(address) + .or_default() + .code = code; + + Ok(()) + } + + fn reset_balance(&mut self, address: H160) { + self.current_layer_mut() + .state + .entry(address) + .or_default() + .balance = U256::zero(); + } + + fn withdrawal(&mut self, source: H160, value: U256) -> Result<(), ExitError> { + let source = self.current_layer_mut().state.entry(source).or_default(); + if source.balance < value { + return Err(ExitException::OutOfFund.into()); + } + source.balance -= value; + Ok(()) + } + + fn deposit(&mut self, target: H160, value: U256) { + if value == U256::zero() { + return; + } + + self.current_layer_mut() + .state + .entry(target) + .or_default() + .balance += value; + } + + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { + let entry = self.current_layer_mut().state.entry(address).or_default(); + entry.nonce = entry.nonce.saturating_add(U256::one()); + Ok(()) + } +} + +impl TransactionalBackend for InMemoryBackend { + fn push_substate(&mut self) { + let layer = self.current_layer().clone(); + self.layers.push(layer); + } + + fn pop_substate(&mut self, strategy: MergeStrategy) { + let layer = self.layers.pop().expect("current layer exist"); + + match strategy { + MergeStrategy::Commit => { + *self.current_layer_mut() = layer; + } + MergeStrategy::Discard | MergeStrategy::Revert => (), + } + } +} diff --git a/src/backend/memory.rs b/src/backend/memory.rs deleted file mode 100644 index 37e7718b1..000000000 --- a/src/backend/memory.rs +++ /dev/null @@ -1,231 +0,0 @@ -use super::{Apply, ApplyBackend, Backend, Basic, Log}; -use alloc::collections::BTreeMap; -use alloc::vec::Vec; -use primitive_types::{H160, H256, U256}; - -/// Vicinity value of a memory backend. -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr( - feature = "with-codec", - derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) -)] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub struct MemoryVicinity { - /// Gas price. - pub gas_price: U256, - /// Origin. - pub origin: H160, - /// Chain ID. - pub chain_id: U256, - /// Environmental block hashes. - pub block_hashes: Vec, - /// Environmental block number. - pub block_number: U256, - /// Environmental coinbase. - pub block_coinbase: H160, - /// Environmental block timestamp. - pub block_timestamp: U256, - /// Environmental block difficulty. - pub block_difficulty: U256, - /// Environmental block gas limit. - pub block_gas_limit: U256, - /// Environmental base fee per gas. - pub block_base_fee_per_gas: U256, - /// Environmental randomness. - /// - /// In Ethereum, this is the randomness beacon provided by the beacon - /// chain and is only enabled post Merge. - pub block_randomness: Option, -} - -/// Account information of a memory backend. -#[derive(Default, Clone, Debug, Eq, PartialEq)] -#[cfg_attr( - feature = "with-codec", - derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) -)] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub struct MemoryAccount { - /// Account nonce. - pub nonce: U256, - /// Account balance. - pub balance: U256, - /// Full account storage. - pub storage: BTreeMap, - /// Account code. - pub code: Vec, -} - -/// Memory backend, storing all state values in a `BTreeMap` in memory. -#[derive(Clone, Debug)] -pub struct MemoryBackend<'vicinity> { - vicinity: &'vicinity MemoryVicinity, - state: BTreeMap, - logs: Vec, -} - -impl<'vicinity> MemoryBackend<'vicinity> { - /// Create a new memory backend. - pub fn new(vicinity: &'vicinity MemoryVicinity, state: BTreeMap) -> Self { - Self { - vicinity, - state, - logs: Vec::new(), - } - } - - /// Get the underlying `BTreeMap` storing the state. - pub fn state(&self) -> &BTreeMap { - &self.state - } - - /// Get a mutable reference to the underlying `BTreeMap` storing the state. - pub fn state_mut(&mut self) -> &mut BTreeMap { - &mut self.state - } -} - -impl<'vicinity> Backend for MemoryBackend<'vicinity> { - fn gas_price(&self) -> U256 { - self.vicinity.gas_price - } - fn origin(&self) -> H160 { - self.vicinity.origin - } - fn block_hash(&self, number: U256) -> H256 { - if number >= self.vicinity.block_number - || self.vicinity.block_number - number - U256::one() - >= U256::from(self.vicinity.block_hashes.len()) - { - H256::default() - } else { - let index = (self.vicinity.block_number - number - U256::one()).as_usize(); - self.vicinity.block_hashes[index] - } - } - fn block_number(&self) -> U256 { - self.vicinity.block_number - } - fn block_coinbase(&self) -> H160 { - self.vicinity.block_coinbase - } - fn block_timestamp(&self) -> U256 { - self.vicinity.block_timestamp - } - fn block_difficulty(&self) -> U256 { - self.vicinity.block_difficulty - } - fn block_randomness(&self) -> Option { - self.vicinity.block_randomness - } - fn block_gas_limit(&self) -> U256 { - self.vicinity.block_gas_limit - } - fn block_base_fee_per_gas(&self) -> U256 { - self.vicinity.block_base_fee_per_gas - } - - fn chain_id(&self) -> U256 { - self.vicinity.chain_id - } - - fn exists(&self, address: H160) -> bool { - self.state.contains_key(&address) - } - - fn basic(&self, address: H160) -> Basic { - self.state - .get(&address) - .map(|a| Basic { - balance: a.balance, - nonce: a.nonce, - }) - .unwrap_or_default() - } - - fn code(&self, address: H160) -> Vec { - self.state - .get(&address) - .map(|v| v.code.clone()) - .unwrap_or_default() - } - - fn storage(&self, address: H160, index: H256) -> H256 { - self.state - .get(&address) - .map(|v| v.storage.get(&index).cloned().unwrap_or_default()) - .unwrap_or_default() - } - - fn original_storage(&self, address: H160, index: H256) -> Option { - Some(self.storage(address, index)) - } -} - -impl<'vicinity> ApplyBackend for MemoryBackend<'vicinity> { - fn apply(&mut self, values: A, logs: L, delete_empty: bool) - where - A: IntoIterator>, - I: IntoIterator, - L: IntoIterator, - { - for apply in values { - match apply { - Apply::Modify { - address, - basic, - code, - storage, - reset_storage, - } => { - let is_empty = { - let account = self.state.entry(address).or_insert_with(Default::default); - account.balance = basic.balance; - account.nonce = basic.nonce; - if let Some(code) = code { - account.code = code; - } - - if reset_storage { - account.storage = BTreeMap::new(); - } - - let zeros = account - .storage - .iter() - .filter(|(_, v)| v == &&H256::default()) - .map(|(k, _)| *k) - .collect::>(); - - for zero in zeros { - account.storage.remove(&zero); - } - - for (index, value) in storage { - if value == H256::default() { - account.storage.remove(&index); - } else { - account.storage.insert(index, value); - } - } - - account.balance == U256::zero() - && account.nonce == U256::zero() - && account.code.is_empty() - }; - - if is_empty && delete_empty { - self.state.remove(&address); - } - } - Apply::Delete { address } => { - self.state.remove(&address); - } - } - } - - for log in logs { - self.logs.push(log); - } - } -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 297f475f7..900fa7b5f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,96 +1,6 @@ -//! # EVM backends -//! -//! Backends store state information of the VM, and exposes it to runtime. +pub mod in_memory; -mod memory; - -pub use self::memory::{MemoryAccount, MemoryBackend, MemoryVicinity}; -use alloc::vec::Vec; -use primitive_types::{H160, H256, U256}; -/// Basic account information. -#[derive(Clone, Eq, PartialEq, Debug, Default)] -#[cfg_attr( - feature = "with-codec", - derive(scale_codec::Encode, scale_codec::Decode, scale_info::TypeInfo) -)] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Basic { - /// Account balance. - pub balance: U256, - /// Account nonce. - pub nonce: U256, -} - -pub use ethereum::Log; - -/// Apply state operation. -#[derive(Clone, Debug)] -pub enum Apply { - /// Modify or create at address. - Modify { - /// Address. - address: H160, - /// Basic information of the address. - basic: Basic, - /// Code. `None` means leaving it unchanged. - code: Option>, - /// Storage iterator. - storage: I, - /// Whether storage should be wiped empty before applying the storage - /// iterator. - reset_storage: bool, - }, - /// Delete address. - Delete { - /// Address. - address: H160, - }, -} - -/// EVM backend. -#[auto_impl::auto_impl(&, Arc, Box)] -pub trait Backend { - /// Gas price. Unused for London. - fn gas_price(&self) -> U256; - /// Origin. - fn origin(&self) -> H160; - /// Environmental block hash. - fn block_hash(&self, number: U256) -> H256; - /// Environmental block number. - fn block_number(&self) -> U256; - /// Environmental coinbase. - fn block_coinbase(&self) -> H160; - /// Environmental block timestamp. - fn block_timestamp(&self) -> U256; - /// Environmental block difficulty. - fn block_difficulty(&self) -> U256; - /// Get environmental block randomness. - fn block_randomness(&self) -> Option; - /// Environmental block gas limit. - fn block_gas_limit(&self) -> U256; - /// Environmental block base fee. - fn block_base_fee_per_gas(&self) -> U256; - /// Environmental chain ID. - fn chain_id(&self) -> U256; - - /// Whether account at address exists. - fn exists(&self, address: H160) -> bool; - /// Get basic account information. - fn basic(&self, address: H160) -> Basic; - /// Get account code. - fn code(&self, address: H160) -> Vec; - /// Get storage value of address at index. - fn storage(&self, address: H160, index: H256) -> H256; - /// Get original storage value of address at index, if available. - fn original_storage(&self, address: H160, index: H256) -> Option; -} - -/// EVM backend that can apply changes. -pub trait ApplyBackend { - /// Apply given values and logs at backend. - fn apply(&mut self, values: A, logs: L, delete_empty: bool) - where - A: IntoIterator>, - I: IntoIterator, - L: IntoIterator; +pub trait TransactionalBackend { + fn push_substate(&mut self); + fn pop_substate(&mut self, strategy: crate::MergeStrategy); } diff --git a/src/call_stack.rs b/src/call_stack.rs new file mode 100644 index 000000000..1dd622e46 --- /dev/null +++ b/src/call_stack.rs @@ -0,0 +1,491 @@ +use crate::{Capture, ExitError, ExitFatal, ExitResult, Invoker, InvokerControl, InvokerMachine}; +use core::convert::Infallible; + +struct Substack { + invoke: TrD, + machine: M, +} + +struct LastSubstack { + machine: M, + status: LastSubstackStatus, +} + +enum LastSubstackStatus { + Running, + ExternalTrapped, + Exited(Capture), +} + +// Note: this should not be exposed to public because it does not implement +// Drop. +struct CallStack<'backend, 'invoker, H, Tr, I: Invoker> { + stack: Vec>, + last: Option>, + initial_depth: usize, + backend: &'backend mut H, + invoker: &'invoker I, +} + +impl<'backend, 'invoker, H, Tr, I> CallStack<'backend, 'invoker, H, Tr, I> +where + I: Invoker, +{ + pub fn new( + machine: I::Machine, + initial_depth: usize, + backend: &'backend mut H, + invoker: &'invoker I, + ) -> Self { + let call_stack = Self { + stack: Vec::new(), + last: Some(LastSubstack { + machine, + status: LastSubstackStatus::Running, + }), + initial_depth, + backend, + invoker, + }; + + call_stack + } + + pub fn run(&mut self) -> Capture, I::Interrupt> { + loop { + let step_ret = self.step_run(); + + if let Err(step_ret) = step_ret { + return step_ret; + } + } + } + + pub fn step( + &mut self, + ) -> Result<(), Capture, I::Interrupt>> { + self.step_with(|machine, handler| { + let result = machine.step(handler); + match result { + Ok(()) => LastSubstackStatus::Running, + Err(result) => LastSubstackStatus::Exited(result), + } + }) + } + + pub fn step_run( + &mut self, + ) -> Result<(), Capture, I::Interrupt>> { + self.step_with(|machine, handler| { + let result = machine.run(handler); + LastSubstackStatus::Exited(result) + }) + } + + fn step_with( + &mut self, + fs: FS, + ) -> Result<(), Capture, I::Interrupt>> + where + FS: Fn(&mut I::Machine, &mut H) -> LastSubstackStatus, + { + let mut step_ret = None; + + self.last = match self.last.take() { + None => { + step_ret = Some(Capture::Exit(Err(ExitFatal::AlreadyExited.into()))); + None + } + Some(LastSubstack { + status: LastSubstackStatus::ExternalTrapped, + machine, + }) => Some(LastSubstack { + status: LastSubstackStatus::Running, + machine, + }), + Some(LastSubstack { + status: LastSubstackStatus::Running, + mut machine, + }) => { + let status = fs(&mut machine, self.backend); + Some(LastSubstack { status, machine }) + } + Some(LastSubstack { + status: LastSubstackStatus::Exited(Capture::Exit(exit)), + machine, + }) => { + if self.stack.is_empty() { + step_ret = Some(Capture::Exit(Ok((exit, machine)))); + None + } else { + let mut upward = self + .stack + .pop() + .expect("checked stack is not empty above; qed"); + + let machine = machine.deconstruct(); + let feedback_result = self.invoker.exit_substack( + exit, + machine, + upward.invoke, + &mut upward.machine, + self.backend, + ); + + match feedback_result { + Ok(()) => Some(LastSubstack { + status: LastSubstackStatus::Running, + machine: upward.machine, + }), + Err(err) => Some(LastSubstack { + machine: upward.machine, + status: LastSubstackStatus::Exited(Capture::Exit(Err(err))), + }), + } + } + } + Some(LastSubstack { + status: LastSubstackStatus::Exited(Capture::Trap(trap)), + mut machine, + }) => { + match self.invoker.enter_substack( + trap, + &mut machine, + self.backend, + self.initial_depth + self.stack.len() + 1, + ) { + Capture::Exit(Ok((trap_data, InvokerControl::Enter(sub_machine)))) => { + self.stack.push(Substack { + invoke: trap_data, + machine, + }); + + Some(LastSubstack { + status: LastSubstackStatus::Running, + machine: sub_machine, + }) + } + Capture::Exit(Ok(( + trap_data, + InvokerControl::DirectExit((exit, sub_machine)), + ))) => { + let feedback_result = self.invoker.exit_substack( + exit, + sub_machine, + trap_data, + &mut machine, + self.backend, + ); + + match feedback_result { + Ok(()) => Some(LastSubstack { + status: LastSubstackStatus::Running, + machine, + }), + Err(err) => Some(LastSubstack { + machine, + status: LastSubstackStatus::Exited(Capture::Exit(Err(err))), + }), + } + } + Capture::Exit(Err(err)) => Some(LastSubstack { + status: LastSubstackStatus::Exited(Capture::Exit(Err(err))), + machine, + }), + Capture::Trap(trap) => { + step_ret = Some(Capture::Trap(trap)); + + Some(LastSubstack { + status: LastSubstackStatus::ExternalTrapped, + machine, + }) + } + } + } + }; + + match step_ret { + Some(res) => Err(res), + None => Ok(()), + } + } +} + +fn execute( + mut machine: I::Machine, + initial_depth: usize, + heap_depth: Option, + backend: &mut H, + invoker: &I, +) -> Result<(ExitResult, I::Machine), ExitFatal> +where + I: Invoker, +{ + let mut result = machine.run(backend); + + loop { + match result { + Capture::Exit(exit) => return Ok((exit, machine)), + Capture::Trap(trap) => { + match invoker.enter_substack(trap, &mut machine, backend, initial_depth + 1) { + Capture::Exit(Ok((trap_data, InvokerControl::Enter(sub_machine)))) => { + let (sub_result, sub_machine) = if heap_depth + .map(|hd| initial_depth + 1 >= hd) + .unwrap_or(false) + { + match CallStack::new(sub_machine, initial_depth + 1, backend, invoker) + .run() + { + Capture::Exit(v) => v?, + Capture::Trap(infallible) => match infallible {}, + } + } else { + execute(sub_machine, initial_depth + 1, heap_depth, backend, invoker)? + }; + + match invoker.exit_substack( + sub_result, + sub_machine.deconstruct(), + trap_data, + &mut machine, + backend, + ) { + Ok(()) => { + result = machine.run(backend); + } + Err(err) => return Ok((Err(err), machine)), + } + } + Capture::Exit(Ok(( + trap_data, + InvokerControl::DirectExit((sub_result, sub_machine)), + ))) => { + match invoker.exit_substack( + sub_result, + sub_machine, + trap_data, + &mut machine, + backend, + ) { + Ok(()) => { + result = machine.run(backend); + } + Err(err) => return Ok((Err(err), machine)), + } + } + Capture::Exit(Err(err)) => return Ok((Err(err), machine)), + Capture::Trap(infallible) => match infallible {}, + } + } + } + } +} + +enum HeapTransactState<'backend, 'invoker, H, Tr, I: Invoker> { + Created { + args: I::TransactArgs, + invoker: &'invoker I, + backend: &'backend mut H, + }, + Running { + call_stack: CallStack<'backend, 'invoker, H, Tr, I>, + transact_invoke: I::TransactInvoke, + }, +} + +pub struct HeapTransact<'backend, 'invoker, H, Tr, I: Invoker>( + Option>, +); + +impl<'backend, 'invoker, H, Tr, I> HeapTransact<'backend, 'invoker, H, Tr, I> +where + I: Invoker, +{ + pub fn new( + args: I::TransactArgs, + invoker: &'invoker I, + backend: &'backend mut H, + ) -> Result { + Ok(Self(Some(HeapTransactState::Created { + args, + invoker, + backend, + }))) + } + + fn step_with( + &mut self, + fs: FS, + ) -> Result<(), Capture, I::Interrupt>> + where + FS: Fn( + &mut CallStack<'backend, 'invoker, H, Tr, I>, + ) -> Result<(), Capture, I::Interrupt>>, + { + let ret; + + self.0 = match self.0.take() { + Some(HeapTransactState::Running { + mut call_stack, + transact_invoke, + }) => { + ret = match fs(&mut call_stack) { + Ok(()) => Ok(()), + Err(Capture::Trap(interrupt)) => Err(Capture::Trap(interrupt)), + Err(Capture::Exit(Err(fatal))) => Err(Capture::Exit(Err(fatal.into()))), + Err(Capture::Exit(Ok((ret, machine)))) => { + let machine = machine.deconstruct(); + Err(Capture::Exit(call_stack.invoker.finalize_transact( + &transact_invoke, + ret, + machine, + call_stack.backend, + ))) + } + }; + + Some(HeapTransactState::Running { + call_stack, + transact_invoke, + }) + } + Some(HeapTransactState::Created { + args, + invoker, + backend, + }) => { + let (transact_invoke, control) = match invoker.new_transact(args, backend) { + Ok((transact_invoke, control)) => (transact_invoke, control), + Err(err) => return Err(Capture::Exit(Err(err))), + }; + + match control { + InvokerControl::Enter(machine) => { + let call_stack = CallStack::new(machine, 0, backend, invoker); + + ret = Ok(()); + Some(HeapTransactState::Running { + call_stack, + transact_invoke, + }) + } + InvokerControl::DirectExit((exit, machine)) => { + return Err(Capture::Exit(invoker.finalize_transact( + &transact_invoke, + exit, + machine, + backend, + ))); + } + } + } + None => return Err(Capture::Exit(Err(ExitFatal::AlreadyExited.into()))), + }; + + ret + } + + pub fn step_run( + &mut self, + ) -> Result<(), Capture, I::Interrupt>> { + self.step_with(|call_stack| call_stack.step_run()) + } + + pub fn step( + &mut self, + ) -> Result<(), Capture, I::Interrupt>> { + self.step_with(|call_stack| call_stack.step()) + } + + pub fn run(&mut self) -> Capture, I::Interrupt> { + loop { + let step_ret = self.step_run(); + + if let Err(step_ret) = step_ret { + return step_ret; + } + } + } + + pub fn last_machine(&self) -> Option<&I::Machine> { + match &self.0 { + Some(HeapTransactState::Running { call_stack, .. }) => match &call_stack.last { + Some(last) => Some(&last.machine), + None => None, + }, + _ => None, + } + } +} + +impl<'backend, 'invoker, H, Tr, I> Drop for HeapTransact<'backend, 'invoker, H, Tr, I> +where + I: Invoker, +{ + fn drop(&mut self) { + if let Some(state) = self.0.take() { + match state { + HeapTransactState::Running { + mut call_stack, + transact_invoke, + } => { + if let Some(mut last) = call_stack.last.take() { + loop { + if let Some(mut parent) = call_stack.stack.pop() { + let last_machine = last.machine.deconstruct(); + let _ = call_stack.invoker.exit_substack( + ExitFatal::Unfinished.into(), + last_machine, + parent.invoke, + &mut parent.machine, + call_stack.backend, + ); + + last = LastSubstack { + machine: parent.machine, + status: LastSubstackStatus::Exited(Capture::Exit( + ExitFatal::Unfinished.into(), + )), + }; + } else { + break; + } + } + + let last_machine = last.machine.deconstruct(); + let _ = call_stack.invoker.finalize_transact( + &transact_invoke, + ExitFatal::Unfinished.into(), + last_machine, + call_stack.backend, + ); + } + } + _ => (), + } + } + } +} + +pub fn transact( + args: I::TransactArgs, + heap_depth: Option, + backend: &mut H, + invoker: &I, +) -> Result +where + I: Invoker, +{ + let (transact_invoke, control) = invoker.new_transact(args, backend)?; + + match control { + InvokerControl::Enter(machine) => { + let (ret, machine) = execute(machine, 0, heap_depth, backend, invoker)?; + let machine = machine.deconstruct(); + invoker.finalize_transact(&transact_invoke, ret, machine, backend) + } + InvokerControl::DirectExit((exit, machine)) => { + invoker.finalize_transact(&transact_invoke, exit, machine, backend) + } + } +} diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 000000000..64206d599 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,101 @@ +use crate::{ + Capture, Control, Etable, ExitResult, Gasometer, InvokerMachine, Machine, Opcode, RuntimeState, +}; + +pub struct ColoredMachine { + pub machine: Machine, + pub gasometer: G, + pub is_static: bool, + pub color: C, +} + +impl InvokerMachine for ColoredMachine +where + C: Color, +{ + type Deconstruct = (S, G, Vec); + + fn step(&mut self, handler: &mut H) -> Result<(), Capture> { + self.color.step( + &mut self.machine, + &mut self.gasometer, + self.is_static, + handler, + ) + } + + fn run(&mut self, handler: &mut H) -> Capture { + self.color.run( + &mut self.machine, + &mut self.gasometer, + self.is_static, + handler, + ) + } + + fn deconstruct(self) -> Self::Deconstruct { + (self.machine.state, self.gasometer, self.machine.retval) + } +} + +pub trait Color { + fn step( + &self, + machine: &mut Machine, + gasometer: &mut G, + is_static: bool, + handler: &mut H, + ) -> Result<(), Capture>; + + fn run( + &self, + machine: &mut Machine, + gasometer: &mut G, + is_static: bool, + handler: &mut H, + ) -> Capture; +} + +impl<'etable, S, G, H, Tr, F> Color for &'etable Etable +where + S: AsMut, + G: Gasometer, + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, +{ + fn step( + &self, + machine: &mut Machine, + gasometer: &mut G, + is_static: bool, + handler: &mut H, + ) -> Result<(), Capture> { + match gasometer.record_step(&machine, is_static, handler) { + Ok(()) => { + machine.state.as_mut().gas = gasometer.gas().into(); + machine.step(handler, self) + } + Err(e) => return Err(Capture::Exit(Err(e))), + } + } + + fn run( + &self, + machine: &mut Machine, + gasometer: &mut G, + is_static: bool, + handler: &mut H, + ) -> Capture { + loop { + match gasometer.record_stepn(&machine, is_static, handler) { + Ok(stepn) => { + machine.state.as_mut().gas = gasometer.gas().into(); + match machine.stepn(stepn, handler, self) { + Ok(()) => (), + Err(c) => return c, + } + } + Err(e) => return Capture::Exit(Err(e)), + } + } + } +} diff --git a/src/executor/mod.rs b/src/executor/mod.rs deleted file mode 100644 index a0a05e3b5..000000000 --- a/src/executor/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! # EVM executors -//! -//! Executors are structs that hook gasometer and the EVM core together. It -//! also handles the call stacks in EVM. -//! -//! Currently only a stack-based (customizable) executor is provided. - -pub mod stack; diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs deleted file mode 100644 index e35bae13b..000000000 --- a/src/executor/stack/executor.rs +++ /dev/null @@ -1,1516 +0,0 @@ -use crate::backend::Backend; -use crate::executor::stack::precompile::{ - IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet, -}; -use crate::executor::stack::tagged_runtime::{RuntimeKind, TaggedRuntime}; -use crate::gasometer::{self, Gasometer, StorageTarget}; -use crate::maybe_borrowed::MaybeBorrowed; -use crate::{ - Capture, Config, Context, CreateScheme, ExitError, ExitReason, Handler, Opcode, Runtime, Stack, - Transfer, -}; -use alloc::{collections::BTreeSet, rc::Rc, vec::Vec}; -use core::{cmp::min, convert::Infallible}; -use evm_core::ExitFatal; -use evm_runtime::Resolve; -use primitive_types::{H160, H256, U256}; -use sha3::{Digest, Keccak256}; - -macro_rules! emit_exit { - ($reason:expr) => {{ - let reason = $reason; - event!(Exit { - reason: &reason, - return_value: &Vec::new(), - }); - reason - }}; - ($reason:expr, $return_value:expr) => {{ - let reason = $reason; - let return_value = $return_value; - event!(Exit { - reason: &reason, - return_value: &return_value, - }); - (reason, return_value) - }}; -} - -const DEFAULT_CALL_STACK_CAPACITY: usize = 4; - -pub enum StackExitKind { - Succeeded, - Reverted, - Failed, -} - -#[derive(Default, Clone, Debug)] -pub struct Accessed { - pub accessed_addresses: BTreeSet, - pub accessed_storage: BTreeSet<(H160, H256)>, -} - -impl Accessed { - pub fn access_address(&mut self, address: H160) { - self.accessed_addresses.insert(address); - } - - pub fn access_addresses(&mut self, addresses: I) - where - I: Iterator, - { - for address in addresses { - self.accessed_addresses.insert(address); - } - } - - pub fn access_storages(&mut self, storages: I) - where - I: Iterator, - { - for storage in storages { - self.accessed_storage.insert((storage.0, storage.1)); - } - } -} - -#[derive(Clone, Debug)] -pub struct StackSubstateMetadata<'config> { - gasometer: Gasometer<'config>, - is_static: bool, - depth: Option, - accessed: Option, -} - -impl<'config> StackSubstateMetadata<'config> { - pub fn new(gas_limit: u64, config: &'config Config) -> Self { - let accessed = if config.increase_state_access_gas { - Some(Accessed::default()) - } else { - None - }; - Self { - gasometer: Gasometer::new(gas_limit, config), - is_static: false, - depth: None, - accessed, - } - } - - pub fn swallow_commit(&mut self, other: Self) -> Result<(), ExitError> { - self.gasometer.record_stipend(other.gasometer.gas())?; - self.gasometer - .record_refund(other.gasometer.refunded_gas())?; - - if let (Some(mut other_accessed), Some(self_accessed)) = - (other.accessed, self.accessed.as_mut()) - { - self_accessed - .accessed_addresses - .append(&mut other_accessed.accessed_addresses); - self_accessed - .accessed_storage - .append(&mut other_accessed.accessed_storage); - } - - Ok(()) - } - - pub fn swallow_revert(&mut self, other: Self) -> Result<(), ExitError> { - self.gasometer.record_stipend(other.gasometer.gas())?; - - Ok(()) - } - - pub fn swallow_discard(&mut self, _other: Self) -> Result<(), ExitError> { - Ok(()) - } - - pub fn spit_child(&self, gas_limit: u64, is_static: bool) -> Self { - Self { - gasometer: Gasometer::new(gas_limit, self.gasometer.config()), - is_static: is_static || self.is_static, - depth: match self.depth { - None => Some(0), - Some(n) => Some(n + 1), - }, - accessed: self.accessed.as_ref().map(|_| Accessed::default()), - } - } - - pub fn gasometer(&self) -> &Gasometer<'config> { - &self.gasometer - } - - pub fn gasometer_mut(&mut self) -> &mut Gasometer<'config> { - &mut self.gasometer - } - - pub fn is_static(&self) -> bool { - self.is_static - } - - pub fn depth(&self) -> Option { - self.depth - } - - pub fn access_address(&mut self, address: H160) { - if let Some(accessed) = &mut self.accessed { - accessed.access_address(address) - } - } - - pub fn access_addresses(&mut self, addresses: I) - where - I: Iterator, - { - if let Some(accessed) = &mut self.accessed { - accessed.access_addresses(addresses); - } - } - - pub fn access_storage(&mut self, address: H160, key: H256) { - if let Some(accessed) = &mut self.accessed { - accessed.accessed_storage.insert((address, key)); - } - } - - pub fn access_storages(&mut self, storages: I) - where - I: Iterator, - { - if let Some(accessed) = &mut self.accessed { - accessed.access_storages(storages); - } - } - - pub fn accessed(&self) -> &Option { - &self.accessed - } -} - -#[auto_impl::auto_impl(&mut, Box)] -pub trait StackState<'config>: Backend { - fn metadata(&self) -> &StackSubstateMetadata<'config>; - fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config>; - - fn enter(&mut self, gas_limit: u64, is_static: bool); - fn exit_commit(&mut self) -> Result<(), ExitError>; - fn exit_revert(&mut self) -> Result<(), ExitError>; - fn exit_discard(&mut self) -> Result<(), ExitError>; - - fn is_empty(&self, address: H160) -> bool; - fn deleted(&self, address: H160) -> bool; - fn is_cold(&self, address: H160) -> bool; - fn is_storage_cold(&self, address: H160, key: H256) -> bool; - - fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError>; - fn set_storage(&mut self, address: H160, key: H256, value: H256); - fn reset_storage(&mut self, address: H160); - fn log(&mut self, address: H160, topics: Vec, data: Vec); - fn set_deleted(&mut self, address: H160); - fn set_code(&mut self, address: H160, code: Vec); - fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError>; - fn reset_balance(&mut self, address: H160); - fn touch(&mut self, address: H160); - - /// Fetch the code size of an address. - /// Provide a default implementation by fetching the code, but - /// can be customized to use a more performant approach that don't need to - /// fetch the code. - fn code_size(&self, address: H160) -> U256 { - U256::from(self.code(address).len()) - } - - /// Fetch the code hash of an address. - /// Provide a default implementation by fetching the code, but - /// can be customized to use a more performant approach that don't need to - /// fetch the code. - fn code_hash(&self, address: H160) -> H256 { - H256::from_slice(Keccak256::digest(self.code(address)).as_slice()) - } - - fn record_external_operation( - &mut self, - _op: crate::ExternalOperation, - ) -> Result<(), ExitError> { - Ok(()) - } - - fn record_external_dynamic_opcode_cost( - &mut self, - _opcode: Opcode, - _gas_cost: crate::gasometer::GasCost, - _target: StorageTarget, - ) -> Result<(), ExitError> { - Ok(()) - } - - fn record_external_cost( - &mut self, - _ref_time: Option, - _proof_size: Option, - _storage_growth: Option, - ) -> Result<(), ExitError> { - Ok(()) - } - - fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} -} - -/// Stack-based executor. -pub struct StackExecutor<'config, 'precompiles, S, P> { - config: &'config Config, - state: S, - precompile_set: &'precompiles P, -} - -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> - StackExecutor<'config, 'precompiles, S, P> -{ - /// Return a reference of the Config. - pub fn config(&self) -> &'config Config { - self.config - } - - /// Return a reference to the precompile set. - pub fn precompiles(&self) -> &'precompiles P { - self.precompile_set - } - - /// Create a new stack-based executor with given precompiles. - pub fn new_with_precompiles( - state: S, - config: &'config Config, - precompile_set: &'precompiles P, - ) -> Self { - Self { - config, - state, - precompile_set, - } - } - - pub fn state(&self) -> &S { - &self.state - } - - pub fn state_mut(&mut self) -> &mut S { - &mut self.state - } - - pub fn into_state(self) -> S { - self.state - } - - /// Create a substate executor from the current executor. - pub fn enter_substate(&mut self, gas_limit: u64, is_static: bool) { - self.state.enter(gas_limit, is_static); - } - - /// Exit a substate. Panic if it results an empty substate stack. - pub fn exit_substate(&mut self, kind: StackExitKind) -> Result<(), ExitError> { - match kind { - StackExitKind::Succeeded => self.state.exit_commit(), - StackExitKind::Reverted => self.state.exit_revert(), - StackExitKind::Failed => self.state.exit_discard(), - } - } - - /// Execute the runtime until it returns. - pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason { - let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); - call_stack.push(TaggedRuntime { - kind: RuntimeKind::Execute, - inner: MaybeBorrowed::Borrowed(runtime), - }); - let (reason, _, _) = self.execute_with_call_stack(&mut call_stack); - reason - } - - /// Execute using Runtimes on the call_stack until it returns. - fn execute_with_call_stack( - &mut self, - call_stack: &mut Vec>, - ) -> (ExitReason, Option, Vec) { - // This `interrupt_runtime` is used to pass the runtime obtained from the - // `Capture::Trap` branch in the match below back to the top of the call stack. - // The reason we can't simply `push` the runtime directly onto the stack in the - // `Capture::Trap` branch is because the borrow-checker complains that the stack - // is already borrowed as long as we hold a pointer on the last element - // (i.e. the currently executing runtime). - let mut interrupt_runtime = None; - loop { - if let Some(rt) = interrupt_runtime.take() { - call_stack.push(rt); - } - let runtime = match call_stack.last_mut() { - Some(runtime) => runtime, - None => { - return ( - ExitReason::Fatal(ExitFatal::UnhandledInterrupt), - None, - Vec::new(), - ); - } - }; - let reason = { - let inner_runtime = &mut runtime.inner; - match inner_runtime.run(self) { - Capture::Exit(reason) => reason, - Capture::Trap(Resolve::Call(rt, _)) => { - interrupt_runtime = Some(rt.0); - continue; - } - Capture::Trap(Resolve::Create(rt, _)) => { - interrupt_runtime = Some(rt.0); - continue; - } - } - }; - let runtime_kind = runtime.kind; - let (reason, maybe_address, return_data) = match runtime_kind { - RuntimeKind::Create(created_address) => { - let (reason, maybe_address, return_data) = self.cleanup_for_create( - created_address, - reason, - runtime.inner.machine().return_value(), - ); - (reason, maybe_address, return_data) - } - RuntimeKind::Call(code_address) => { - let return_data = self.cleanup_for_call( - code_address, - &reason, - runtime.inner.machine().return_value(), - ); - (reason, None, return_data) - } - RuntimeKind::Execute => (reason, None, runtime.inner.machine().return_value()), - }; - // We're done with that runtime now, so can pop it off the call stack - call_stack.pop(); - // Now pass the results from that runtime on to the next one in the stack - let runtime = match call_stack.last_mut() { - Some(r) => r, - None => return (reason, None, return_data), - }; - emit_exit!(&reason, &return_data); - let inner_runtime = &mut runtime.inner; - let maybe_error = match runtime_kind { - RuntimeKind::Create(_) => { - inner_runtime.finish_create(reason, maybe_address, return_data) - } - RuntimeKind::Call(_) => inner_runtime.finish_call(reason, return_data), - RuntimeKind::Execute => inner_runtime.finish_call(reason, return_data), - }; - // Early exit if passing on the result caused an error - if let Err(e) = maybe_error { - return (e, None, Vec::new()); - } - } - } - - /// Get remaining gas. - pub fn gas(&self) -> u64 { - self.state.metadata().gasometer.gas() - } - - fn record_create_transaction_cost( - &mut self, - init_code: &[u8], - access_list: &[(H160, Vec)], - ) -> Result<(), ExitError> { - let transaction_cost = gasometer::create_transaction_cost(init_code, access_list); - let gasometer = &mut self.state.metadata_mut().gasometer; - gasometer.record_transaction(transaction_cost) - } - - fn maybe_record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> { - if let Some(limit) = self.config.max_initcode_size { - // EIP-3860 - if init_code.len() > limit { - self.state.metadata_mut().gasometer.fail(); - return Err(ExitError::CreateContractLimit); - } - return self - .state - .metadata_mut() - .gasometer - .record_cost(gasometer::init_code_cost(init_code)); - } - Ok(()) - } - - /// Execute a `CREATE` transaction. - pub fn transact_create( - &mut self, - caller: H160, - value: U256, - init_code: Vec, - gas_limit: u64, - access_list: Vec<(H160, Vec)>, // See EIP-2930 - ) -> (ExitReason, Vec) { - event!(TransactCreate { - caller, - value, - init_code: &init_code, - gas_limit, - address: self.create_address(CreateScheme::Legacy { caller }), - }); - - if let Some(limit) = self.config.max_initcode_size { - if init_code.len() > limit { - self.state.metadata_mut().gasometer.fail(); - return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); - } - } - - if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { - return emit_exit!(e.into(), Vec::new()); - } - self.initialize_with_access_list(access_list); - - match self.create_inner( - caller, - CreateScheme::Legacy { caller }, - value, - init_code, - Some(gas_limit), - false, - ) { - Capture::Exit((s, _, v)) => emit_exit!(s, v), - Capture::Trap(rt) => { - let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); - cs.push(rt.0); - let (s, _, v) = self.execute_with_call_stack(&mut cs); - emit_exit!(s, v) - } - } - } - - /// Execute a `CREATE2` transaction. - pub fn transact_create2( - &mut self, - caller: H160, - value: U256, - init_code: Vec, - salt: H256, - gas_limit: u64, - access_list: Vec<(H160, Vec)>, // See EIP-2930 - ) -> (ExitReason, Vec) { - if let Some(limit) = self.config.max_initcode_size { - if init_code.len() > limit { - self.state.metadata_mut().gasometer.fail(); - return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); - } - } - - let code_hash = H256::from_slice(Keccak256::digest(&init_code).as_slice()); - event!(TransactCreate2 { - caller, - value, - init_code: &init_code, - salt, - gas_limit, - address: self.create_address(CreateScheme::Create2 { - caller, - code_hash, - salt, - }), - }); - - if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { - return emit_exit!(e.into(), Vec::new()); - } - self.initialize_with_access_list(access_list); - - match self.create_inner( - caller, - CreateScheme::Create2 { - caller, - code_hash, - salt, - }, - value, - init_code, - Some(gas_limit), - false, - ) { - Capture::Exit((s, _, v)) => emit_exit!(s, v), - Capture::Trap(rt) => { - let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); - cs.push(rt.0); - let (s, _, v) = self.execute_with_call_stack(&mut cs); - emit_exit!(s, v) - } - } - } - - /// Execute a `CALL` transaction with a given caller, address, value and - /// gas limit and data. - /// - /// Takes in an additional `access_list` parameter for EIP-2930 which was - /// introduced in the Ethereum Berlin hard fork. If you do not wish to use - /// this functionality, just pass in an empty vector. - pub fn transact_call( - &mut self, - caller: H160, - address: H160, - value: U256, - data: Vec, - gas_limit: u64, - access_list: Vec<(H160, Vec)>, - ) -> (ExitReason, Vec) { - event!(TransactCall { - caller, - address, - value, - data: &data, - gas_limit, - }); - - let transaction_cost = gasometer::call_transaction_cost(&data, &access_list); - let gasometer = &mut self.state.metadata_mut().gasometer; - match gasometer.record_transaction(transaction_cost) { - Ok(()) => (), - Err(e) => return emit_exit!(e.into(), Vec::new()), - } - - // Initialize initial addresses for EIP-2929 - if self.config.increase_state_access_gas { - if self.config.warm_coinbase_address { - // Warm coinbase address for EIP-3651 - let addresses = core::iter::once(caller) - .chain(core::iter::once(address)) - .chain(core::iter::once(self.block_coinbase())); - self.state.metadata_mut().access_addresses(addresses); - } else { - let addresses = core::iter::once(caller).chain(core::iter::once(address)); - self.state.metadata_mut().access_addresses(addresses); - } - - self.initialize_with_access_list(access_list); - } - if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { - return (e.into(), Vec::new()); - } - if let Err(e) = self.state.inc_nonce(caller) { - return (e.into(), Vec::new()); - } - - let context = Context { - caller, - address, - apparent_value: value, - }; - - match self.call_inner( - address, - Some(Transfer { - source: caller, - target: address, - value, - }), - data, - Some(gas_limit), - false, - false, - false, - context, - ) { - Capture::Exit((s, v)) => emit_exit!(s, v), - Capture::Trap(rt) => { - let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); - cs.push(rt.0); - let (s, _, v) = self.execute_with_call_stack(&mut cs); - emit_exit!(s, v) - } - } - } - - /// Get used gas for the current executor, given the price. - pub fn used_gas(&self) -> u64 { - self.state.metadata().gasometer.total_used_gas() - - min( - self.state.metadata().gasometer.total_used_gas() / self.config.max_refund_quotient, - self.state.metadata().gasometer.refunded_gas() as u64, - ) - } - - /// Get fee needed for the current executor, given the price. - pub fn fee(&self, price: U256) -> U256 { - let used_gas = self.used_gas(); - U256::from(used_gas).saturating_mul(price) - } - - /// Get account nonce. - pub fn nonce(&self, address: H160) -> U256 { - self.state.basic(address).nonce - } - - /// Get the create address from given scheme. - pub fn create_address(&self, scheme: CreateScheme) -> H160 { - match scheme { - CreateScheme::Create2 { - caller, - code_hash, - salt, - } => { - let mut hasher = Keccak256::new(); - hasher.update([0xff]); - hasher.update(&caller[..]); - hasher.update(&salt[..]); - hasher.update(&code_hash[..]); - H256::from_slice(hasher.finalize().as_slice()).into() - } - CreateScheme::Legacy { caller } => { - let nonce = self.nonce(caller); - let mut stream = rlp::RlpStream::new_list(2); - stream.append(&caller); - stream.append(&nonce); - H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() - } - CreateScheme::Fixed(naddress) => naddress, - } - } - - pub fn initialize_with_access_list(&mut self, access_list: Vec<(H160, Vec)>) { - let addresses = access_list.iter().map(|a| a.0); - self.state.metadata_mut().access_addresses(addresses); - - let storage_keys = access_list - .into_iter() - .flat_map(|(address, keys)| keys.into_iter().map(move |key| (address, key))); - self.state.metadata_mut().access_storages(storage_keys); - } - - fn create_inner( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - take_l64: bool, - ) -> Capture<(ExitReason, Option, Vec), StackExecutorCreateInterrupt<'static>> { - macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), None, Vec::new())), - } - }; - } - - fn l64(gas: u64) -> u64 { - gas - gas / 64 - } - - let address = self.create_address(scheme); - - self.state.metadata_mut().access_address(caller); - self.state.metadata_mut().access_address(address); - - event!(Create { - caller, - address, - scheme, - value, - init_code: &init_code, - target_gas - }); - - if let Some(depth) = self.state.metadata().depth { - if depth > self.config.call_stack_limit { - return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())); - } - } - - if self.balance(caller) < value { - return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())); - } - - if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - if let Err(e) = self.state.inc_nonce(caller) { - return Capture::Exit((e.into(), None, Vec::new())); - } - - let after_gas = if take_l64 && self.config.call_l64_after_gas { - if self.config.estimate { - let initial_after_gas = self.state.metadata().gasometer.gas(); - let diff = initial_after_gas - l64(initial_after_gas); - try_or_fail!(self.state.metadata_mut().gasometer.record_cost(diff)); - self.state.metadata().gasometer.gas() - } else { - l64(self.state.metadata().gasometer.gas()) - } - } else { - self.state.metadata().gasometer.gas() - }; - - let target_gas = target_gas.unwrap_or(after_gas); - - let gas_limit = min(after_gas, target_gas); - try_or_fail!(self.state.metadata_mut().gasometer.record_cost(gas_limit)); - - self.enter_substate(gas_limit, false); - - { - if let Err(e) = - self.record_external_operation(crate::ExternalOperation::AddressCodeRead(address)) - { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - let code_size = self.code_size(address); - if code_size != U256::zero() { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); - } - - if self.nonce(address) > U256::zero() { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); - } - - self.state.reset_storage(address); - } - - let context = Context { - address, - caller, - apparent_value: value, - }; - let transfer = Transfer { - source: caller, - target: address, - value, - }; - match self.state.transfer(transfer) { - Ok(()) => (), - Err(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - } - - if self.config.create_increase_nonce { - if let Err(e) = - self.record_external_operation(crate::ExternalOperation::AccountBasicRead) - { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - if let Err(e) = self.state.inc_nonce(address) { - return Capture::Exit((e.into(), None, Vec::new())); - } - } - - let runtime = Runtime::new( - Rc::new(init_code), - Rc::new(Vec::new()), - context, - self.config.stack_limit, - self.config.memory_limit, - ); - - Capture::Trap(StackExecutorCreateInterrupt(TaggedRuntime { - kind: RuntimeKind::Create(address), - inner: MaybeBorrowed::Owned(runtime), - })) - } - - #[allow(clippy::too_many_arguments)] - fn call_inner( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - take_l64: bool, - take_stipend: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), StackExecutorCallInterrupt<'static>> { - macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), Vec::new())), - } - }; - } - - fn l64(gas: u64) -> u64 { - gas - gas / 64 - } - - event!(Call { - code_address, - transfer: &transfer, - input: &input, - target_gas, - is_static, - context: &context, - }); - - let after_gas = if take_l64 && self.config.call_l64_after_gas { - if self.config.estimate { - let initial_after_gas = self.state.metadata().gasometer.gas(); - let diff = initial_after_gas - l64(initial_after_gas); - try_or_fail!(self.state.metadata_mut().gasometer.record_cost(diff)); - self.state.metadata().gasometer.gas() - } else { - l64(self.state.metadata().gasometer.gas()) - } - } else { - self.state.metadata().gasometer.gas() - }; - - let target_gas = target_gas.unwrap_or(after_gas); - let mut gas_limit = min(target_gas, after_gas); - - try_or_fail!(self.state.metadata_mut().gasometer.record_cost(gas_limit)); - - if let Some(transfer) = transfer.as_ref() { - if take_stipend && transfer.value != U256::zero() { - gas_limit = gas_limit.saturating_add(self.config.call_stipend); - } - } - - self.enter_substate(gas_limit, is_static); - self.state.touch(context.address); - - if let Err(e) = - self.record_external_operation(crate::ExternalOperation::AddressCodeRead(code_address)) - { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitReason::Error(e), Vec::new())); - } - let code = self.code(code_address); - if let Some(depth) = self.state.metadata().depth { - if depth > self.config.call_stack_limit { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())); - } - } - - if let Some(transfer) = transfer { - if let Err(e) = - self.record_external_operation(crate::ExternalOperation::AccountBasicRead) - { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitReason::Error(e), Vec::new())); - } - match self.state.transfer(transfer) { - Ok(()) => (), - Err(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), Vec::new())); - } - } - } - - // At this point, the state has been modified in enter_substate to - // reflect both the is_static parameter of this call and the is_static - // of the caller context. - let precompile_is_static = self.state.metadata().is_static(); - if let Some(result) = self.precompile_set.execute(&mut StackExecutorHandle { - executor: self, - code_address, - input: &input, - gas_limit: Some(gas_limit), - context: &context, - is_static: precompile_is_static, - }) { - return match result { - Ok(PrecompileOutput { - exit_status, - output, - }) => { - let _ = self.exit_substate(StackExitKind::Succeeded); - Capture::Exit((ExitReason::Succeed(exit_status), output)) - } - Err(PrecompileFailure::Error { exit_status }) => { - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(exit_status), Vec::new())) - } - Err(PrecompileFailure::Revert { - exit_status, - output, - }) => { - let _ = self.exit_substate(StackExitKind::Reverted); - Capture::Exit((ExitReason::Revert(exit_status), output)) - } - Err(PrecompileFailure::Fatal { exit_status }) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Fatal(exit_status), Vec::new())) - } - }; - } - - let runtime = Runtime::new( - Rc::new(code), - Rc::new(input), - context, - self.config.stack_limit, - self.config.memory_limit, - ); - - Capture::Trap(StackExecutorCallInterrupt(TaggedRuntime { - kind: RuntimeKind::Call(code_address), - inner: MaybeBorrowed::Owned(runtime), - })) - } - - fn cleanup_for_create( - &mut self, - created_address: H160, - reason: ExitReason, - return_data: Vec, - ) -> (ExitReason, Option, Vec) { - fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> { - if config.disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first() - { - return Err(ExitError::InvalidCode(Opcode::EOFMAGIC)); - } - Ok(()) - } - - log::debug!(target: "evm", "Create execution using address {}: {:?}", created_address, reason); - - match reason { - ExitReason::Succeed(s) => { - let out = return_data; - let address = created_address; - // As of EIP-3541 code starting with 0xef cannot be deployed - if let Err(e) = check_first_byte(self.config, &out) { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - return (e.into(), None, Vec::new()); - } - - if let Some(limit) = self.config.create_contract_limit { - if out.len() > limit { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - return (ExitError::CreateContractLimit.into(), None, Vec::new()); - } - } - - match self - .state - .metadata_mut() - .gasometer - .record_deposit(out.len()) - { - Ok(()) => { - let exit_result = self.exit_substate(StackExitKind::Succeeded); - if let Err(e) = self.record_external_operation( - crate::ExternalOperation::Write(U256::from(out.len())), - ) { - return (e.into(), None, Vec::new()); - } - self.state.set_code(address, out); - if let Err(e) = exit_result { - return (e.into(), None, Vec::new()); - } - (ExitReason::Succeed(s), Some(address), Vec::new()) - } - Err(e) => { - let _ = self.exit_substate(StackExitKind::Failed); - (ExitReason::Error(e), None, Vec::new()) - } - } - } - ExitReason::Error(e) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - (ExitReason::Error(e), None, Vec::new()) - } - ExitReason::Revert(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - (ExitReason::Revert(e), None, return_data) - } - ExitReason::Fatal(e) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - (ExitReason::Fatal(e), None, Vec::new()) - } - } - } - - fn cleanup_for_call( - &mut self, - code_address: H160, - reason: &ExitReason, - return_data: Vec, - ) -> Vec { - log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); - match reason { - ExitReason::Succeed(_) => { - let _ = self.exit_substate(StackExitKind::Succeeded); - return_data - } - ExitReason::Error(_) => { - let _ = self.exit_substate(StackExitKind::Failed); - Vec::new() - } - ExitReason::Revert(_) => { - let _ = self.exit_substate(StackExitKind::Reverted); - return_data - } - ExitReason::Fatal(_) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Vec::new() - } - } - } -} - -pub struct StackExecutorCallInterrupt<'borrow>(TaggedRuntime<'borrow>); -pub struct StackExecutorCreateInterrupt<'borrow>(TaggedRuntime<'borrow>); - -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler - for StackExecutor<'config, 'precompiles, S, P> -{ - type CreateInterrupt = StackExecutorCreateInterrupt<'static>; - type CreateFeedback = Infallible; - type CallInterrupt = StackExecutorCallInterrupt<'static>; - type CallFeedback = Infallible; - - fn balance(&self, address: H160) -> U256 { - self.state.basic(address).balance - } - - fn code_size(&self, address: H160) -> U256 { - self.state.code_size(address) - } - - fn code_hash(&self, address: H160) -> H256 { - if !self.exists(address) { - return H256::default(); - } - - self.state.code_hash(address) - } - - fn code(&self, address: H160) -> Vec { - self.state.code(address) - } - - fn storage(&self, address: H160, index: H256) -> H256 { - self.state.storage(address, index) - } - - fn original_storage(&self, address: H160, index: H256) -> H256 { - self.state - .original_storage(address, index) - .unwrap_or_default() - } - - fn exists(&self, address: H160) -> bool { - if self.config.empty_considered_exists { - self.state.exists(address) - } else { - self.state.exists(address) && !self.state.is_empty(address) - } - } - - fn is_cold(&mut self, address: H160, maybe_index: Option) -> Result { - Ok(match maybe_index { - None => { - let is_precompile = match self - .precompile_set - .is_precompile(address, self.state.metadata().gasometer.gas()) - { - IsPrecompileResult::Answer { - is_precompile, - extra_cost, - } => { - self.state - .metadata_mut() - .gasometer - .record_cost(extra_cost)?; - is_precompile - } - IsPrecompileResult::OutOfGas => return Err(ExitError::OutOfGas), - }; - - !is_precompile && self.state.is_cold(address) - } - Some(index) => self.state.is_storage_cold(address, index), - }) - } - - fn gas_left(&self) -> U256 { - U256::from(self.state.metadata().gasometer.gas()) - } - - fn gas_price(&self) -> U256 { - self.state.gas_price() - } - fn origin(&self) -> H160 { - self.state.origin() - } - fn block_hash(&self, number: U256) -> H256 { - self.state.block_hash(number) - } - fn block_number(&self) -> U256 { - self.state.block_number() - } - fn block_coinbase(&self) -> H160 { - self.state.block_coinbase() - } - fn block_timestamp(&self) -> U256 { - self.state.block_timestamp() - } - fn block_difficulty(&self) -> U256 { - self.state.block_difficulty() - } - fn block_randomness(&self) -> Option { - self.state.block_randomness() - } - fn block_gas_limit(&self) -> U256 { - self.state.block_gas_limit() - } - fn block_base_fee_per_gas(&self) -> U256 { - self.state.block_base_fee_per_gas() - } - fn chain_id(&self) -> U256 { - self.state.chain_id() - } - - fn deleted(&self, address: H160) -> bool { - self.state.deleted(address) - } - - fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> { - self.state.set_storage(address, index, value); - Ok(()) - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - self.state.log(address, topics, data); - Ok(()) - } - - fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> { - let balance = self.balance(address); - - event!(Suicide { - target, - address, - balance, - }); - - self.state.transfer(Transfer { - source: address, - target, - value: balance, - })?; - self.state.reset_balance(address); - self.state.set_deleted(address); - - Ok(()) - } - - #[cfg(not(feature = "tracing"))] - fn create( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { - if let Err(e) = self.maybe_record_init_code_cost(&init_code) { - let reason: ExitReason = e.into(); - emit_exit!(reason.clone()); - return Capture::Exit((reason, None, Vec::new())); - } - - self.create_inner(caller, scheme, value, init_code, target_gas, true) - } - - #[cfg(feature = "tracing")] - fn create( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { - if let Err(e) = self.maybe_record_init_code_cost(&init_code) { - let reason: ExitReason = e.into(); - emit_exit!(reason.clone()); - return Capture::Exit((reason, None, Vec::new())); - } - - let capture = self.create_inner(caller, scheme, value, init_code, target_gas, true); - - if let Capture::Exit((ref reason, _, ref return_value)) = capture { - emit_exit!(reason, return_value); - } - - capture - } - - #[cfg(not(feature = "tracing"))] - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { - self.call_inner( - code_address, - transfer, - input, - target_gas, - is_static, - true, - true, - context, - ) - } - - #[cfg(feature = "tracing")] - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { - let capture = self.call_inner( - code_address, - transfer, - input, - target_gas, - is_static, - true, - true, - context, - ); - - if let Capture::Exit((ref reason, ref return_value)) = capture { - emit_exit!(reason, return_value); - } - - capture - } - - #[inline] - fn pre_validate( - &mut self, - context: &Context, - opcode: Opcode, - stack: &Stack, - ) -> Result<(), ExitError> { - // log::trace!(target: "evm", "Running opcode: {:?}, Pre gas-left: {:?}", opcode, gasometer.gas()); - - if let Some(cost) = gasometer::static_opcode_cost(opcode) { - self.state.metadata_mut().gasometer.record_cost(cost)?; - } else { - let is_static = self.state.metadata().is_static; - let (gas_cost, target, memory_cost) = gasometer::dynamic_opcode_cost( - context.address, - opcode, - stack, - is_static, - self.config, - self, - )?; - - let gasometer = &mut self.state.metadata_mut().gasometer; - gasometer.record_dynamic_cost(gas_cost, memory_cost)?; - - self.state - .record_external_dynamic_opcode_cost(opcode, gas_cost, target)?; - - match target { - StorageTarget::Address(address) => { - self.state.metadata_mut().access_address(address) - } - StorageTarget::Slot(address, key) => { - self.state.metadata_mut().access_storage(address, key) - } - StorageTarget::None => (), - } - } - - Ok(()) - } - - fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError> { - self.state.record_external_operation(op) - } -} - -struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { - executor: &'inner mut StackExecutor<'config, 'precompiles, S, P>, - code_address: H160, - input: &'inner [u8], - gas_limit: Option, - context: &'inner Context, - is_static: bool, -} - -impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle - for StackExecutorHandle<'inner, 'config, 'precompiles, S, P> -{ - // Perform subcall in provided context. - /// Precompile specifies in which context the subcall is executed. - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - gas_limit: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec) { - // For normal calls the cost is recorded at opcode level. - // Since we don't go through opcodes we need manually record the call - // cost. Not doing so will make the code panic as recording the call stipend - // will do an underflow. - let target_is_cold = match self.executor.is_cold(code_address, None) { - Ok(x) => x, - Err(err) => return (ExitReason::Error(err), Vec::new()), - }; - - let target_exists = self.executor.exists(code_address); - - let gas_cost = crate::gasometer::GasCost::Call { - value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), - gas: U256::from(gas_limit.unwrap_or(u64::MAX)), - target_is_cold, - target_exists, - }; - - // We record the length of the input. - let memory_cost = Some(crate::gasometer::MemoryCost { - offset: U256::zero(), - len: input.len().into(), - }); - - if let Err(error) = self - .executor - .state - .metadata_mut() - .gasometer - .record_dynamic_cost(gas_cost, memory_cost) - { - return (ExitReason::Error(error), Vec::new()); - } - - event!(PrecompileSubcall { - code_address, - transfer: &transfer, - input: &input, - target_gas: gas_limit, - is_static, - context - }); - - // Perform the subcall - match Handler::call( - self.executor, - code_address, - transfer, - input, - gas_limit, - is_static, - context.clone(), - ) { - Capture::Exit((s, v)) => (s, v), - Capture::Trap(rt) => { - // Ideally this would pass the interrupt back to the executor so it could be - // handled like any other call, however the type signature of this function does - // not allow it. For now we'll make a recursive call instead of making a breaking - // change to the precompile API. But this means a custom precompile could still - // potentially cause a stack overflow if you're not careful. - let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); - call_stack.push(rt.0); - let (reason, _, return_data) = - self.executor.execute_with_call_stack(&mut call_stack); - emit_exit!(reason, return_data) - } - } - } - - /// Record cost to the Runtime gasometer. - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.executor - .state - .metadata_mut() - .gasometer - .record_cost(cost) - } - - /// Record Substrate specific cost. - fn record_external_cost( - &mut self, - ref_time: Option, - proof_size: Option, - storage_growth: Option, - ) -> Result<(), ExitError> { - self.executor - .state - .record_external_cost(ref_time, proof_size, storage_growth) - } - - /// Refund Substrate specific cost. - fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { - self.executor - .state - .refund_external_cost(ref_time, proof_size); - } - - /// Retreive the remaining gas. - fn remaining_gas(&self) -> u64 { - self.executor.state.metadata().gasometer.gas() - } - - /// Record a log. - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - Handler::log(self.executor, address, topics, data) - } - - /// Retreive the code address (what is the address of the precompile being called). - fn code_address(&self) -> H160 { - self.code_address - } - - /// Retreive the input data the precompile is called with. - fn input(&self) -> &[u8] { - self.input - } - - /// Retreive the context in which the precompile is executed. - fn context(&self) -> &Context { - self.context - } - - /// Is the precompile call is done statically. - fn is_static(&self) -> bool { - self.is_static - } - - /// Retreive the gas limit of this call. - fn gas_limit(&self) -> Option { - self.gas_limit - } -} diff --git a/src/executor/stack/memory.rs b/src/executor/stack/memory.rs deleted file mode 100644 index d9658f033..000000000 --- a/src/executor/stack/memory.rs +++ /dev/null @@ -1,589 +0,0 @@ -use crate::backend::{Apply, Backend, Basic, Log}; -use crate::executor::stack::executor::{Accessed, StackState, StackSubstateMetadata}; -use crate::{ExitError, Transfer}; -use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, - vec::Vec, -}; -use core::mem; -use primitive_types::{H160, H256, U256}; - -#[derive(Clone, Debug)] -pub struct MemoryStackAccount { - pub basic: Basic, - pub code: Option>, - pub reset: bool, -} - -#[derive(Clone, Debug)] -pub struct MemoryStackSubstate<'config> { - metadata: StackSubstateMetadata<'config>, - parent: Option>>, - logs: Vec, - accounts: BTreeMap, - storages: BTreeMap<(H160, H256), H256>, - deletes: BTreeSet, -} - -impl<'config> MemoryStackSubstate<'config> { - pub fn new(metadata: StackSubstateMetadata<'config>) -> Self { - Self { - metadata, - parent: None, - logs: Vec::new(), - accounts: BTreeMap::new(), - storages: BTreeMap::new(), - deletes: BTreeSet::new(), - } - } - - pub fn logs(&self) -> &[Log] { - &self.logs - } - - pub fn logs_mut(&mut self) -> &mut Vec { - &mut self.logs - } - - pub fn metadata(&self) -> &StackSubstateMetadata<'config> { - &self.metadata - } - - pub fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { - &mut self.metadata - } - - /// Deconstruct the memory stack substate, return state to be applied. Panic if the - /// substate is not in the top-level substate. - #[must_use] - pub fn deconstruct( - mut self, - backend: &B, - ) -> ( - impl IntoIterator>>, - impl IntoIterator, - ) { - assert!(self.parent.is_none()); - - let mut applies = Vec::>>::new(); - - let mut addresses = BTreeSet::new(); - - for address in self.accounts.keys() { - addresses.insert(*address); - } - - for (address, _) in self.storages.keys() { - addresses.insert(*address); - } - - for address in addresses { - if self.deletes.contains(&address) { - continue; - } - - let mut storage = BTreeMap::new(); - for ((oa, ok), ov) in &self.storages { - if *oa == address { - storage.insert(*ok, *ov); - } - } - - let apply = { - let account = self.account_mut(address, backend); - - Apply::Modify { - address, - basic: account.basic.clone(), - code: account.code.clone(), - storage, - reset_storage: account.reset, - } - }; - - applies.push(apply); - } - - for address in self.deletes { - applies.push(Apply::Delete { address }); - } - - (applies, self.logs) - } - - pub fn enter(&mut self, gas_limit: u64, is_static: bool) { - let mut entering = Self { - metadata: self.metadata.spit_child(gas_limit, is_static), - parent: None, - logs: Vec::new(), - accounts: BTreeMap::new(), - storages: BTreeMap::new(), - deletes: BTreeSet::new(), - }; - mem::swap(&mut entering, self); - - self.parent = Some(Box::new(entering)); - } - - pub fn exit_commit(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot commit on root substate"); - mem::swap(&mut exited, self); - - self.metadata.swallow_commit(exited.metadata)?; - self.logs.append(&mut exited.logs); - - let mut resets = BTreeSet::new(); - for (address, account) in &exited.accounts { - if account.reset { - resets.insert(*address); - } - } - let mut reset_keys = BTreeSet::new(); - for (address, key) in self.storages.keys() { - if resets.contains(address) { - reset_keys.insert((*address, *key)); - } - } - for (address, key) in reset_keys { - self.storages.remove(&(address, key)); - } - - self.accounts.append(&mut exited.accounts); - self.storages.append(&mut exited.storages); - self.deletes.append(&mut exited.deletes); - - Ok(()) - } - - pub fn exit_revert(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot discard on root substate"); - mem::swap(&mut exited, self); - - self.metadata.swallow_revert(exited.metadata)?; - - Ok(()) - } - - pub fn exit_discard(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot discard on root substate"); - mem::swap(&mut exited, self); - - self.metadata.swallow_discard(exited.metadata)?; - - Ok(()) - } - - pub fn known_account(&self, address: H160) -> Option<&MemoryStackAccount> { - if let Some(account) = self.accounts.get(&address) { - Some(account) - } else if let Some(parent) = self.parent.as_ref() { - parent.known_account(address) - } else { - None - } - } - - pub fn known_basic(&self, address: H160) -> Option { - self.known_account(address).map(|acc| acc.basic.clone()) - } - - pub fn known_code(&self, address: H160) -> Option> { - self.known_account(address).and_then(|acc| acc.code.clone()) - } - - pub fn known_empty(&self, address: H160) -> Option { - if let Some(account) = self.known_account(address) { - if account.basic.balance != U256::zero() { - return Some(false); - } - - if account.basic.nonce != U256::zero() { - return Some(false); - } - - if let Some(code) = &account.code { - return Some( - account.basic.balance == U256::zero() - && account.basic.nonce == U256::zero() - && code.is_empty(), - ); - } - } - - None - } - - pub fn known_storage(&self, address: H160, key: H256) -> Option { - if let Some(value) = self.storages.get(&(address, key)) { - return Some(*value); - } - - if let Some(account) = self.accounts.get(&address) { - if account.reset { - return Some(H256::default()); - } - } - - if let Some(parent) = self.parent.as_ref() { - return parent.known_storage(address, key); - } - - None - } - - pub fn known_original_storage(&self, address: H160) -> Option { - if let Some(account) = self.accounts.get(&address) { - if account.reset { - return Some(H256::default()); - } - } - - if let Some(parent) = self.parent.as_ref() { - return parent.known_original_storage(address); - } - - None - } - - pub fn is_cold(&self, address: H160) -> bool { - self.recursive_is_cold(&|a| a.accessed_addresses.contains(&address)) - } - - pub fn is_storage_cold(&self, address: H160, key: H256) -> bool { - self.recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key))) - } - - fn recursive_is_cold bool>(&self, f: &F) -> bool { - let local_is_accessed = self.metadata.accessed().as_ref().map(f).unwrap_or(false); - if local_is_accessed { - false - } else { - self.parent - .as_ref() - .map(|p| p.recursive_is_cold(f)) - .unwrap_or(true) - } - } - - pub fn deleted(&self, address: H160) -> bool { - if self.deletes.contains(&address) { - return true; - } - - if let Some(parent) = self.parent.as_ref() { - return parent.deleted(address); - } - - false - } - - #[allow(clippy::map_entry)] - fn account_mut(&mut self, address: H160, backend: &B) -> &mut MemoryStackAccount { - if !self.accounts.contains_key(&address) { - let account = self - .known_account(address) - .cloned() - .map(|mut v| { - v.reset = false; - v - }) - .unwrap_or_else(|| MemoryStackAccount { - basic: backend.basic(address), - code: None, - reset: false, - }); - self.accounts.insert(address, account); - } - - self.accounts - .get_mut(&address) - .expect("New account was just inserted") - } - - pub fn inc_nonce(&mut self, address: H160, backend: &B) -> Result<(), ExitError> { - let nonce = &mut self.account_mut(address, backend).basic.nonce; - if *nonce >= U256::from(u64::MAX) { - return Err(ExitError::MaxNonce); - } - *nonce += U256::one(); - Ok(()) - } - - pub fn set_storage(&mut self, address: H160, key: H256, value: H256) { - self.storages.insert((address, key), value); - } - - pub fn reset_storage(&mut self, address: H160, backend: &B) { - let mut removing = Vec::new(); - - for (oa, ok) in self.storages.keys() { - if *oa == address { - removing.push(*ok); - } - } - - for ok in removing { - self.storages.remove(&(address, ok)); - } - - self.account_mut(address, backend).reset = true; - } - - pub fn log(&mut self, address: H160, topics: Vec, data: Vec) { - self.logs.push(Log { - address, - topics, - data, - }); - } - - pub fn set_deleted(&mut self, address: H160) { - self.deletes.insert(address); - } - - pub fn set_code(&mut self, address: H160, code: Vec, backend: &B) { - self.account_mut(address, backend).code = Some(code); - } - - pub fn transfer( - &mut self, - transfer: Transfer, - backend: &B, - ) -> Result<(), ExitError> { - { - let source = self.account_mut(transfer.source, backend); - if source.basic.balance < transfer.value { - return Err(ExitError::OutOfFund); - } - source.basic.balance -= transfer.value; - } - - { - let target = self.account_mut(transfer.target, backend); - target.basic.balance = target.basic.balance.saturating_add(transfer.value); - } - - Ok(()) - } - - // Only needed for jsontests. - pub fn withdraw( - &mut self, - address: H160, - value: U256, - backend: &B, - ) -> Result<(), ExitError> { - let source = self.account_mut(address, backend); - if source.basic.balance < value { - return Err(ExitError::OutOfFund); - } - source.basic.balance -= value; - - Ok(()) - } - - // Only needed for jsontests. - pub fn deposit(&mut self, address: H160, value: U256, backend: &B) { - let target = self.account_mut(address, backend); - target.basic.balance = target.basic.balance.saturating_add(value); - } - - pub fn reset_balance(&mut self, address: H160, backend: &B) { - self.account_mut(address, backend).basic.balance = U256::zero(); - } - - pub fn touch(&mut self, address: H160, backend: &B) { - self.account_mut(address, backend); - } -} - -#[derive(Debug)] -pub struct MemoryStackState<'backend, 'config, B> { - backend: &'backend mut B, - substate: MemoryStackSubstate<'config>, -} - -impl<'backend, 'config, B: Backend> Backend for MemoryStackState<'backend, 'config, B> { - fn gas_price(&self) -> U256 { - self.backend.gas_price() - } - fn origin(&self) -> H160 { - self.backend.origin() - } - fn block_hash(&self, number: U256) -> H256 { - self.backend.block_hash(number) - } - fn block_number(&self) -> U256 { - self.backend.block_number() - } - fn block_coinbase(&self) -> H160 { - self.backend.block_coinbase() - } - fn block_timestamp(&self) -> U256 { - self.backend.block_timestamp() - } - fn block_difficulty(&self) -> U256 { - self.backend.block_difficulty() - } - fn block_randomness(&self) -> Option { - self.backend.block_randomness() - } - fn block_gas_limit(&self) -> U256 { - self.backend.block_gas_limit() - } - fn block_base_fee_per_gas(&self) -> U256 { - self.backend.block_base_fee_per_gas() - } - - fn chain_id(&self) -> U256 { - self.backend.chain_id() - } - - fn exists(&self, address: H160) -> bool { - self.substate.known_account(address).is_some() || self.backend.exists(address) - } - - fn basic(&self, address: H160) -> Basic { - self.substate - .known_basic(address) - .unwrap_or_else(|| self.backend.basic(address)) - } - - fn code(&self, address: H160) -> Vec { - if let Some(code) = self.substate.known_code(address) { - return code; - } - self.backend.code(address) - } - - fn storage(&self, address: H160, key: H256) -> H256 { - self.substate - .known_storage(address, key) - .unwrap_or_else(|| self.backend.storage(address, key)) - } - - fn original_storage(&self, address: H160, key: H256) -> Option { - if let Some(value) = self.substate.known_original_storage(address) { - return Some(value); - } - - self.backend.original_storage(address, key) - } -} - -impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'backend, 'config, B> { - fn metadata(&self) -> &StackSubstateMetadata<'config> { - self.substate.metadata() - } - - fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { - self.substate.metadata_mut() - } - - fn enter(&mut self, gas_limit: u64, is_static: bool) { - self.substate.enter(gas_limit, is_static) - } - - fn exit_commit(&mut self) -> Result<(), ExitError> { - self.substate.exit_commit() - } - - fn exit_revert(&mut self) -> Result<(), ExitError> { - self.substate.exit_revert() - } - - fn exit_discard(&mut self) -> Result<(), ExitError> { - self.substate.exit_discard() - } - - fn is_empty(&self, address: H160) -> bool { - if let Some(known_empty) = self.substate.known_empty(address) { - return known_empty; - } - - self.backend.basic(address).balance == U256::zero() - && self.backend.basic(address).nonce == U256::zero() - && self.backend.code(address).len() == 0 - } - - fn deleted(&self, address: H160) -> bool { - self.substate.deleted(address) - } - - fn is_cold(&self, address: H160) -> bool { - self.substate.is_cold(address) - } - - fn is_storage_cold(&self, address: H160, key: H256) -> bool { - self.substate.is_storage_cold(address, key) - } - - fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { - self.substate.inc_nonce(address, self.backend) - } - - fn set_storage(&mut self, address: H160, key: H256, value: H256) { - self.substate.set_storage(address, key, value) - } - - fn reset_storage(&mut self, address: H160) { - self.substate.reset_storage(address, self.backend); - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) { - self.substate.log(address, topics, data); - } - - fn set_deleted(&mut self, address: H160) { - self.substate.set_deleted(address) - } - - fn set_code(&mut self, address: H160, code: Vec) { - self.substate.set_code(address, code, self.backend); - } - - fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { - self.substate.transfer(transfer, self.backend) - } - - fn reset_balance(&mut self, address: H160) { - self.substate.reset_balance(address, self.backend) - } - - fn touch(&mut self, address: H160) { - self.substate.touch(address, self.backend) - } -} - -impl<'backend, 'config, B: Backend> MemoryStackState<'backend, 'config, B> { - pub fn new(metadata: StackSubstateMetadata<'config>, backend: &'backend mut B) -> Self { - Self { - backend, - substate: MemoryStackSubstate::new(metadata), - } - } - - /// Returns a mutable reference to an account given its address - pub fn account_mut(&mut self, address: H160) -> &mut MemoryStackAccount { - self.substate.account_mut(address, self.backend) - } - - #[must_use] - pub fn deconstruct( - self, - ) -> ( - impl IntoIterator>>, - impl IntoIterator, - ) { - self.substate.deconstruct(self.backend) - } - - pub fn withdraw(&mut self, address: H160, value: U256) -> Result<(), ExitError> { - self.substate.withdraw(address, value, self.backend) - } - - pub fn deposit(&mut self, address: H160, value: U256) { - self.substate.deposit(address, value, self.backend) - } -} diff --git a/src/executor/stack/mod.rs b/src/executor/stack/mod.rs deleted file mode 100644 index fddb737eb..000000000 --- a/src/executor/stack/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! A stack-based executor with customizable state. -//! A memory-based state is provided, but can replaced by a custom -//! implementation, for exemple one interacting with a database. - -mod executor; -mod memory; -mod precompile; -mod tagged_runtime; - -pub use self::executor::{ - Accessed, StackExecutor, StackExitKind, StackState, StackSubstateMetadata, -}; -pub use self::memory::{MemoryStackAccount, MemoryStackState, MemoryStackSubstate}; -pub use self::precompile::{ - IsPrecompileResult, PrecompileFailure, PrecompileFn, PrecompileHandle, PrecompileOutput, - PrecompileSet, -}; -pub use ethereum::Log; diff --git a/src/executor/stack/precompile.rs b/src/executor/stack/precompile.rs deleted file mode 100644 index c59b32752..000000000 --- a/src/executor/stack/precompile.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::{Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed, Transfer}; -use alloc::{collections::BTreeMap, vec::Vec}; -use primitive_types::{H160, H256}; - -/// A precompile result. -pub type PrecompileResult = Result; - -/// Data returned by a precompile on success. -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PrecompileOutput { - pub exit_status: ExitSucceed, - pub output: Vec, -} - -/// Data returned by a precompile in case of failure. -#[derive(Debug, Eq, PartialEq, Clone)] -pub enum PrecompileFailure { - /// Reverts the state changes and consume all the gas. - Error { exit_status: ExitError }, - /// Reverts the state changes. - /// Returns the provided error message. - Revert { - exit_status: ExitRevert, - output: Vec, - }, - /// Mark this failure as fatal, and all EVM execution stacks must be exited. - Fatal { exit_status: ExitFatal }, -} - -impl From for PrecompileFailure { - fn from(error: ExitError) -> PrecompileFailure { - PrecompileFailure::Error { exit_status: error } - } -} - -/// Handle provided to a precompile to interact with the EVM. -pub trait PrecompileHandle { - /// Perform subcall in provided context. - /// Precompile specifies in which context the subcall is executed. - fn call( - &mut self, - to: H160, - transfer: Option, - input: Vec, - gas_limit: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec); - - /// Record cost to the Runtime gasometer. - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; - - /// Record Substrate specific cost. - fn record_external_cost( - &mut self, - ref_time: Option, - proof_size: Option, - storage_growth: Option, - ) -> Result<(), ExitError>; - - /// Refund Substrate specific cost. - fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option); - - /// Retreive the remaining gas. - fn remaining_gas(&self) -> u64; - - /// Record a log. - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError>; - - /// Retreive the code address (what is the address of the precompile being called). - fn code_address(&self) -> H160; - - /// Retreive the input data the precompile is called with. - fn input(&self) -> &[u8]; - - /// Retreive the context in which the precompile is executed. - fn context(&self) -> &Context; - - /// Is the precompile call is done statically. - fn is_static(&self) -> bool; - - /// Retreive the gas limit of this call. - fn gas_limit(&self) -> Option; -} - -/// A set of precompiles. -/// -/// Checks if the provided address is in the precompile set. This should be -/// as cheap as possible since it may be called often. -pub trait PrecompileSet { - /// Tries to execute a precompile in the precompile set. - /// If the provided address is not a precompile, returns None. - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option; - - /// Check if the given address is a precompile. Should only be called to - /// perform the check while not executing the precompile afterward, since - /// `execute` already performs a check internally. - fn is_precompile(&self, address: H160, remaining_gas: u64) -> IsPrecompileResult; -} - -pub enum IsPrecompileResult { - Answer { - is_precompile: bool, - extra_cost: u64, - }, - OutOfGas, -} - -impl PrecompileSet for () { - fn execute(&self, _: &mut impl PrecompileHandle) -> Option { - None - } - - fn is_precompile(&self, _: H160, _: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: false, - extra_cost: 0, - } - } -} - -/// Precompiles function signature. Expected input arguments are: -/// * Input -/// * Gas limit -/// * Context -/// * Is static -/// -/// In case of success returns the output and the cost. -pub type PrecompileFn = - fn(&[u8], Option, &Context, bool) -> Result<(PrecompileOutput, u64), PrecompileFailure>; - -impl PrecompileSet for BTreeMap { - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - let address = handle.code_address(); - - self.get(&address).map(|precompile| { - let input = handle.input(); - let gas_limit = handle.gas_limit(); - let context = handle.context(); - let is_static = handle.is_static(); - - match (*precompile)(input, gas_limit, context, is_static) { - Ok((output, cost)) => { - handle.record_cost(cost)?; - Ok(output) - } - Err(err) => Err(err), - } - }) - } - - /// Check if the given address is a precompile. Should only be called to - /// perform the check while not executing the precompile afterward, since - /// `execute` already performs a check internally. - fn is_precompile(&self, address: H160, _: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: self.contains_key(&address), - extra_cost: 0, - } - } -} diff --git a/src/executor/stack/tagged_runtime.rs b/src/executor/stack/tagged_runtime.rs deleted file mode 100644 index c0399f8e4..000000000 --- a/src/executor/stack/tagged_runtime.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! A module containing data types for keeping track of the kinds of calls -//! (CALL vs CREATE) in the EVM call stack. - -use crate::maybe_borrowed::MaybeBorrowed; -use crate::Runtime; -use primitive_types::H160; - -pub struct TaggedRuntime<'borrow> { - pub kind: RuntimeKind, - pub inner: MaybeBorrowed<'borrow, Runtime>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RuntimeKind { - Create(H160), - Call(H160), - /// Special variant used only in `StackExecutor::execute` - Execute, -} diff --git a/src/gasometer.rs b/src/gasometer.rs new file mode 100644 index 000000000..95f7fe6f7 --- /dev/null +++ b/src/gasometer.rs @@ -0,0 +1,42 @@ +//! EVM gasometer. + +use crate::{ExitError, Machine}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use primitive_types::U256; + +pub trait Gas: + Copy + + Into + + Add + + AddAssign + + Sub + + SubAssign +{ +} + +impl Gas for u64 {} +impl Gas for U256 {} + +pub trait StaticGasometer: Sized { + fn record_cost(&mut self, cost: U256) -> Result<(), ExitError>; + fn record_codedeposit(&mut self, len: usize) -> Result<(), ExitError>; + fn gas(&self) -> U256; +} + +pub trait Gasometer: StaticGasometer { + fn record_step( + &mut self, + machine: &Machine, + is_static: bool, + backend: &H, + ) -> Result<(), ExitError>; + fn record_stepn( + &mut self, + machine: &Machine, + is_static: bool, + backend: &H, + ) -> Result { + self.record_step(machine, is_static, backend)?; + Ok(1) + } +} diff --git a/src/invoker.rs b/src/invoker.rs new file mode 100644 index 000000000..e2bde3a03 --- /dev/null +++ b/src/invoker.rs @@ -0,0 +1,80 @@ +use crate::{Capture, ExitError, ExitResult}; + +pub enum InvokerControl { + Enter(VE), + DirectExit(VD), +} + +pub trait InvokerMachine { + type Deconstruct; + + fn step(&mut self, handler: &mut H) -> Result<(), Capture>; + fn run(&mut self, handler: &mut H) -> Capture; + fn deconstruct(self) -> Self::Deconstruct; +} + +pub trait Invoker { + type Machine: InvokerMachine; + type Interrupt; + + type TransactArgs; + type TransactInvoke; + type TransactValue; + type SubstackInvoke; + + fn new_transact( + &self, + args: Self::TransactArgs, + handler: &mut H, + ) -> Result< + ( + Self::TransactInvoke, + InvokerControl< + Self::Machine, + ( + ExitResult, + >::Deconstruct, + ), + >, + ), + ExitError, + >; + fn finalize_transact( + &self, + invoke: &Self::TransactInvoke, + exit: ExitResult, + machine: >::Deconstruct, + handler: &mut H, + ) -> Result; + + fn enter_substack( + &self, + trap: Tr, + machine: &mut Self::Machine, + handler: &mut H, + depth: usize, + ) -> Capture< + Result< + ( + Self::SubstackInvoke, + InvokerControl< + Self::Machine, + ( + ExitResult, + >::Deconstruct, + ), + >, + ), + ExitError, + >, + Self::Interrupt, + >; + fn exit_substack( + &self, + result: ExitResult, + child: >::Deconstruct, + trap_data: Self::SubstackInvoke, + parent: &mut Self::Machine, + handler: &mut H, + ) -> Result<(), ExitError>; +} diff --git a/src/lib.rs b/src/lib.rs index 44d3c23c0..9a761b6bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,30 @@ //! Ethereum Virtual Machine implementation in Rust -#![deny(warnings)] -#![forbid(unsafe_code, unused_variables)] +// #![deny(warnings)] +// #![forbid(unsafe_code, unused_variables)] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -pub use evm_core::*; -pub use evm_gasometer as gasometer; -pub use evm_runtime::*; +pub mod backend; +pub mod standard; -#[cfg(feature = "tracing")] -pub mod tracing; +mod call_stack; +mod color; +mod gasometer; +mod invoker; -#[cfg(feature = "tracing")] -macro_rules! event { - ($x:expr) => { - use crate::tracing::Event::*; - crate::tracing::with(|listener| listener.event($x)); - }; -} +pub use evm_interpreter::*; -#[cfg(not(feature = "tracing"))] -macro_rules! event { - ($x:expr) => {}; -} +pub use crate::backend::TransactionalBackend; +pub use crate::call_stack::{transact, HeapTransact}; +pub use crate::color::{Color, ColoredMachine}; +pub use crate::gasometer::{Gas, Gasometer, StaticGasometer}; +pub use crate::invoker::{Invoker, InvokerControl, InvokerMachine}; -pub mod backend; -pub mod executor; -pub mod maybe_borrowed; +#[derive(Clone, Debug, Copy)] +pub enum MergeStrategy { + Commit, + Revert, + Discard, +} diff --git a/src/maybe_borrowed.rs b/src/maybe_borrowed.rs deleted file mode 100644 index a7483bb76..000000000 --- a/src/maybe_borrowed.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! A module containing the `MaybeBorrowed` enum. See its documentation for details. - -/// Similar to `Cow` from the standard library, but without requiring `T: Clone`. -/// Instead of "copy on write", this data structure represents a type that can create -/// `&mut T`, either because it is `&mut T`, or because it is an owned `T`. -/// This is also distinct from the `BorrowMut` trait in the standard library because -/// you can have a single collection mix both borrowed and owned data (e.g. -/// `let xs: Vec> = vec![&mut t1, t2]` would be possible whereas -/// `Vec where B: BorrowMut` would need to consist of all owned or all borrowed data). -#[derive(Debug)] -pub enum MaybeBorrowed<'a, T> { - Borrowed(&'a mut T), - Owned(T), -} - -impl<'a, T> core::ops::Deref for MaybeBorrowed<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - Self::Borrowed(x) => x, - Self::Owned(x) => x, - } - } -} - -impl<'a, T> core::ops::DerefMut for MaybeBorrowed<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Borrowed(x) => x, - Self::Owned(x) => x, - } - } -} diff --git a/runtime/src/lib.rs b/src/standard/config.rs similarity index 70% rename from runtime/src/lib.rs rename to src/standard/config.rs index 8809e58f3..63300bab4 100644 --- a/runtime/src/lib.rs +++ b/src/standard/config.rs @@ -1,192 +1,3 @@ -//! Runtime layer for EVM. - -#![deny(warnings)] -#![forbid(unsafe_code, unused_variables)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -#[cfg(feature = "tracing")] -pub mod tracing; - -#[cfg(feature = "tracing")] -macro_rules! event { - ($x:expr) => { - use crate::tracing::Event::*; - crate::tracing::with(|listener| listener.event($x)); - }; -} - -#[cfg(not(feature = "tracing"))] -macro_rules! event { - ($x:expr) => {}; -} - -mod context; -mod eval; -mod handler; -mod interrupt; - -pub use evm_core::*; - -pub use crate::context::{CallScheme, Context, CreateScheme}; -pub use crate::handler::{Handler, Transfer}; -pub use crate::interrupt::{Resolve, ResolveCall, ResolveCreate}; - -use alloc::rc::Rc; -use alloc::vec::Vec; -use primitive_types::{H160, U256}; - -macro_rules! step { - ( $self:expr, $handler:expr, $return:tt $($err:path)?; $($ok:path)? ) => ({ - if let Some((opcode, stack)) = $self.machine.inspect() { - event!(Step { - context: &$self.context, - opcode, - position: $self.machine.position(), - stack, - memory: $self.machine.memory() - }); - - match $handler.pre_validate(&$self.context, opcode, stack) { - Ok(()) => (), - Err(e) => { - $self.machine.exit(e.clone().into()); - $self.status = Err(e.into()); - }, - } - } - - match &$self.status { - Ok(()) => (), - Err(e) => { - #[allow(unused_parens)] - $return $($err)*(Capture::Exit(e.clone())) - }, - } - - let result = $self.machine.step(); - - event!(StepResult { - result: &result, - return_value: &$self.machine.return_value(), - }); - - match result { - Ok(()) => $($ok)?(()), - Err(Capture::Exit(e)) => { - $self.status = Err(e.clone()); - #[allow(unused_parens)] - $return $($err)*(Capture::Exit(e)) - }, - Err(Capture::Trap(opcode)) => { - match eval::eval($self, opcode, $handler) { - eval::Control::Continue => $($ok)?(()), - eval::Control::CallInterrupt(interrupt) => { - let resolve = ResolveCall::new($self); - #[allow(unused_parens)] - $return $($err)*(Capture::Trap(Resolve::Call(interrupt, resolve))) - }, - eval::Control::CreateInterrupt(interrupt) => { - let resolve = ResolveCreate::new($self); - #[allow(unused_parens)] - $return $($err)*(Capture::Trap(Resolve::Create(interrupt, resolve))) - }, - eval::Control::Exit(exit) => { - $self.machine.exit(exit.clone().into()); - $self.status = Err(exit.clone()); - #[allow(unused_parens)] - $return $($err)*(Capture::Exit(exit)) - }, - } - }, - } - }); -} - -/// EVM runtime. -/// -/// The runtime wraps an EVM `Machine` with support of return data and context. -pub struct Runtime { - machine: Machine, - status: Result<(), ExitReason>, - return_data_buffer: Vec, - return_data_len: U256, - return_data_offset: U256, - context: Context, -} - -impl Runtime { - /// Create a new runtime with given code and data. - pub fn new( - code: Rc>, - data: Rc>, - context: Context, - stack_limit: usize, - memory_limit: usize, - ) -> Self { - Self { - machine: Machine::new(code, data, stack_limit, memory_limit), - status: Ok(()), - return_data_buffer: Vec::new(), - return_data_len: U256::zero(), - return_data_offset: U256::zero(), - context, - } - } - - /// Get a reference to the machine. - pub fn machine(&self) -> &Machine { - &self.machine - } - - /// Get a reference to the execution context. - pub fn context(&self) -> &Context { - &self.context - } - - /// Step the runtime. - pub fn step<'a, H: Handler>( - &'a mut self, - handler: &mut H, - ) -> Result<(), Capture>> { - step!(self, handler, return Err; Ok) - } - - /// Loop stepping the runtime until it stops. - pub fn run<'a, H: Handler>( - &'a mut self, - handler: &mut H, - ) -> Capture> { - loop { - step!(self, handler, return;) - } - } - - pub fn finish_create( - &mut self, - reason: ExitReason, - address: Option, - return_data: Vec, - ) -> Result<(), ExitReason> { - eval::finish_create(self, reason, address, return_data) - } - - pub fn finish_call( - &mut self, - reason: ExitReason, - return_data: Vec, - ) -> Result<(), ExitReason> { - eval::finish_call( - self, - self.return_data_len, - self.return_data_offset, - reason, - return_data, - ) - } -} - /// Runtime configuration. #[derive(Clone, Debug)] pub struct Config { @@ -286,8 +97,6 @@ pub struct Config { pub has_base_fee: bool, /// Has PUSH0 opcode. See [EIP-3855](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3855.md) pub has_push0: bool, - /// Whether the gasometer is running in estimate mode. - pub estimate: bool, } impl Config { @@ -341,7 +150,6 @@ impl Config { has_ext_code_hash: false, has_base_fee: false, has_push0: false, - estimate: false, } } @@ -395,7 +203,6 @@ impl Config { has_ext_code_hash: true, has_base_fee: false, has_push0: false, - estimate: false, } } @@ -492,7 +299,6 @@ impl Config { has_ext_code_hash: true, has_base_fee, has_push0, - estimate: false, } } } diff --git a/src/standard/gasometer/consts.rs b/src/standard/gasometer/consts.rs new file mode 100644 index 000000000..f51085577 --- /dev/null +++ b/src/standard/gasometer/consts.rs @@ -0,0 +1,141 @@ +use evm_interpreter::Opcode; + +pub const G_ZERO: u64 = 0; +pub const G_BASE: u64 = 2; +pub const G_VERYLOW: u64 = 3; +pub const G_LOW: u64 = 5; +pub const G_MID: u64 = 8; +pub const G_HIGH: u64 = 10; +pub const G_JUMPDEST: u64 = 1; +pub const R_SUICIDE: i64 = 24000; +pub const G_CREATE: u64 = 32000; +pub const G_CALLVALUE: u64 = 9000; +pub const G_NEWACCOUNT: u64 = 25000; +pub const G_EXP: u64 = 10; +pub const G_MEMORY: u64 = 3; +pub const G_LOG: u64 = 375; +pub const G_LOGDATA: u64 = 8; +pub const G_LOGTOPIC: u64 = 375; +pub const G_SHA3: u64 = 30; +pub const G_SHA3WORD: u64 = 6; +pub const G_COPY: u64 = 3; +pub const G_BLOCKHASH: u64 = 20; +pub const G_CODEDEPOSIT: u64 = 200; + +pub static STATIC_COST_TABLE: [Option; 256] = { + let mut table = [None; 256]; + + table[Opcode::STOP.as_usize()] = Some(G_ZERO); + table[Opcode::CALLDATASIZE.as_usize()] = Some(G_BASE); + table[Opcode::CODESIZE.as_usize()] = Some(G_BASE); + table[Opcode::POP.as_usize()] = Some(G_BASE); + table[Opcode::PC.as_usize()] = Some(G_BASE); + table[Opcode::MSIZE.as_usize()] = Some(G_BASE); + + table[Opcode::ADDRESS.as_usize()] = Some(G_BASE); + table[Opcode::ORIGIN.as_usize()] = Some(G_BASE); + table[Opcode::CALLER.as_usize()] = Some(G_BASE); + table[Opcode::CALLVALUE.as_usize()] = Some(G_BASE); + table[Opcode::COINBASE.as_usize()] = Some(G_BASE); + table[Opcode::TIMESTAMP.as_usize()] = Some(G_BASE); + table[Opcode::NUMBER.as_usize()] = Some(G_BASE); + table[Opcode::DIFFICULTY.as_usize()] = Some(G_BASE); + table[Opcode::GASLIMIT.as_usize()] = Some(G_BASE); + table[Opcode::GASPRICE.as_usize()] = Some(G_BASE); + table[Opcode::GAS.as_usize()] = Some(G_BASE); + + table[Opcode::ADD.as_usize()] = Some(G_VERYLOW); + table[Opcode::SUB.as_usize()] = Some(G_VERYLOW); + table[Opcode::NOT.as_usize()] = Some(G_VERYLOW); + table[Opcode::LT.as_usize()] = Some(G_VERYLOW); + table[Opcode::GT.as_usize()] = Some(G_VERYLOW); + table[Opcode::SLT.as_usize()] = Some(G_VERYLOW); + table[Opcode::SGT.as_usize()] = Some(G_VERYLOW); + table[Opcode::EQ.as_usize()] = Some(G_VERYLOW); + table[Opcode::ISZERO.as_usize()] = Some(G_VERYLOW); + table[Opcode::AND.as_usize()] = Some(G_VERYLOW); + table[Opcode::OR.as_usize()] = Some(G_VERYLOW); + table[Opcode::XOR.as_usize()] = Some(G_VERYLOW); + table[Opcode::BYTE.as_usize()] = Some(G_VERYLOW); + table[Opcode::CALLDATALOAD.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH1.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH2.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH3.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH4.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH5.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH6.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH7.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH8.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH9.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH10.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH11.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH12.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH13.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH14.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH15.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH16.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH17.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH18.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH19.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH20.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH21.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH22.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH23.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH24.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH25.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH26.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH27.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH28.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH29.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH30.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH31.as_usize()] = Some(G_VERYLOW); + table[Opcode::PUSH32.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP1.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP2.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP3.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP4.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP5.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP6.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP7.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP8.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP9.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP10.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP11.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP12.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP13.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP14.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP15.as_usize()] = Some(G_VERYLOW); + table[Opcode::DUP16.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP1.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP2.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP3.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP4.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP5.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP6.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP7.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP8.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP9.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP10.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP11.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP12.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP13.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP14.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP15.as_usize()] = Some(G_VERYLOW); + table[Opcode::SWAP16.as_usize()] = Some(G_VERYLOW); + + table[Opcode::MUL.as_usize()] = Some(G_LOW); + table[Opcode::DIV.as_usize()] = Some(G_LOW); + table[Opcode::SDIV.as_usize()] = Some(G_LOW); + table[Opcode::MOD.as_usize()] = Some(G_LOW); + table[Opcode::SMOD.as_usize()] = Some(G_LOW); + table[Opcode::SIGNEXTEND.as_usize()] = Some(G_LOW); + + table[Opcode::ADDMOD.as_usize()] = Some(G_MID); + table[Opcode::MULMOD.as_usize()] = Some(G_MID); + table[Opcode::JUMP.as_usize()] = Some(G_MID); + + table[Opcode::JUMPI.as_usize()] = Some(G_HIGH); + table[Opcode::JUMPDEST.as_usize()] = Some(G_JUMPDEST); + + table +}; diff --git a/gasometer/src/costs.rs b/src/standard/gasometer/costs.rs similarity index 72% rename from gasometer/src/costs.rs rename to src/standard/gasometer/costs.rs index c25f5db9c..dda497e66 100644 --- a/gasometer/src/costs.rs +++ b/src/standard/gasometer/costs.rs @@ -1,11 +1,12 @@ -use crate::consts::*; -use crate::Config; -use evm_core::ExitError; +use super::consts::*; +use super::utils::log2floor; +use crate::standard::Config; +use evm_interpreter::ExitException; use primitive_types::{H256, U256}; -pub fn call_extra_check(gas: U256, after_gas: u64, config: &Config) -> Result<(), ExitError> { +pub fn call_extra_check(gas: U256, after_gas: u64, config: &Config) -> Result<(), ExitException> { if config.err_on_call_with_more_gas && U256::from(after_gas) < gas { - Err(ExitError::OutOfGas) + Err(ExitException::OutOfGas) } else { Ok(()) } @@ -58,7 +59,7 @@ pub fn sstore_refund(original: H256, current: H256, new: H256, config: &Config) } } -pub fn create2_cost(len: U256) -> Result { +pub fn create2_cost(len: U256) -> Result { let base = U256::from(G_CREATE); // ceil(len / 32.0) let sha_addup_base = len / U256::from(32) @@ -69,37 +70,37 @@ pub fn create2_cost(len: U256) -> Result { }; let sha_addup = U256::from(G_SHA3WORD) .checked_mul(sha_addup_base) - .ok_or(ExitError::OutOfGas)?; - let gas = base.checked_add(sha_addup).ok_or(ExitError::OutOfGas)?; + .ok_or(ExitException::OutOfGas)?; + let gas = base.checked_add(sha_addup).ok_or(ExitException::OutOfGas)?; if gas > U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) } -pub fn exp_cost(power: U256, config: &Config) -> Result { +pub fn exp_cost(power: U256, config: &Config) -> Result { if power == U256::zero() { Ok(G_EXP) } else { let gas = U256::from(G_EXP) .checked_add( U256::from(config.gas_expbyte) - .checked_mul(U256::from(crate::utils::log2floor(power) / 8 + 1)) - .ok_or(ExitError::OutOfGas)?, + .checked_mul(U256::from(log2floor(power) / 8 + 1)) + .ok_or(ExitException::OutOfGas)?, ) - .ok_or(ExitError::OutOfGas)?; + .ok_or(ExitException::OutOfGas)?; if gas > U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) } } -pub fn verylowcopy_cost(len: U256) -> Result { +pub fn verylowcopy_cost(len: U256) -> Result { let wordd = len / U256::from(32); let wordr = len % U256::from(32); @@ -111,18 +112,18 @@ pub fn verylowcopy_cost(len: U256) -> Result { } else { wordd + U256::one() }) - .ok_or(ExitError::OutOfGas)?, + .ok_or(ExitException::OutOfGas)?, ) - .ok_or(ExitError::OutOfGas)?; + .ok_or(ExitException::OutOfGas)?; if gas > U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) } -pub fn extcodecopy_cost(len: U256, is_cold: bool, config: &Config) -> Result { +pub fn extcodecopy_cost(len: U256, is_cold: bool, config: &Config) -> Result { let wordd = len / U256::from(32); let wordr = len % U256::from(32); let gas = U256::from(address_access_cost(is_cold, config.gas_ext_code, config)) @@ -133,36 +134,36 @@ pub fn extcodecopy_cost(len: U256, is_cold: bool, config: &Config) -> Result U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) } -pub fn log_cost(n: u8, len: U256) -> Result { +pub fn log_cost(n: u8, len: U256) -> Result { let gas = U256::from(G_LOG) .checked_add( U256::from(G_LOGDATA) .checked_mul(len) - .ok_or(ExitError::OutOfGas)?, + .ok_or(ExitException::OutOfGas)?, ) - .ok_or(ExitError::OutOfGas)? + .ok_or(ExitException::OutOfGas)? .checked_add(U256::from(G_LOGTOPIC * n as u64)) - .ok_or(ExitError::OutOfGas)?; + .ok_or(ExitException::OutOfGas)?; if gas > U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) } -pub fn sha3_cost(len: U256) -> Result { +pub fn sha3_cost(len: U256) -> Result { let wordd = len / U256::from(32); let wordr = len % U256::from(32); @@ -174,12 +175,12 @@ pub fn sha3_cost(len: U256) -> Result { } else { wordd + U256::one() }) - .ok_or(ExitError::OutOfGas)?, + .ok_or(ExitException::OutOfGas)?, ) - .ok_or(ExitError::OutOfGas)?; + .ok_or(ExitException::OutOfGas)?; if gas > U256::from(u64::MAX) { - return Err(ExitError::OutOfGas); + return Err(ExitException::OutOfGas); } Ok(gas.as_u64()) @@ -205,35 +206,31 @@ pub fn sstore_cost( gas: u64, is_cold: bool, config: &Config, -) -> Result { - let gas_cost = if config.estimate { - config.gas_sstore_set - } else { - if config.sstore_gas_metering { - if config.sstore_revert_under_stipend && gas <= config.call_stipend { - return Err(ExitError::OutOfGas); - } +) -> Result { + let gas_cost = if config.sstore_gas_metering { + if config.sstore_revert_under_stipend && gas <= config.call_stipend { + return Err(ExitException::OutOfGas); + } - if new == current { - config.gas_sload - } else { - if original == current { - if original == H256::zero() { - config.gas_sstore_set - } else { - config.gas_sstore_reset - } + if new == current { + config.gas_sload + } else { + if original == current { + if original == H256::zero() { + config.gas_sstore_set } else { - config.gas_sload + config.gas_sstore_reset } - } - } else { - if current == H256::zero() && new != H256::zero() { - config.gas_sstore_set } else { - config.gas_sstore_reset + config.gas_sload } } + } else { + if current == H256::zero() && new != H256::zero() { + config.gas_sstore_set + } else { + config.gas_sstore_reset + } }; Ok( // In EIP-2929 we charge extra if the slot has not been used yet in this transaction @@ -323,3 +320,12 @@ fn new_cost( 0 } } + +pub fn memory_gas(a: usize) -> Result { + let a = a as u64; + G_MEMORY + .checked_mul(a) + .ok_or(ExitException::OutOfGas)? + .checked_add(a.checked_mul(a).ok_or(ExitException::OutOfGas)? / 512) + .ok_or(ExitException::OutOfGas) +} diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs new file mode 100644 index 000000000..05da39ef4 --- /dev/null +++ b/src/standard/gasometer/mod.rs @@ -0,0 +1,920 @@ +mod consts; +mod costs; +mod utils; + +use crate::standard::Config; +use crate::{ + ExitError, ExitException, ExitFatal, Gasometer as GasometerT, Machine, MergeStrategy, Opcode, + RuntimeBackend, RuntimeState, Stack, StaticGasometer, +}; +use core::cmp::{max, min}; +use primitive_types::{H160, H256, U256}; + +pub trait TransactGasometer<'config, S: AsRef>: Sized { + fn new_transact_call( + gas_limit: U256, + data: &[u8], + access_list: &Vec<(H160, Vec)>, + config: &'config Config, + ) -> Result; + + fn new_transact_create( + gas_limit: U256, + code: &[u8], + access_list: &Vec<(H160, Vec)>, + config: &'config Config, + ) -> Result; + + fn effective_gas(&self) -> U256; + + fn submeter(&mut self, gas_limit: U256, call_has_value: bool) -> Result; + + fn merge(&mut self, other: Self, strategy: MergeStrategy); + + /// Analyse the code so that the gasometer can apply further optimizations. + fn analyse_code(&mut self, _code: &[u8]) {} +} + +pub struct Gasometer<'config> { + gas_limit: u64, + memory_gas: u64, + used_gas: u64, + refunded_gas: u64, + config: &'config Config, +} + +impl<'config> Gasometer<'config> { + #[inline] + pub fn perform Result>( + &mut self, + f: F, + ) -> Result { + match f(self) { + Ok(r) => Ok(r), + Err(e) => { + self.oog(); + Err(e) + } + } + } + + pub fn oog(&mut self) { + self.memory_gas = 0; + self.refunded_gas = 0; + self.used_gas = self.gas_limit; + } + + /// Total used gas. Simply used gas plus memory cost. + pub fn total_used_gas(&self) -> u64 { + self.used_gas + self.memory_gas + } + + pub fn gas(&self) -> u64 { + self.gas_limit - self.memory_gas - self.used_gas + } + + /// Record an explicit cost. + pub fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + let all_gas_cost = self.total_used_gas().checked_add(cost); + if let Some(all_gas_cost) = all_gas_cost { + if self.gas_limit < all_gas_cost { + Err(ExitException::OutOfGas.into()) + } else { + self.used_gas += cost; + Ok(()) + } + } else { + Err(ExitException::OutOfGas.into()) + } + } + + pub fn set_memory_gas(&mut self, memory_cost: u64) -> Result<(), ExitError> { + let all_gas_cost = self.used_gas.checked_add(memory_cost); + if let Some(all_gas_cost) = all_gas_cost { + if self.gas_limit < all_gas_cost { + Err(ExitException::OutOfGas.into()) + } else { + self.memory_gas = memory_cost; + Ok(()) + } + } else { + Err(ExitException::OutOfGas.into()) + } + } + + pub fn new(gas_limit: u64, config: &'config Config) -> Self { + Self { + gas_limit, + memory_gas: 0, + used_gas: 0, + refunded_gas: 0, + config, + } + } +} + +impl<'config, S: AsRef> TransactGasometer<'config, S> for Gasometer<'config> { + fn new_transact_call( + gas_limit: U256, + data: &[u8], + access_list: &Vec<(H160, Vec)>, + config: &'config Config, + ) -> Result { + let gas_limit = if gas_limit > U256::from(u64::MAX) { + return Err(ExitException::OutOfGas.into()); + } else { + gas_limit.as_u64() + }; + + let mut s = Self::new(gas_limit, config); + let transaction_cost = TransactionCost::call(data, access_list).cost(config); + + s.record_cost(transaction_cost)?; + Ok(s) + } + + fn new_transact_create( + gas_limit: U256, + code: &[u8], + access_list: &Vec<(H160, Vec)>, + config: &'config Config, + ) -> Result { + let gas_limit = if gas_limit > U256::from(u64::MAX) { + return Err(ExitException::OutOfGas.into()); + } else { + gas_limit.as_u64() + }; + + let mut s = Self::new(gas_limit, config); + let transaction_cost = TransactionCost::create(code, access_list).cost(config); + + s.record_cost(transaction_cost)?; + Ok(s) + } + + fn effective_gas(&self) -> U256 { + U256::from( + self.gas_limit + - (self.total_used_gas() + - min( + self.total_used_gas() / self.config.max_refund_quotient, + self.refunded_gas, + )), + ) + } + + fn submeter(&mut self, gas_limit: U256, call_has_value: bool) -> Result { + let mut gas_limit = if gas_limit > U256::from(u64::MAX) { + return Err(ExitException::OutOfGas.into()); + } else { + gas_limit.as_u64() + }; + + self.record_cost(gas_limit)?; + + if call_has_value { + gas_limit = gas_limit.saturating_add(self.config.call_stipend); + } + + Ok(Self::new(gas_limit, self.config)) + } + + fn merge(&mut self, other: Self, strategy: MergeStrategy) { + match strategy { + MergeStrategy::Commit => { + self.used_gas -= other.gas(); + self.refunded_gas += other.refunded_gas; + } + MergeStrategy::Revert => { + self.used_gas -= other.gas(); + } + MergeStrategy::Discard => {} + } + } +} + +impl<'config> StaticGasometer for Gasometer<'config> { + fn record_codedeposit(&mut self, len: usize) -> Result<(), ExitError> { + self.perform(|gasometer| { + let cost = len as u64 * consts::G_CODEDEPOSIT; + gasometer.record_cost(cost)?; + Ok(()) + }) + } + + fn record_cost(&mut self, cost: U256) -> Result<(), ExitError> { + if cost > U256::from(u64::MAX) { + return Err(ExitException::OutOfGas.into()); + } + + self.record_cost(cost.as_u64()) + } + + fn gas(&self) -> U256 { + self.gas().into() + } +} + +impl<'config, S: AsRef, H: RuntimeBackend> GasometerT for Gasometer<'config> { + fn record_step( + &mut self, + machine: &Machine, + is_static: bool, + handler: &H, + ) -> Result<(), ExitError> { + if machine.is_empty() { + return Ok(()); + } + + self.perform(|gasometer| { + let opcode = machine.peek_opcode().ok_or(ExitFatal::AlreadyExited)?; + + if let Some(cost) = consts::STATIC_COST_TABLE[opcode.as_usize()] { + gasometer.record_cost(cost)?; + } else { + let address = machine.state.as_ref().context.address; + let (gas, memory_gas) = dynamic_opcode_cost( + address, + opcode, + &machine.stack, + is_static, + gasometer.config, + handler, + )?; + let cost = gas.cost(gasometer.gas(), gasometer.config)?; + let refund = gas.refund(gasometer.config); + + gasometer.record_cost(cost)?; + if refund >= 0 { + gasometer.refunded_gas += refund as u64; + } else { + gasometer.refunded_gas = gasometer.refunded_gas.saturating_sub(-refund as u64); + } + if let Some(memory_gas) = memory_gas { + let memory_cost = memory_gas.cost()?; + if let Some(memory_cost) = memory_cost { + gasometer.set_memory_gas(max(gasometer.memory_gas, memory_cost))?; + } + } + + let after_gas = gasometer.gas(); + gas.extra_check(after_gas, gasometer.config)?; + } + + Ok(()) + }) + } +} + +/// Calculate the opcode cost. +#[allow(clippy::nonminimal_bool)] +fn dynamic_opcode_cost( + address: H160, + opcode: Opcode, + stack: &Stack, + is_static: bool, + config: &Config, + handler: &H, +) -> Result<(GasCost, Option), ExitError> { + let gas_cost = match opcode { + Opcode::RETURN => GasCost::Zero, + + Opcode::MLOAD | Opcode::MSTORE | Opcode::MSTORE8 => GasCost::VeryLow, + + Opcode::REVERT if config.has_revert => GasCost::Zero, + Opcode::REVERT => GasCost::Invalid(opcode), + + Opcode::CHAINID if config.has_chain_id => GasCost::Base, + Opcode::CHAINID => GasCost::Invalid(opcode), + + Opcode::SHL | Opcode::SHR | Opcode::SAR if config.has_bitwise_shifting => GasCost::VeryLow, + Opcode::SHL | Opcode::SHR | Opcode::SAR => GasCost::Invalid(opcode), + + Opcode::SELFBALANCE if config.has_self_balance => GasCost::Low, + Opcode::SELFBALANCE => GasCost::Invalid(opcode), + + Opcode::BASEFEE if config.has_base_fee => GasCost::Base, + Opcode::BASEFEE => GasCost::Invalid(opcode), + + Opcode::EXTCODESIZE => { + let target = stack.peek(0)?.into(); + GasCost::ExtCodeSize { + target_is_cold: handler.is_cold(target, None), + } + } + Opcode::BALANCE => { + let target = stack.peek(0)?.into(); + GasCost::Balance { + target_is_cold: handler.is_cold(target, None), + } + } + Opcode::BLOCKHASH => GasCost::BlockHash, + + Opcode::EXTCODEHASH if config.has_ext_code_hash => { + let target = stack.peek(0)?.into(); + GasCost::ExtCodeHash { + target_is_cold: handler.is_cold(target, None), + } + } + Opcode::EXTCODEHASH => GasCost::Invalid(opcode), + + Opcode::CALLCODE => { + let target = stack.peek(1)?.into(); + GasCost::CallCode { + value: U256::from_big_endian(&stack.peek(2)?[..]), + gas: U256::from_big_endian(&stack.peek(0)?[..]), + target_is_cold: handler.is_cold(target, None), + target_exists: { handler.exists(target) }, + } + } + Opcode::STATICCALL => { + let target = stack.peek(1)?.into(); + GasCost::StaticCall { + gas: U256::from_big_endian(&stack.peek(0)?[..]), + target_is_cold: handler.is_cold(target, None), + target_exists: { handler.exists(target) }, + } + } + Opcode::SHA3 => GasCost::Sha3 { + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::EXTCODECOPY => { + let target = stack.peek(0)?.into(); + GasCost::ExtCodeCopy { + target_is_cold: handler.is_cold(target, None), + len: U256::from_big_endian(&stack.peek(3)?[..]), + } + } + Opcode::CALLDATACOPY | Opcode::CODECOPY => GasCost::VeryLowCopy { + len: U256::from_big_endian(&stack.peek(2)?[..]), + }, + Opcode::EXP => GasCost::Exp { + power: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::SLOAD => { + let index = stack.peek(0)?; + GasCost::SLoad { + target_is_cold: handler.is_cold(address, Some(index)), + } + } + + Opcode::DELEGATECALL if config.has_delegate_call => { + let target = stack.peek(1)?.into(); + GasCost::DelegateCall { + gas: U256::from_big_endian(&stack.peek(0)?[..]), + target_is_cold: handler.is_cold(target, None), + target_exists: { handler.exists(target) }, + } + } + Opcode::DELEGATECALL => GasCost::Invalid(opcode), + + Opcode::RETURNDATASIZE if config.has_return_data => GasCost::Base, + Opcode::RETURNDATACOPY if config.has_return_data => GasCost::VeryLowCopy { + len: U256::from_big_endian(&stack.peek(2)?[..]), + }, + Opcode::RETURNDATASIZE | Opcode::RETURNDATACOPY => GasCost::Invalid(opcode), + + Opcode::SSTORE if !is_static => { + let index = stack.peek(0)?; + let value = stack.peek(1)?; + + GasCost::SStore { + original: handler.original_storage(address, index), + current: handler.storage(address, index), + new: value, + target_is_cold: handler.is_cold(address, Some(index)), + } + } + Opcode::LOG0 if !is_static => GasCost::Log { + n: 0, + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::LOG1 if !is_static => GasCost::Log { + n: 1, + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::LOG2 if !is_static => GasCost::Log { + n: 2, + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::LOG3 if !is_static => GasCost::Log { + n: 3, + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::LOG4 if !is_static => GasCost::Log { + n: 4, + len: U256::from_big_endian(&stack.peek(1)?[..]), + }, + Opcode::CREATE if !is_static => GasCost::Create, + Opcode::CREATE2 if !is_static && config.has_create2 => GasCost::Create2 { + len: U256::from_big_endian(&stack.peek(2)?[..]), + }, + Opcode::SUICIDE if !is_static => { + let target = stack.peek(0)?.into(); + GasCost::Suicide { + value: handler.balance(address), + target_is_cold: handler.is_cold(target, None), + target_exists: { handler.exists(target) }, + already_removed: handler.deleted(address), + } + } + Opcode::CALL + if !is_static + || (is_static && U256::from_big_endian(&stack.peek(2)?[..]) == U256::zero()) => + { + let target = stack.peek(1)?.into(); + GasCost::Call { + value: U256::from_big_endian(&stack.peek(2)?[..]), + gas: U256::from_big_endian(&stack.peek(0)?[..]), + target_is_cold: handler.is_cold(target, None), + target_exists: { handler.exists(target) }, + } + } + + Opcode::PUSH0 if config.has_push0 => GasCost::Base, + + _ => GasCost::Invalid(opcode), + }; + + let memory_cost = match opcode { + Opcode::SHA3 + | Opcode::RETURN + | Opcode::REVERT + | Opcode::LOG0 + | Opcode::LOG1 + | Opcode::LOG2 + | Opcode::LOG3 + | Opcode::LOG4 => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(0)?[..]), + len: U256::from_big_endian(&stack.peek(1)?[..]), + }), + + Opcode::CODECOPY | Opcode::CALLDATACOPY | Opcode::RETURNDATACOPY => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(0)?[..]), + len: U256::from_big_endian(&stack.peek(2)?[..]), + }), + + Opcode::EXTCODECOPY => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(1)?[..]), + len: U256::from_big_endian(&stack.peek(3)?[..]), + }), + + Opcode::MLOAD | Opcode::MSTORE => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(0)?[..]), + len: U256::from(32), + }), + + Opcode::MSTORE8 => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(0)?[..]), + len: U256::from(1), + }), + + Opcode::CREATE | Opcode::CREATE2 => Some(MemoryCost { + offset: U256::from_big_endian(&stack.peek(1)?[..]), + len: U256::from_big_endian(&stack.peek(2)?[..]), + }), + + Opcode::CALL | Opcode::CALLCODE => Some( + MemoryCost { + offset: U256::from_big_endian(&stack.peek(3)?[..]), + len: U256::from_big_endian(&stack.peek(4)?[..]), + } + .join(MemoryCost { + offset: U256::from_big_endian(&stack.peek(5)?[..]), + len: U256::from_big_endian(&stack.peek(6)?[..]), + }), + ), + + Opcode::DELEGATECALL | Opcode::STATICCALL => Some( + MemoryCost { + offset: U256::from_big_endian(&stack.peek(2)?[..]), + len: U256::from_big_endian(&stack.peek(3)?[..]), + } + .join(MemoryCost { + offset: U256::from_big_endian(&stack.peek(4)?[..]), + len: U256::from_big_endian(&stack.peek(5)?[..]), + }), + ), + + _ => None, + }; + + Ok((gas_cost, memory_cost)) +} + +/// Gas cost. +#[derive(Debug, Clone, Copy)] +pub enum GasCost { + /// Zero gas cost. + Zero, + /// Base gas cost. + Base, + /// Very low gas cost. + VeryLow, + /// Low gas cost. + Low, + /// Fail the gasometer. + Invalid(Opcode), + + /// Gas cost for `EXTCODESIZE`. + ExtCodeSize { + /// True if address has not been previously accessed in this transaction + target_is_cold: bool, + }, + /// Gas cost for `BALANCE`. + Balance { + /// True if address has not been previously accessed in this transaction + target_is_cold: bool, + }, + /// Gas cost for `BLOCKHASH`. + BlockHash, + /// Gas cost for `EXTBLOCKHASH`. + ExtCodeHash { + /// True if address has not been previously accessed in this transaction + target_is_cold: bool, + }, + + /// Gas cost for `CALL`. + Call { + /// Call value. + value: U256, + /// Call gas. + gas: U256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Whether the target exists. + target_exists: bool, + }, + /// Gas cost for `CALLCODE. + CallCode { + /// Call value. + value: U256, + /// Call gas. + gas: U256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Whether the target exists. + target_exists: bool, + }, + /// Gas cost for `DELEGATECALL`. + DelegateCall { + /// Call gas. + gas: U256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Whether the target exists. + target_exists: bool, + }, + /// Gas cost for `STATICCALL`. + StaticCall { + /// Call gas. + gas: U256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Whether the target exists. + target_exists: bool, + }, + /// Gas cost for `SUICIDE`. + Suicide { + /// Value. + value: U256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Whether the target exists. + target_exists: bool, + /// Whether the target has already been removed. + already_removed: bool, + }, + /// Gas cost for `SSTORE`. + SStore { + /// Original value. + original: H256, + /// Current value. + current: H256, + /// New value. + new: H256, + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + }, + /// Gas cost for `SHA3`. + Sha3 { + /// Length of the data. + len: U256, + }, + /// Gas cost for `LOG`. + Log { + /// Topic length. + n: u8, + /// Data length. + len: U256, + }, + /// Gas cost for `EXTCODECOPY`. + ExtCodeCopy { + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + /// Length. + len: U256, + }, + /// Gas cost for some copy opcodes that is documented as `VERYLOW`. + VeryLowCopy { + /// Length. + len: U256, + }, + /// Gas cost for `EXP`. + Exp { + /// Power of `EXP`. + power: U256, + }, + /// Gas cost for `CREATE`. + Create, + /// Gas cost for `CREATE2`. + Create2 { + /// Length. + len: U256, + }, + /// Gas cost for `SLOAD`. + SLoad { + /// True if target has not been previously accessed in this transaction + target_is_cold: bool, + }, +} + +impl GasCost { + /// Returns the gas cost numerical value. + pub fn cost(&self, gas: u64, config: &Config) -> Result { + Ok(match *self { + GasCost::Call { + value, + target_is_cold, + target_exists, + .. + } => costs::call_cost(value, target_is_cold, true, true, !target_exists, config), + GasCost::CallCode { + value, + target_is_cold, + target_exists, + .. + } => costs::call_cost(value, target_is_cold, true, false, !target_exists, config), + GasCost::DelegateCall { + target_is_cold, + target_exists, + .. + } => costs::call_cost( + U256::zero(), + target_is_cold, + false, + false, + !target_exists, + config, + ), + GasCost::StaticCall { + target_is_cold, + target_exists, + .. + } => costs::call_cost( + U256::zero(), + target_is_cold, + false, + true, + !target_exists, + config, + ), + + GasCost::Suicide { + value, + target_is_cold, + target_exists, + .. + } => costs::suicide_cost(value, target_is_cold, target_exists, config), + GasCost::SStore { + original, + current, + new, + target_is_cold, + } => costs::sstore_cost(original, current, new, gas, target_is_cold, config)?, + + GasCost::Sha3 { len } => costs::sha3_cost(len)?, + GasCost::Log { n, len } => costs::log_cost(n, len)?, + GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?, + GasCost::Exp { power } => costs::exp_cost(power, config)?, + GasCost::Create => consts::G_CREATE, + GasCost::Create2 { len } => costs::create2_cost(len)?, + GasCost::SLoad { target_is_cold } => costs::sload_cost(target_is_cold, config), + + GasCost::Zero => consts::G_ZERO, + GasCost::Base => consts::G_BASE, + GasCost::VeryLow => consts::G_VERYLOW, + GasCost::Low => consts::G_LOW, + GasCost::Invalid(opcode) => return Err(ExitException::InvalidOpcode(opcode).into()), + + GasCost::ExtCodeSize { target_is_cold } => { + costs::address_access_cost(target_is_cold, config.gas_ext_code, config) + } + GasCost::ExtCodeCopy { + target_is_cold, + len, + } => costs::extcodecopy_cost(len, target_is_cold, config)?, + GasCost::Balance { target_is_cold } => { + costs::address_access_cost(target_is_cold, config.gas_balance, config) + } + GasCost::BlockHash => consts::G_BLOCKHASH, + GasCost::ExtCodeHash { target_is_cold } => { + costs::address_access_cost(target_is_cold, config.gas_ext_code_hash, config) + } + }) + } + + /// Numeric value for the refund. + pub fn refund(&self, config: &Config) -> i64 { + match *self { + GasCost::SStore { + original, + current, + new, + .. + } => costs::sstore_refund(original, current, new, config), + GasCost::Suicide { + already_removed, .. + } if !config.decrease_clears_refund => costs::suicide_refund(already_removed), + _ => 0, + } + } + + /// Extra check of the cost. + pub fn extra_check(&self, after_gas: u64, config: &Config) -> Result<(), ExitException> { + match *self { + GasCost::Call { gas, .. } => costs::call_extra_check(gas, after_gas, config), + GasCost::CallCode { gas, .. } => costs::call_extra_check(gas, after_gas, config), + GasCost::DelegateCall { gas, .. } => costs::call_extra_check(gas, after_gas, config), + GasCost::StaticCall { gas, .. } => costs::call_extra_check(gas, after_gas, config), + _ => Ok(()), + } + } +} + +/// Memory cost. +#[derive(Debug, Clone, Copy)] +pub struct MemoryCost { + /// Affected memory offset. + pub offset: U256, + /// Affected length. + pub len: U256, +} + +impl MemoryCost { + /// Join two memory cost together. + pub fn join(self, other: MemoryCost) -> MemoryCost { + if self.len == U256::zero() { + return other; + } + + if other.len == U256::zero() { + return self; + } + + let self_end = self.offset.saturating_add(self.len); + let other_end = other.offset.saturating_add(other.len); + + if self_end >= other_end { + self + } else { + other + } + } + + /// Numeric value of the cost. + pub fn cost(&self) -> Result, ExitError> { + let from = self.offset; + let len = self.len; + + if len == U256::zero() { + return Ok(None); + } + + let end = from.checked_add(len).ok_or(ExitException::OutOfGas)?; + + if end > U256::from(usize::MAX) { + return Err(ExitException::OutOfGas.into()); + } + let end = end.as_usize(); + + let rem = end % 32; + let new = if rem == 0 { end / 32 } else { end / 32 + 1 }; + + Ok(Some(costs::memory_gas(new)?)) + } +} + +/// Transaction cost. +#[derive(Debug, Clone, Copy)] +pub enum TransactionCost { + /// Call transaction cost. + Call { + /// Length of zeros in transaction data. + zero_data_len: usize, + /// Length of non-zeros in transaction data. + non_zero_data_len: usize, + /// Number of addresses in transaction access list (see EIP-2930) + access_list_address_len: usize, + /// Total number of storage keys in transaction access list (see EIP-2930) + access_list_storage_len: usize, + }, + /// Create transaction cost. + Create { + /// Length of zeros in transaction data. + zero_data_len: usize, + /// Length of non-zeros in transaction data. + non_zero_data_len: usize, + /// Number of addresses in transaction access list (see EIP-2930) + access_list_address_len: usize, + /// Total number of storage keys in transaction access list (see EIP-2930) + access_list_storage_len: usize, + /// Cost of initcode = 2 * ceil(len(initcode) / 32) (see EIP-3860) + initcode_cost: u64, + }, +} + +impl TransactionCost { + pub fn call(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { + let zero_data_len = data.iter().filter(|v| **v == 0).count(); + let non_zero_data_len = data.len() - zero_data_len; + let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); + + TransactionCost::Call { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + } + } + + pub fn create(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { + let zero_data_len = data.iter().filter(|v| **v == 0).count(); + let non_zero_data_len = data.len() - zero_data_len; + let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); + let initcode_cost = init_code_cost(data); + + TransactionCost::Create { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + initcode_cost, + } + } + + pub fn cost(&self, config: &Config) -> u64 { + let gas_cost = match self { + TransactionCost::Call { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + } => { + #[deny(clippy::let_and_return)] + let cost = config.gas_transaction_call + + *zero_data_len as u64 * config.gas_transaction_zero_data + + *non_zero_data_len as u64 * config.gas_transaction_non_zero_data + + *access_list_address_len as u64 * config.gas_access_list_address + + *access_list_storage_len as u64 * config.gas_access_list_storage_key; + + cost + } + TransactionCost::Create { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + initcode_cost, + } => { + let mut cost = config.gas_transaction_create + + *zero_data_len as u64 * config.gas_transaction_zero_data + + *non_zero_data_len as u64 * config.gas_transaction_non_zero_data + + *access_list_address_len as u64 * config.gas_access_list_address + + *access_list_storage_len as u64 * config.gas_access_list_storage_key; + if config.max_initcode_size.is_some() { + cost += initcode_cost; + } + + cost + } + }; + + gas_cost + } +} + +/// Counts the number of addresses and storage keys in the access list +fn count_access_list(access_list: &[(H160, Vec)]) -> (usize, usize) { + let access_list_address_len = access_list.len(); + let access_list_storage_len = access_list.iter().map(|(_, keys)| keys.len()).sum(); + + (access_list_address_len, access_list_storage_len) +} + +pub fn init_code_cost(data: &[u8]) -> u64 { + // As per EIP-3860: + // > We define initcode_cost(initcode) to equal INITCODE_WORD_COST * ceil(len(initcode) / 32). + // where INITCODE_WORD_COST is 2. + 2 * ((data.len() as u64 + 31) / 32) +} diff --git a/gasometer/src/utils.rs b/src/standard/gasometer/utils.rs similarity index 100% rename from gasometer/src/utils.rs rename to src/standard/gasometer/utils.rs diff --git a/src/standard/invoker/mod.rs b/src/standard/invoker/mod.rs new file mode 100644 index 000000000..7afceca52 --- /dev/null +++ b/src/standard/invoker/mod.rs @@ -0,0 +1,526 @@ +mod resolver; +mod routines; + +pub use resolver::{EtableResolver, PrecompileSet, Resolver}; + +use super::{Config, MergeableRuntimeState, TransactGasometer}; +use crate::call_create::{CallCreateTrapData, CallTrapData, CreateScheme, CreateTrapData}; +use crate::{ + Capture, ColoredMachine, Context, ExitError, ExitException, ExitResult, ExitSucceed, + Gasometer as GasometerT, Invoker as InvokerT, InvokerControl, MergeStrategy, Opcode, + RuntimeBackend, RuntimeEnvironment, RuntimeState, TransactionContext, TransactionalBackend, + Transfer, +}; +use alloc::rc::Rc; +use core::cmp::min; +use core::convert::Infallible; +use core::marker::PhantomData; +use primitive_types::{H160, H256, U256}; +use sha3::{Digest, Keccak256}; + +pub trait IntoCallCreateTrap { + type Interrupt; + + fn into_call_create_trap(self) -> Result; +} + +impl IntoCallCreateTrap for Opcode { + type Interrupt = Infallible; + + fn into_call_create_trap(self) -> Result { + Ok(self) + } +} + +pub enum SubstackInvoke { + Call { trap: CallTrapData }, + Create { trap: CreateTrapData, address: H160 }, +} + +pub struct TransactInvoke { + pub create_address: Option, + pub gas_fee: U256, + pub gas_price: U256, + pub caller: H160, +} + +#[derive(Clone, Debug)] +pub enum TransactArgs { + Call { + caller: H160, + address: H160, + value: U256, + data: Vec, + gas_limit: U256, + gas_price: U256, + access_list: Vec<(H160, Vec)>, + }, + Create { + caller: H160, + value: U256, + init_code: Vec, + salt: Option, // Some for CREATE2 + gas_limit: U256, + gas_price: U256, + access_list: Vec<(H160, Vec)>, + }, +} + +impl TransactArgs { + pub fn gas_limit(&self) -> U256 { + match self { + Self::Call { gas_limit, .. } => *gas_limit, + Self::Create { gas_limit, .. } => *gas_limit, + } + } + + pub fn gas_price(&self) -> U256 { + match self { + Self::Call { gas_price, .. } => *gas_price, + Self::Create { gas_price, .. } => *gas_price, + } + } + + pub fn access_list(&self) -> &Vec<(H160, Vec)> { + match self { + Self::Call { access_list, .. } => access_list, + Self::Create { access_list, .. } => access_list, + } + } + + pub fn caller(&self) -> H160 { + match self { + Self::Call { caller, .. } => *caller, + Self::Create { caller, .. } => *caller, + } + } + + pub fn value(&self) -> U256 { + match self { + Self::Call { value, .. } => *value, + Self::Create { value, .. } => *value, + } + } +} + +pub struct Invoker<'config, 'resolver, S, G, H, R, Tr> { + config: &'config Config, + resolver: &'resolver R, + _marker: PhantomData<(S, G, H, Tr)>, +} + +impl<'config, 'resolver, S, G, H, R, Tr> Invoker<'config, 'resolver, S, G, H, R, Tr> { + pub fn new(config: &'config Config, resolver: &'resolver R) -> Self { + Self { + config, + resolver, + _marker: PhantomData, + } + } +} + +impl<'config, 'resolver, S, G, H, R, Tr> InvokerT + for Invoker<'config, 'resolver, S, G, H, R, Tr> +where + S: MergeableRuntimeState>, + G: GasometerT + TransactGasometer<'config, S>, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, + R: Resolver, + Tr: IntoCallCreateTrap, +{ + type Machine = ColoredMachine; + type Interrupt = Tr::Interrupt; + type TransactArgs = TransactArgs; + type TransactInvoke = TransactInvoke; + type TransactValue = (ExitSucceed, Option); + type SubstackInvoke = SubstackInvoke; + + fn new_transact( + &self, + args: TransactArgs, + handler: &mut H, + ) -> Result< + ( + TransactInvoke, + InvokerControl, (ExitResult, (S, G, Vec))>, + ), + ExitError, + > { + let caller = args.caller(); + let gas_price = args.gas_price(); + + let gas_fee = args.gas_limit().saturating_mul(gas_price); + handler.withdrawal(caller, gas_fee)?; + + handler.inc_nonce(caller)?; + + let address = match &args { + TransactArgs::Call { address, .. } => *address, + TransactArgs::Create { + caller, + salt, + init_code, + .. + } => match salt { + Some(salt) => { + let scheme = CreateScheme::Create2 { + caller: *caller, + code_hash: H256::from_slice(Keccak256::digest(&init_code).as_slice()), + salt: *salt, + }; + scheme.address(handler) + } + None => { + let scheme = CreateScheme::Legacy { caller: *caller }; + scheme.address(handler) + } + }, + }; + let value = args.value(); + + let invoke = TransactInvoke { + gas_fee, + gas_price: args.gas_price(), + caller: args.caller(), + create_address: match &args { + TransactArgs::Call { .. } => None, + TransactArgs::Create { .. } => Some(address), + }, + }; + + handler.push_substate(); + + let context = Context { + caller, + address, + apparent_value: value, + }; + let transaction_context = TransactionContext { + origin: caller, + gas_price, + }; + let transfer = Transfer { + source: caller, + target: address, + value, + }; + + let work = || -> Result<(TransactInvoke, _), ExitError> { + match args { + TransactArgs::Call { + caller, + address, + data, + gas_limit, + access_list, + .. + } => { + for (address, keys) in &access_list { + handler.mark_hot(*address, None); + for key in keys { + handler.mark_hot(*address, Some(*key)); + } + } + + let gasometer = + G::new_transact_call(gas_limit, &data, &access_list, self.config)?; + + let machine = routines::make_enter_call_machine( + self.config, + self.resolver, + address, + data, + false, // is_static + Some(transfer), + S::new_transact_call(RuntimeState { + context, + transaction_context: Rc::new(transaction_context), + retbuf: Vec::new(), + gas: U256::from(gas_limit), + }), + gasometer, + handler, + )?; + + if self.config.increase_state_access_gas { + if self.config.warm_coinbase_address { + let coinbase = handler.block_coinbase(); + handler.mark_hot(coinbase, None); + } + handler.mark_hot(caller, None); + handler.mark_hot(address, None); + } + + Ok((invoke, machine)) + } + TransactArgs::Create { + caller, + init_code, + gas_limit, + access_list, + .. + } => { + let gasometer = + G::new_transact_create(gas_limit, &init_code, &access_list, self.config)?; + + let machine = routines::make_enter_create_machine( + self.config, + self.resolver, + caller, + init_code, + false, // is_static + transfer, + S::new_transact_create(RuntimeState { + context, + transaction_context: Rc::new(transaction_context), + retbuf: Vec::new(), + gas: U256::from(gas_limit), + }), + gasometer, + handler, + )?; + + Ok((invoke, machine)) + } + } + }; + + match work() { + Ok(ret) => Ok(ret), + Err(err) => { + handler.pop_substate(MergeStrategy::Discard); + Err(err) + } + } + } + + fn finalize_transact( + &self, + invoke: &TransactInvoke, + result: ExitResult, + (_substate, mut submeter, retval): (S, G, Vec), + handler: &mut H, + ) -> Result { + let left_gas = submeter.effective_gas(); + + let work = || -> Result { + if result.is_ok() { + if let Some(address) = invoke.create_address { + let retbuf = retval; + + routines::deploy_create_code( + self.config, + address, + &retbuf, + &mut submeter, + handler, + )?; + } + } + + result.map(|s| (s, invoke.create_address)) + }; + + let result = work(); + + let refunded_gas = match result { + Ok(_) | Err(ExitError::Reverted) => left_gas, + Err(_) => U256::zero(), + }; + let refunded_fee = refunded_gas.saturating_mul(invoke.gas_price); + let coinbase_reward = invoke.gas_fee.saturating_sub(refunded_fee); + + match &result { + Ok(_) => { + handler.pop_substate(MergeStrategy::Commit); + } + Err(_) => { + handler.pop_substate(MergeStrategy::Discard); + } + } + + handler.deposit(invoke.caller, refunded_fee); + handler.deposit(handler.block_coinbase(), coinbase_reward); + + result + } + + fn enter_substack( + &self, + trap: Tr, + machine: &mut ColoredMachine, + handler: &mut H, + depth: usize, + ) -> Capture< + Result< + ( + SubstackInvoke, + InvokerControl, (ExitResult, (S, G, Vec))>, + ), + ExitError, + >, + Tr::Interrupt, + > { + fn l64(gas: U256) -> U256 { + gas - gas / U256::from(64) + } + + let opcode = match trap.into_call_create_trap() { + Ok(opcode) => opcode, + Err(interrupt) => return Capture::Trap(interrupt), + }; + + if depth >= self.config.call_stack_limit { + return Capture::Exit(Err(ExitException::CallTooDeep.into())); + } + + let trap_data = match CallCreateTrapData::new_from(opcode, &mut machine.machine) { + Ok(trap_data) => trap_data, + Err(err) => return Capture::Exit(Err(err)), + }; + + let after_gas = if self.config.call_l64_after_gas { + l64(machine.gasometer.gas()) + } else { + machine.gasometer.gas() + }; + let target_gas = trap_data.target_gas().unwrap_or(after_gas); + let gas_limit = min(after_gas, target_gas); + + let call_has_value = match &trap_data { + CallCreateTrapData::Call(call) if call.has_value() => true, + _ => false, + }; + + let is_static = if machine.is_static { + true + } else { + match &trap_data { + CallCreateTrapData::Call(CallTrapData { is_static, .. }) => *is_static, + _ => false, + } + }; + + let transaction_context = machine.machine.state.as_ref().transaction_context.clone(); + + match trap_data { + CallCreateTrapData::Call(call_trap_data) => { + let submeter = match machine.gasometer.submeter(gas_limit, call_has_value) { + Ok(submeter) => submeter, + Err(err) => return Capture::Exit(Err(err)), + }; + + let substate = machine.machine.state.substate( + RuntimeState { + context: call_trap_data.context.clone(), + transaction_context, + retbuf: Vec::new(), + gas: U256::from(gas_limit), + }, + &machine, + ); + + let target = call_trap_data.target; + + Capture::Exit(routines::enter_call_substack( + self.config, + self.resolver, + call_trap_data, + target, + is_static, + substate, + submeter, + handler, + )) + } + CallCreateTrapData::Create(create_trap_data) => { + let code = create_trap_data.code.clone(); + let submeter = match machine.gasometer.submeter(gas_limit, call_has_value) { + Ok(submeter) => submeter, + Err(err) => return Capture::Exit(Err(err)), + }; + + let caller = create_trap_data.scheme.caller(); + let address = create_trap_data.scheme.address(handler); + let substate = machine.machine.state.substate( + RuntimeState { + context: Context { + address, + caller, + apparent_value: create_trap_data.value, + }, + transaction_context, + retbuf: Vec::new(), + gas: U256::from(gas_limit), + }, + &machine, + ); + + Capture::Exit(routines::enter_create_substack( + self.config, + self.resolver, + code, + create_trap_data, + is_static, + substate, + submeter, + handler, + )) + } + } + } + + fn exit_substack( + &self, + result: ExitResult, + (substate, submeter, retval): (S, G, Vec), + trap_data: SubstackInvoke, + parent: &mut ColoredMachine, + handler: &mut H, + ) -> Result<(), ExitError> { + let strategy = match &result { + Ok(_) => MergeStrategy::Commit, + Err(ExitError::Reverted) => MergeStrategy::Revert, + Err(_) => MergeStrategy::Discard, + }; + + match trap_data { + SubstackInvoke::Create { address, trap } => { + let retbuf = retval; + parent.machine.state.merge(substate, strategy); + + let mut child_gasometer = submeter; + let result = result.and_then(|_| { + routines::deploy_create_code( + self.config, + address, + &retbuf, + &mut child_gasometer, + handler, + )?; + + Ok(address) + }); + + handler.pop_substate(strategy); + TransactGasometer::::merge(&mut parent.gasometer, child_gasometer, strategy); + + trap.feedback(result, retbuf, &mut parent.machine)?; + + Ok(()) + } + SubstackInvoke::Call { trap } => { + parent.machine.state.merge(substate, strategy); + + let retbuf = retval; + + handler.pop_substate(strategy); + TransactGasometer::::merge(&mut parent.gasometer, submeter, strategy); + + trap.feedback(result, retbuf, &mut parent.machine)?; + + Ok(()) + } + } + } +} diff --git a/src/standard/invoker/resolver.rs b/src/standard/invoker/resolver.rs new file mode 100644 index 000000000..88c4aa24c --- /dev/null +++ b/src/standard/invoker/resolver.rs @@ -0,0 +1,177 @@ +use super::routines; +use crate::standard::{Config, TransactGasometer}; +use crate::{ + Color, ColoredMachine, Control, Etable, ExitError, ExitResult, Gasometer, InvokerControl, + Machine, Opcode, RuntimeBackend, RuntimeState, +}; +use alloc::rc::Rc; +use primitive_types::H160; + +pub trait Resolver { + type Color: Color; + + fn resolve_call( + &self, + code_address: H160, + input: Vec, + is_static: bool, + state: S, + gasometer: G, + handler: &mut H, + ) -> Result< + InvokerControl, (ExitResult, (S, G, Vec))>, + ExitError, + >; + + fn resolve_create( + &self, + init_code: Vec, + is_static: bool, + state: S, + gasometer: G, + handler: &mut H, + ) -> Result< + InvokerControl, (ExitResult, (S, G, Vec))>, + ExitError, + >; +} + +pub trait PrecompileSet { + fn execute( + &self, + code_address: H160, + input: &[u8], + is_static: bool, + state: &mut S, + gasometer: &mut G, + handler: &mut H, + ) -> Option<(ExitResult, Vec)>; +} + +impl PrecompileSet for () { + fn execute( + &self, + _code_address: H160, + _input: &[u8], + _is_static: bool, + _state: &mut S, + _gasometer: &mut G, + _handler: &mut H, + ) -> Option<(ExitResult, Vec)> { + None + } +} + +pub struct EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> { + config: &'config Config, + etable: &'etable Etable, + precompiles: &'precompile Pre, +} + +impl<'config, 'precompile, 'etable, S, H, Pre, Tr, F> + EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> +{ + pub fn new( + config: &'config Config, + precompiles: &'precompile Pre, + etable: &'etable Etable, + ) -> Self { + Self { + config, + precompiles, + etable, + } + } +} + +impl<'config, 'precompile, 'etable, S, G, H, Pre, Tr, F> Resolver + for EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> +where + S: AsRef + AsMut, + G: Gasometer + TransactGasometer<'config, S>, + F: Fn(&mut Machine, &mut H, Opcode, usize) -> Control, + H: RuntimeBackend, + Pre: PrecompileSet, +{ + type Color = &'etable Etable; + + fn resolve_call( + &self, + code_address: H160, + input: Vec, + is_static: bool, + mut state: S, + mut gasometer: G, + handler: &mut H, + ) -> Result< + InvokerControl< + ColoredMachine>, + (ExitResult, (S, G, Vec)), + >, + ExitError, + > { + if let Some((r, retval)) = self.precompiles.execute( + code_address, + &input, + is_static, + &mut state, + &mut gasometer, + handler, + ) { + return Ok(InvokerControl::DirectExit((r, (state, gasometer, retval)))); + } + + let code = handler.code(code_address); + + let machine = Machine::::new( + Rc::new(code), + Rc::new(input), + self.config.stack_limit, + self.config.memory_limit, + state, + ); + + let mut ret = InvokerControl::Enter(ColoredMachine { + machine, + gasometer, + is_static, + color: self.etable, + }); + routines::maybe_analyse_code(&mut ret); + + Ok(ret) + } + + fn resolve_create( + &self, + init_code: Vec, + is_static: bool, + state: S, + gasometer: G, + _handler: &mut H, + ) -> Result< + InvokerControl< + ColoredMachine>, + (ExitResult, (S, G, Vec)), + >, + ExitError, + > { + let machine = Machine::new( + Rc::new(init_code), + Rc::new(Vec::new()), + self.config.stack_limit, + self.config.memory_limit, + state, + ); + + let mut ret = InvokerControl::Enter(ColoredMachine { + machine, + gasometer, + is_static, + color: self.etable, + }); + routines::maybe_analyse_code(&mut ret); + + Ok(ret) + } +} diff --git a/src/standard/invoker/routines.rs b/src/standard/invoker/routines.rs new file mode 100644 index 000000000..432c1beeb --- /dev/null +++ b/src/standard/invoker/routines.rs @@ -0,0 +1,229 @@ +use super::{CallTrapData, CreateTrapData, Resolver, SubstackInvoke, TransactGasometer}; +use crate::standard::Config; +use crate::{ + ColoredMachine, ExitError, ExitException, ExitResult, Gasometer as GasometerT, InvokerControl, + MergeStrategy, Opcode, RuntimeBackend, RuntimeEnvironment, RuntimeState, StaticGasometer, + TransactionalBackend, Transfer, +}; +use primitive_types::{H160, U256}; + +pub fn maybe_analyse_code<'config, S: AsRef, G: TransactGasometer<'config, S>, C>( + result: &mut InvokerControl, (ExitResult, (S, G, Vec))>, +) { + if let InvokerControl::Enter(machine) = result { + machine.gasometer.analyse_code(&machine.machine.code()) + } +} + +pub fn make_enter_call_machine<'config, 'resolver, S, G, H, R, Tr>( + _config: &'config Config, + resolver: &'resolver R, + code_address: H160, + input: Vec, + is_static: bool, + transfer: Option, + state: S, + gasometer: G, + handler: &mut H, +) -> Result, (ExitResult, (S, G, Vec))>, ExitError> +where + S: AsRef, + G: GasometerT, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, + R: Resolver, +{ + handler.mark_hot(state.as_ref().context.address, None); + + if let Some(transfer) = transfer { + handler.transfer(transfer)?; + } + + resolver.resolve_call(code_address, input, is_static, state, gasometer, handler) +} + +pub fn make_enter_create_machine<'config, 'resolver, S, G, H, R, Tr>( + config: &'config Config, + resolver: &'resolver R, + caller: H160, + init_code: Vec, + is_static: bool, + transfer: Transfer, + state: S, + gasometer: G, + handler: &mut H, +) -> Result, (ExitResult, (S, G, Vec))>, ExitError> +where + S: AsRef, + G: GasometerT, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, + R: Resolver, +{ + if let Some(limit) = config.max_initcode_size { + if init_code.len() > limit { + return Err(ExitException::CreateContractLimit.into()); + } + } + + handler.mark_hot(caller, None); + handler.mark_hot(state.as_ref().context.address, None); + + handler.transfer(transfer)?; + + if handler.code_size(state.as_ref().context.address) != U256::zero() + || handler.nonce(state.as_ref().context.address) > U256::zero() + { + return Err(ExitException::CreateCollision.into()); + } + handler.inc_nonce(caller)?; + if config.create_increase_nonce { + handler.inc_nonce(state.as_ref().context.address)?; + } + + handler.reset_storage(state.as_ref().context.address); + + resolver.resolve_create(init_code, is_static, state, gasometer, handler) +} + +pub fn enter_call_substack<'config, 'resolver, S, G, H, R, Tr>( + config: &'config Config, + resolver: &'resolver R, + trap_data: CallTrapData, + code_address: H160, + is_static: bool, + state: S, + gasometer: G, + handler: &mut H, +) -> Result< + ( + SubstackInvoke, + InvokerControl, (ExitResult, (S, G, Vec))>, + ), + ExitError, +> +where + S: AsRef, + G: GasometerT + TransactGasometer<'config, S>, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, + R: Resolver, +{ + handler.push_substate(); + + let work = || -> Result<(SubstackInvoke, _), ExitError> { + let machine = make_enter_call_machine( + config, + resolver, + code_address, + trap_data.input.clone(), + is_static, + trap_data.transfer.clone(), + state, + gasometer, + handler, + )?; + + Ok((SubstackInvoke::Call { trap: trap_data }, machine)) + }; + + match work() { + Ok(machine) => Ok(machine), + Err(err) => { + handler.pop_substate(MergeStrategy::Discard); + Err(err) + } + } +} + +pub fn enter_create_substack<'config, 'resolver, S, G, H, R, Tr>( + config: &'config Config, + resolver: &'resolver R, + code: Vec, + trap_data: CreateTrapData, + is_static: bool, + state: S, + gasometer: G, + handler: &mut H, +) -> Result< + ( + SubstackInvoke, + InvokerControl, (ExitResult, (S, G, Vec))>, + ), + ExitError, +> +where + S: AsRef, + G: GasometerT + TransactGasometer<'config, S>, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, + R: Resolver, +{ + handler.push_substate(); + + let work = || -> Result<(SubstackInvoke, InvokerControl, (ExitResult, (S, G, Vec))>), ExitError> { + let CreateTrapData { + scheme, + value, + code: _, + } = trap_data.clone(); + + let caller = scheme.caller(); + let address = scheme.address(handler); + + let transfer = Transfer { + source: caller, + target: address, + value, + }; + + let machine = make_enter_create_machine( + config, resolver, caller, code, is_static, transfer, state, gasometer, handler, + )?; + + Ok(( + SubstackInvoke::Create { + address, + trap: trap_data, + }, + machine, + )) + }; + + match work() { + Ok(machine) => Ok(machine), + Err(err) => { + handler.pop_substate(MergeStrategy::Discard); + Err(err) + } + } +} + +fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> { + if config.disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first() { + return Err(ExitException::InvalidOpcode(Opcode::EOFMAGIC).into()); + } + Ok(()) +} + +pub fn deploy_create_code<'config, S, G, H>( + config: &'config Config, + address: H160, + retbuf: &Vec, + gasometer: &mut G, + handler: &mut H, +) -> Result<(), ExitError> +where + G: GasometerT, + H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend, +{ + check_first_byte(config, &retbuf[..])?; + + if let Some(limit) = config.create_contract_limit { + if retbuf.len() > limit { + return Err(ExitException::CreateContractLimit.into()); + } + } + + StaticGasometer::record_codedeposit(gasometer, retbuf.len())?; + + handler.set_code(address, retbuf.clone())?; + + Ok(()) +} diff --git a/src/standard/mod.rs b/src/standard/mod.rs new file mode 100644 index 000000000..54496fede --- /dev/null +++ b/src/standard/mod.rs @@ -0,0 +1,35 @@ +mod config; +mod gasometer; +mod invoker; + +pub use self::config::Config; +pub use self::gasometer::{Gasometer, TransactGasometer}; +pub use self::invoker::{EtableResolver, Invoker, PrecompileSet, Resolver, TransactArgs}; + +pub type Machine = crate::Machine; +pub type Efn = crate::Efn; +pub type Etable> = crate::Etable; +pub type ColoredMachine<'etable, G, H, F = Efn> = + crate::ColoredMachine>; + +pub trait MergeableRuntimeState: + AsRef + AsMut +{ + fn substate(&self, runtime: crate::RuntimeState, parent: &M) -> Self; + fn merge(&mut self, substate: Self, strategy: crate::MergeStrategy); + fn new_transact_call(runtime: crate::RuntimeState) -> Self; + fn new_transact_create(runtime: crate::RuntimeState) -> Self; +} + +impl MergeableRuntimeState for crate::RuntimeState { + fn substate(&self, runtime: crate::RuntimeState, _parent: &M) -> Self { + runtime + } + fn merge(&mut self, _substate: Self, _strategy: crate::MergeStrategy) {} + fn new_transact_call(runtime: crate::RuntimeState) -> Self { + runtime + } + fn new_transact_create(runtime: crate::RuntimeState) -> Self { + runtime + } +} diff --git a/src/tracing.rs b/src/tracing.rs deleted file mode 100644 index d866097b8..000000000 --- a/src/tracing.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Allows to listen to runtime events. - -use crate::Context; -use evm_runtime::{CreateScheme, ExitReason, Transfer}; -use primitive_types::{H160, H256, U256}; - -environmental::environmental!(listener: dyn EventListener + 'static); - -pub trait EventListener { - fn event(&mut self, event: Event<'_>); -} - -#[derive(Debug, Copy, Clone)] -pub enum Event<'a> { - Call { - code_address: H160, - transfer: &'a Option, - input: &'a [u8], - target_gas: Option, - is_static: bool, - context: &'a Context, - }, - Create { - caller: H160, - address: H160, - scheme: CreateScheme, - value: U256, - init_code: &'a [u8], - target_gas: Option, - }, - Suicide { - address: H160, - target: H160, - balance: U256, - }, - Exit { - reason: &'a ExitReason, - return_value: &'a [u8], - }, - TransactCall { - caller: H160, - address: H160, - value: U256, - data: &'a [u8], - gas_limit: u64, - }, - TransactCreate { - caller: H160, - value: U256, - init_code: &'a [u8], - gas_limit: u64, - address: H160, - }, - TransactCreate2 { - caller: H160, - value: U256, - init_code: &'a [u8], - salt: H256, - gas_limit: u64, - address: H160, - }, - PrecompileSubcall { - code_address: H160, - transfer: &'a Option, - input: &'a [u8], - target_gas: Option, - is_static: bool, - context: &'a Context, - }, -} - -// Expose `listener::with` to the crate only. -pub(crate) fn with(f: F) { - listener::with(f); -} - -/// Run closure with provided listener. -pub fn using R>(new: &mut (dyn EventListener + 'static), f: F) -> R { - listener::using(new, f) -} diff --git a/tracer/Cargo.toml b/tracer/Cargo.toml new file mode 100644 index 000000000..7550e4350 --- /dev/null +++ b/tracer/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "evm-tracer" +version = "0.1.0" +edition = "2021" + +[dependencies] +evm = { path = ".." } diff --git a/tracer/src/lib.rs b/tracer/src/lib.rs new file mode 100644 index 000000000..7b0b01f81 --- /dev/null +++ b/tracer/src/lib.rs @@ -0,0 +1,7 @@ +mod standard; + +use evm::{Machine, Opcode}; + +pub trait EvalTracer { + fn on_eval(&mut self, machine: &Machine, handle: &H, opcode: Opcode, position: usize); +} diff --git a/tracer/src/standard.rs b/tracer/src/standard.rs new file mode 100644 index 000000000..ff9687ebc --- /dev/null +++ b/tracer/src/standard.rs @@ -0,0 +1,12 @@ +use evm::standard::Machine; +use evm::{Opcode, RuntimeState}; + +pub trait EvalTracer { + fn on_eval(&mut self, machine: &Machine, handle: &H, opcode: Opcode, position: usize); +} + +impl> crate::EvalTracer for T { + fn on_eval(&mut self, machine: &Machine, handle: &H, opcode: Opcode, position: usize) { + EvalTracer::::on_eval(self, machine, handle, opcode, position) + } +}