From 8e95a975b9212a90a4002c689a38898a1c06edb4 Mon Sep 17 00:00:00 2001 From: Artem Vorotnikov Date: Thu, 15 Jul 2021 03:06:32 +0300 Subject: [PATCH] Initial public release --- .github/workflows/main.yml | 55 + .gitignore | 2 + Cargo.toml | 35 + LICENSE | 201 ++++ README.md | 44 + src/common.rs | 231 ++++ src/host.rs | 183 ++++ src/instructions/arithmetic.rs | 175 +++ src/instructions/bitwise.rs | 80 ++ src/instructions/boolean.rs | 80 ++ src/instructions/call.rs | 231 ++++ src/instructions/control.rs | 56 + src/instructions/external.rs | 369 +++++++ src/instructions/instruction_table.rs | 42 + src/instructions/memory.rs | 363 +++++++ src/instructions/mod.rs | 25 + src/instructions/properties.rs | 372 +++++++ src/instructions/stack_manip.rs | 20 + src/interpreter.rs | 483 +++++++++ src/lib.rs | 59 + src/opcode.rs | 335 ++++++ src/state.rs | 101 ++ src/tracing/mod.rs | 119 +++ src/util/bytecode.rs | 236 ++++ src/util/mocked_host.rs | 346 ++++++ src/util/mod.rs | 6 + src/util/tester.rs | 279 +++++ tests/basefee.rs | 35 + tests/call.rs | 1248 ++++++++++++++++++++++ tests/eip2929.rs | 419 ++++++++ tests/execute.rs | 1423 +++++++++++++++++++++++++ tests/state.rs | 1262 ++++++++++++++++++++++ 32 files changed, 8915 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/common.rs create mode 100644 src/host.rs create mode 100644 src/instructions/arithmetic.rs create mode 100644 src/instructions/bitwise.rs create mode 100644 src/instructions/boolean.rs create mode 100644 src/instructions/call.rs create mode 100644 src/instructions/control.rs create mode 100644 src/instructions/external.rs create mode 100644 src/instructions/instruction_table.rs create mode 100644 src/instructions/memory.rs create mode 100644 src/instructions/mod.rs create mode 100644 src/instructions/properties.rs create mode 100644 src/instructions/stack_manip.rs create mode 100644 src/interpreter.rs create mode 100644 src/lib.rs create mode 100644 src/opcode.rs create mode 100644 src/state.rs create mode 100644 src/tracing/mod.rs create mode 100644 src/util/bytecode.rs create mode 100644 src/util/mocked_host.rs create mode 100644 src/util/mod.rs create mode 100644 src/util/tester.rs create mode 100644 tests/basefee.rs create mode 100644 tests/call.rs create mode 100644 tests/eip2929.rs create mode 100644 tests/execute.rs create mode 100644 tests/state.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..a6c36b0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,55 @@ +on: + pull_request: + push: + branches: + - master + +name: CI + +jobs: + ci: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rustfmt, clippy + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + + - uses: actions-rs/cargo@v1 + with: + command: hack + args: check --all --ignore-private --each-feature --no-dev-deps + + - uses: actions-rs/cargo@v1 + with: + command: check + args: --all --all-targets --all-features + + - uses: actions-rs/cargo@v1 + with: + command: test + + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2f49947 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "evmodin" +version = "0.1.0" +edition = "2018" +license = "Apache-2.0" + +[dependencies] +anyhow = { version = "1", default-features = false } +arrayvec = { version = "0.7", default-features = false, features = ["serde"] } +async-trait = { version = "0.1", default-features = false } +bytes = { version = "1", default-features = false, features = ["serde"] } +educe = { version = "0.4", default-features = false, optional = true } +i256 = { git = "https://github.com/vorot93/rust-i256" } +ethereum-types = { version = "0.12", default-features = false } +hex = "0.4" +hex-literal = { version = "0.3", optional = true } +num-traits = { version = "0.2", default-features = false } +once_cell = "1" +parking_lot = { version = "0.11", optional = true } +primitive-types = { version = "0.10", default-features = false, features = [ + "serde", +] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sha3 = "0.9" +strum_macros = "0.21" + +[dev-dependencies] +evmodin-test = { path = ".", package = "evmodin", features = ["util"] } +hex-literal = "0.3" +rand = { version = "0.8", features = ["std"] } +tokio = { version = "1", features = ["full"] } + +[features] +util = ["educe/Debug", "hex-literal", "parking_lot"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a39230 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# evmodin + +Fast EVM implementation with full async support. Port of [evmone](https://github.com/ethereum/evmone) to Rust. + +## Usage +```rust +use evmodin::{*, host::*, util::*, tracing::*}; +use ethereum_types::*; +use hex_literal::hex; + +let my_code = Bytecode::new() + .mstore8_value(0, b'h') + .mstore8_value(1, b'e') + .mstore8_value(2, b'l') + .mstore8_value(3, b'l') + .mstore8_value(4, b'o') + .ret(0, 5) + .build(); +let message = Message { + kind: CallKind::Call, + is_static: true, + depth: 0, + gas: 200, + destination: Address::zero(), + sender: Address::zero(), + input_data: vec![].into(), + value: U256::zero(), +}; + +assert_eq!( + AnalyzedCode::analyze(my_code) + .execute(&mut DummyHost, &mut NoopTracer, message, Revision::London) + .await + .unwrap(), + Output { + status_code: StatusCode::Success, + gas_left: 146, + output_data: b"hello".to_vec().into(), + create_address: None, + } +) +``` + +License: Apache-2.0 diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..4683063 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,231 @@ +use bytes::Bytes; +use ethereum_types::{Address, H256, U256}; +use serde::Serialize; +use strum_macros::Display; + +/// EVM revision. +#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, Serialize)] +pub enum Revision { + /// The Frontier revision. + /// The one Ethereum launched with. + Frontier = 0, + + /// [The Homestead revision.](https://eips.ethereum.org/EIPS/eip-606) + Homestead = 1, + + /// [The Tangerine Whistle revision.](https://eips.ethereum.org/EIPS/eip-608) + Tangerine = 2, + + /// [The Spurious Dragon revision.](https://eips.ethereum.org/EIPS/eip-607) + Spurious = 3, + + /// [The Byzantium revision.](https://eips.ethereum.org/EIPS/eip-609) + Byzantium = 4, + + /// [The Constantinople revision.](https://eips.ethereum.org/EIPS/eip-1013) + Constantinople = 5, + + /// [The Petersburg revision.](https://eips.ethereum.org/EIPS/eip-1716) + Petersburg = 6, + + /// [The Istanbul revision.](https://eips.ethereum.org/EIPS/eip-1679) + Istanbul = 7, + + /// [The Berlin revision.](https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md) + Berlin = 8, + + /// [The London revision.](https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md) + London = 9, +} + +impl Revision { + pub fn iter() -> impl Iterator { + (&[ + Self::Frontier, + Self::Homestead, + Self::Tangerine, + Self::Spurious, + Self::Byzantium, + Self::Constantinople, + Self::Petersburg, + Self::Istanbul, + Self::Berlin, + Self::London, + ]) + .iter() + .copied() + } + + pub const fn len() -> usize { + Self::London as usize + 1 + } + + pub const fn latest() -> Self { + Self::London + } +} + +/// Message status code. +#[derive(Clone, Copy, Debug, Display, PartialEq)] +pub enum StatusCode { + /// Execution finished with success. + #[strum(serialize = "success")] + Success = 0, + + /// Generic execution failure. + #[strum(serialize = "failure")] + Failure = 1, + + /// Execution terminated with REVERT opcode. + /// + /// In this case the amount of gas left MAY be non-zero and additional output + /// data MAY be provided in ::evmc_result. + #[strum(serialize = "revert")] + Revert = 2, + + /// The execution has run out of gas. + #[strum(serialize = "out of gas")] + OutOfGas = 3, + + /// The designated INVALID instruction has been hit during execution. + /// + /// [EIP-141](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-141.md) + /// defines the instruction 0xfe as INVALID instruction to indicate execution + /// abortion coming from high-level languages. This status code is reported + /// in case this INVALID instruction has been encountered. + #[strum(serialize = "invalid instruction")] + InvalidInstruction = 4, + + /// An undefined instruction has been encountered. + #[strum(serialize = "undefined instruction")] + UndefinedInstruction = 5, + + /// The execution has attempted to put more items on the EVM stack + /// than the specified limit. + #[strum(serialize = "stack overflow")] + StackOverflow = 6, + + /// Execution of an opcode has required more items on the EVM stack. + #[strum(serialize = "stack underflow")] + StackUnderflow = 7, + + /// Execution has violated the jump destination restrictions. + #[strum(serialize = "bad jump destination")] + BadJumpDestination = 8, + + /// Tried to read outside memory bounds. + /// + /// An example is RETURNDATACOPY reading past the available buffer. + #[strum(serialize = "invalid memory access")] + InvalidMemoryAccess = 9, + + /// Call depth has exceeded the limit (if any) + #[strum(serialize = "call depth exceeded")] + CallDepthExceeded = 10, + + /// Tried to execute an operation which is restricted in static mode. + #[strum(serialize = "static mode violation")] + StaticModeViolation = 11, + + /// A call to a precompiled or system contract has ended with a failure. + /// + /// An example: elliptic curve functions handed invalid EC points. + #[strum(serialize = "precompile failure")] + PrecompileFailure = 12, + + /// Contract validation has failed (e.g. due to EVM 1.5 jump validity, + /// Casper's purity checker or ewasm contract rules). + #[strum(serialize = "contract validation failure")] + ContractValidationFailure = 13, + + /// An argument to a state accessing method has a value outside of the + /// accepted range of values. + #[strum(serialize = "argument out of range")] + ArgumentOutOfRange = 14, + + /// The caller does not have enough funds for value transfer. + #[strum(serialize = "insufficient balance")] + InsufficientBalance = 17, + + /// EVM implementation generic internal error. + #[strum(serialize = "internal error")] + InternalError = -1, + + /// The execution of the given code and/or message has been rejected + /// by the EVM implementation. + /// + /// This error SHOULD be used to signal that the EVM is not able to or + /// willing to execute the given code type or message. + /// If an EVM returns the ::EVMC_REJECTED status code, + /// the Client MAY try to execute it in other EVM implementation. + /// For example, the Client tries running a code in the EVM 1.5. If the + /// code is not supported there, the execution falls back to the EVM 1.0. + #[strum(serialize = "rejected")] + Rejected = -2, + + /// The VM failed to allocate the amount of memory needed for execution. + #[strum(serialize = "out of memory")] + OutOfMemory = -3, +} + +/// The kind of call-like instruction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CallKind { + Call, + DelegateCall, + CallCode, + Create, + Create2 { salt: H256 }, +} + +/// The message describing an EVM call, +/// including a zero-depth calls from a transaction origin. +#[derive(Clone, Debug, PartialEq)] +pub struct Message { + /// The kind of the call. For zero-depth calls `CallKind::Call` SHOULD be used. + pub kind: CallKind, + + /// Static call mode. + pub is_static: bool, + + /// The call depth. + pub depth: i32, + + /// The amount of gas for message execution. + pub gas: i64, + + /// The destination of the message. + pub destination: Address, + + /// The sender of the message. + pub sender: Address, + + /// Message input data. + pub input_data: Bytes, + + /// The amount of Ether transferred with the message. + pub value: U256, +} + +/// Output of EVM execution. +#[derive(Clone, Debug, PartialEq)] +pub struct Output { + /// EVM exited with this status code. + pub status_code: StatusCode, + /// How much gas was left after execution + pub gas_left: i64, + /// Output data returned. + pub output_data: Bytes, + /// Contract creation address. + pub create_address: Option
, +} + +#[inline] +pub(crate) fn u256_to_address(v: U256) -> Address { + H256(v.into()).into() +} + +#[inline] +pub(crate) fn address_to_u256(v: Address) -> U256 { + U256::from_big_endian(&v.0) +} diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 0000000..6773157 --- /dev/null +++ b/src/host.rs @@ -0,0 +1,183 @@ +use crate::common::{Message, Output}; +use anyhow::bail; +use async_trait::async_trait; +use ethereum_types::{Address, H256, U256}; + +/// State access status (EIP-2929). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AccessStatus { + Cold, + Warm, +} + +impl Default for AccessStatus { + fn default() -> Self { + Self::Cold + } +} + +#[derive(Clone, Copy, Debug)] +pub enum StorageStatus { + /// The value of a storage item has been left unchanged: 0 -> 0 and X -> X. + Unchanged, + /// The value of a storage item has been modified: X -> Y. + Modified, + /// A storage item has been modified after being modified before: X -> Y -> Z. + ModifiedAgain, + /// A new storage item has been added: 0 -> X. + Added, + /// A storage item has been deleted: X -> 0. + Deleted, +} + +/// The transaction and block data for execution. +#[derive(Clone, Debug)] +pub struct TxContext { + /// The transaction gas price. + pub tx_gas_price: U256, + /// The transaction origin account. + pub tx_origin: Address, + /// The miner of the block. + pub block_coinbase: Address, + /// The block number. + pub block_number: u64, + /// The block timestamp. + pub block_timestamp: u64, + /// The block gas limit. + pub block_gas_limit: u64, + /// The block difficulty. + pub block_difficulty: U256, + /// The blockchain's ChainID. + pub chain_id: U256, + /// The block base fee per gas (EIP-1559, EIP-3198). + pub block_base_fee: U256, +} + +/// Abstraction that exposes host context to EVM. +/// +/// It is asynchronous, allowing for remote access. Errors represent network or host errors. +#[async_trait] +pub trait Host { + /// Check if an account exists. + async fn account_exists(&self, address: Address) -> anyhow::Result; + /// Get value of a storage key. + /// + /// Returns `Ok(H256::zero())` if does not exist. + async fn get_storage(&self, address: Address, key: H256) -> anyhow::Result; + /// Set value of a storage key. + async fn set_storage( + &mut self, + address: Address, + key: H256, + value: H256, + ) -> anyhow::Result; + /// Get balance of an account. + /// + /// Returns `Ok(0)` if account does not exist. + async fn get_balance(&self, address: Address) -> anyhow::Result; + /// Get code size of an account. + /// + /// Returns `Ok(0)` if account does not exist. + async fn get_code_size(&self, address: Address) -> anyhow::Result; + /// Get code hash of an account. + /// + /// Returns `Ok(0)` if account does not exist. + async fn get_code_hash(&self, address: Address) -> anyhow::Result; + /// Copy code of an account. + /// + /// Returns `Ok(0)` if offset is invalid. + async fn copy_code( + &self, + address: Address, + offset: usize, + buffer: &mut [u8], + ) -> anyhow::Result; + /// Self-destruct account. + async fn selfdestruct(&mut self, address: Address, beneficiary: Address) -> anyhow::Result<()>; + /// Call to another account. + async fn call(&mut self, msg: &Message) -> anyhow::Result; + /// Retrieve transaction context. + async fn get_tx_context(&self) -> anyhow::Result; + /// Get block hash. + /// + /// Returns `Ok(H256::zero())` if block does not exist. + async fn get_block_hash(&self, block_number: u64) -> anyhow::Result; + /// Emit a log. + async fn emit_log( + &mut self, + address: Address, + data: &[u8], + topics: &[H256], + ) -> anyhow::Result<()>; + /// Mark account as warm, return previous access status. + /// + /// Returns `Ok(AccessStatus::Cold)` if account does not exist. + async fn access_account(&mut self, address: Address) -> anyhow::Result; + /// Mark storage key as warm, return previous access status. + /// + /// Returns `Ok(AccessStatus::Cold)` if account does not exist. + async fn access_storage(&mut self, address: Address, key: H256) + -> anyhow::Result; +} + +/// Host that does not support any ops. +pub struct DummyHost; + +#[async_trait] +impl Host for DummyHost { + async fn account_exists(&self, _: Address) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_storage(&self, _: Address, _: H256) -> anyhow::Result { + bail!("unsupported") + } + + async fn set_storage(&mut self, _: Address, _: H256, _: H256) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_balance(&self, _: Address) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_code_size(&self, _: Address) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_code_hash(&self, _: Address) -> anyhow::Result { + bail!("unsupported") + } + + async fn copy_code(&self, _: Address, _: usize, _: &mut [u8]) -> anyhow::Result { + bail!("unsupported") + } + + async fn selfdestruct(&mut self, _: Address, _: Address) -> anyhow::Result<()> { + bail!("unsupported") + } + + async fn call(&mut self, _: &Message) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_tx_context(&self) -> anyhow::Result { + bail!("unsupported") + } + + async fn get_block_hash(&self, _: u64) -> anyhow::Result { + bail!("unsupported") + } + + async fn emit_log(&mut self, _: Address, _: &[u8], _: &[H256]) -> anyhow::Result<()> { + bail!("unsupported") + } + + async fn access_account(&mut self, _: Address) -> anyhow::Result { + bail!("unsupported") + } + + async fn access_storage(&mut self, _: Address, _: H256) -> anyhow::Result { + bail!("unsupported") + } +} diff --git a/src/instructions/arithmetic.rs b/src/instructions/arithmetic.rs new file mode 100644 index 0000000..cc64c1f --- /dev/null +++ b/src/instructions/arithmetic.rs @@ -0,0 +1,175 @@ +use crate::{state::*, Revision, StatusCode}; +use core::convert::TryInto; +use ethereum_types::{U256, U512}; +use i256::I256; + +#[inline] +pub(crate) fn add(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a.overflowing_add(b).0); +} + +#[inline] +pub(crate) fn mul(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a.overflowing_mul(b).0); +} + +#[inline] +pub(crate) fn sub(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a.overflowing_sub(b).0); +} + +#[inline] +pub(crate) fn div(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(if b.is_zero() { U256::zero() } else { a / b }); +} + +#[inline] +pub(crate) fn sdiv(stack: &mut Stack) { + let a = I256::from(stack.pop()); + let b = I256::from(stack.pop()); + let v = a / b; + stack.push(v.into()); +} + +#[inline] +pub(crate) fn modulo(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + let v = if b.is_zero() { U256::zero() } else { a % b }; + stack.push(v); +} + +#[inline] +pub(crate) fn smod(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + let v = if b.is_zero() { + U256::zero() + } else { + let v = I256::from(a) % I256::from(b); + v.into() + }; + + stack.push(v); +} + +#[inline] +pub(crate) fn addmod(stack: &mut Stack) { + let a = U512::from(stack.pop()); + let b = U512::from(stack.pop()); + let c = U512::from(stack.pop()); + + let v = if c.is_zero() { + U256::zero() + } else { + let v = (a + b) % c; + v.try_into().unwrap() + }; + + stack.push(v); +} + +#[inline] +pub(crate) fn mulmod(stack: &mut Stack) { + let a = U512::from(stack.pop()); + let b = U512::from(stack.pop()); + let c = U512::from(stack.pop()); + + let v = if c.is_zero() { + U256::zero() + } else { + let v = (a * b) % c; + v.try_into().unwrap() + }; + + stack.push(v); +} + +#[inline] +fn log2floor(value: U256) -> u64 { + assert!(value != U256::zero()); + let mut l: u64 = 256; + for i in 0..4 { + let i = 3 - i; + if value.0[i] == 0u64 { + l -= 64; + } else { + l -= value.0[i].leading_zeros() as u64; + if l == 0 { + return l; + } else { + return l - 1; + } + } + } + l +} + +pub(crate) fn exp(state: &mut ExecutionState) -> StatusCode { + let mut base = state.stack.pop(); + let mut power = state.stack.pop(); + + if !power.is_zero() { + let additional_gas = if state.evm_revision >= Revision::Spurious { + 50 + } else { + 10 + } * (log2floor(power) / 8 + 1); + + state.gas_left -= additional_gas as i64; + + if state.gas_left < 0 { + return StatusCode::OutOfGas; + } + } + + let mut v = U256::one(); + + while !power.is_zero() { + if !(power & U256::one()).is_zero() { + v = v.overflowing_mul(base).0; + } + power >>= 1; + base = base.overflowing_mul(base).0; + } + + state.stack.push(v); + + StatusCode::Success +} + +pub(crate) fn signextend(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + let v = if a > U256::from(32) { + b + } else { + let mut v = U256::zero(); + let len: usize = a.as_usize(); + let t: usize = 8 * (len + 1) - 1; + let t_bit_mask = U256::one() << t; + let t_value = (b & t_bit_mask) >> t; + for i in 0..256 { + let bit_mask = U256::one() << i; + let i_value = (b & bit_mask) >> i; + if i <= t { + v = v.overflowing_add(i_value << i).0; + } else { + v = v.overflowing_add(t_value << i).0; + } + } + v + }; + + stack.push(v); +} diff --git a/src/instructions/bitwise.rs b/src/instructions/bitwise.rs new file mode 100644 index 0000000..b669207 --- /dev/null +++ b/src/instructions/bitwise.rs @@ -0,0 +1,80 @@ +use crate::state::Stack; +use ethereum_types::U256; +use i256::{Sign, I256}; + +#[inline] +pub(crate) fn byte(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + let mut ret = U256::zero(); + + for i in 0..256 { + if i < 8 && a < 32.into() { + let o: usize = a.as_usize(); + let t = 255 - (7 - i + 8 * o); + let bit_mask = U256::one() << t; + let value = (b & bit_mask) >> t; + ret = ret.overflowing_add(value << i).0; + } + } + + stack.push(ret) +} + +#[inline] +pub(crate) fn shl(stack: &mut Stack) { + let shift = stack.pop(); + let value = stack.pop(); + + let ret = if value.is_zero() || shift >= U256::from(256) { + U256::zero() + } else { + value << shift.as_usize() + }; + + stack.push(ret) +} + +#[inline] +pub(crate) fn shr(stack: &mut Stack) { + let shift = stack.pop(); + let value = stack.pop(); + + let ret = if value.is_zero() || shift >= U256::from(256) { + U256::zero() + } else { + value >> shift.as_usize() + }; + + stack.push(ret) +} + +#[inline] +pub(crate) fn sar(stack: &mut Stack) { + let shift = stack.pop(); + let value = I256::from(stack.pop()); + + let ret = if value == I256::zero() || shift >= U256::from(256) { + match value.0 { + // value is 0 or >=1, pushing 0 + Sign::Plus | Sign::NoSign => U256::zero(), + // value is <0, pushing -1 + Sign::Minus => I256(Sign::Minus, U256::one()).into(), + } + } else { + let shift = shift.as_usize(); + + match value.0 { + Sign::Plus | Sign::NoSign => value.1 >> shift, + Sign::Minus => { + let shifted = ((value.1.overflowing_sub(U256::one()).0) >> shift) + .overflowing_add(U256::one()) + .0; + I256(Sign::Minus, shifted).into() + } + } + }; + + stack.push(ret) +} diff --git a/src/instructions/boolean.rs b/src/instructions/boolean.rs new file mode 100644 index 0000000..7f9c206 --- /dev/null +++ b/src/instructions/boolean.rs @@ -0,0 +1,80 @@ +use crate::state::*; +use ethereum_types::U256; +use i256::I256; + +#[inline] +pub(crate) fn lt(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + stack.push(if a.lt(&b) { U256::one() } else { U256::zero() }) +} + +#[inline] +pub(crate) fn gt(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + stack.push(if a.gt(&b) { U256::one() } else { U256::zero() }) +} + +#[inline] +pub(crate) fn slt(stack: &mut Stack) { + let a: I256 = stack.pop().into(); + let b: I256 = stack.pop().into(); + + stack.push(if a.lt(&b) { U256::one() } else { U256::zero() }) +} + +#[inline] +pub(crate) fn sgt(stack: &mut Stack) { + let a: I256 = stack.pop().into(); + let b: I256 = stack.pop().into(); + + stack.push(if a.gt(&b) { U256::one() } else { U256::zero() }) +} + +#[inline] +pub(crate) fn eq(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + + stack.push(if a.eq(&b) { U256::one() } else { U256::zero() }) +} + +#[inline] +pub(crate) fn iszero(stack: &mut Stack) { + let a = stack.pop(); + stack.push(if a.is_zero() { + U256::one() + } else { + U256::zero() + }) +} + +#[inline] +pub(crate) fn and(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a & b); +} + +#[inline] +pub(crate) fn or(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a | b); +} + +#[inline] +pub(crate) fn xor(stack: &mut Stack) { + let a = stack.pop(); + let b = stack.pop(); + stack.push(a ^ b); +} + +#[inline] +pub(crate) fn not(stack: &mut Stack) { + let a = stack.pop(); + stack.push(!a); +} diff --git a/src/instructions/call.rs b/src/instructions/call.rs new file mode 100644 index 0000000..192f0ff --- /dev/null +++ b/src/instructions/call.rs @@ -0,0 +1,231 @@ +use std::cmp::min; + +use bytes::Bytes; +use ethereum_types::{Address, H256}; + +use super::*; +use crate::{ + common::{address_to_u256, u256_to_address}, + host::AccessStatus, + instructions::{memory::MemoryRegion, properties::*}, + CallKind, Host, Message, +}; + +pub(crate) async fn call( + host: &mut H, + state: &mut ExecutionState, + kind: CallKind, + is_static: bool, +) -> anyhow::Result { + let gas = state.stack.pop(); + let dst = u256_to_address(state.stack.pop()); + let value = if is_static || matches!(kind, CallKind::DelegateCall) { + U256::zero() + } else { + state.stack.pop() + }; + let has_value = !value.is_zero(); + let input_offset = state.stack.pop(); + let input_size = state.stack.pop(); + let output_offset = state.stack.pop(); + let output_size = state.stack.pop(); + + state.stack.push(U256::zero()); // Assume failure. + + if state.evm_revision >= Revision::Berlin + && host.access_account(dst).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + let input_region = if let Ok(r) = memory::verify_memory_region(state, input_offset, input_size) + { + r + } else { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + }; + + let output_region = + if let Ok(r) = memory::verify_memory_region(state, output_offset, output_size) { + r + } else { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + }; + + let mut msg = Message { + kind, + is_static: is_static || state.message.is_static, + depth: state.message.depth + 1, + destination: dst, + sender: if matches!(kind, CallKind::DelegateCall) { + state.message.sender + } else { + state.message.destination + }, + gas: i64::MAX, + value: if matches!(kind, CallKind::DelegateCall) { + state.message.value + } else { + value + }, + input_data: input_region + .map(|MemoryRegion { offset, size }| { + state.memory[offset..offset + size.get()].to_vec().into() + }) + .unwrap_or_default(), + }; + + let mut cost = if has_value { 9000 } else { 0 }; + + if matches!(kind, CallKind::Call) { + if has_value && state.message.is_static { + return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); + } + + if (has_value || state.evm_revision < Revision::Spurious) + && !host.account_exists(dst).await? + { + cost += 25000; + } + } + state.gas_left -= cost; + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + + if gas < msg.gas.into() { + msg.gas = gas.as_usize() as i64; + } + + if state.evm_revision >= Revision::Tangerine { + // TODO: Always true for STATICCALL. + msg.gas = min(msg.gas, state.gas_left - state.gas_left / 64); + } else if msg.gas > state.gas_left { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + + if has_value { + msg.gas += 2300; // Add stipend. + state.gas_left += 2300; + } + + state.return_data.clear(); + + if state.message.depth >= 1024 { + return Ok(InstructionResolution::Continue); + } + + if has_value && host.get_balance(state.message.destination).await? < value { + return Ok(InstructionResolution::Continue); + } + + let result = host.call(&msg).await?; + state.return_data = result.output_data.clone(); + *state.stack.get_mut(0) = if matches!(result.status_code, StatusCode::Success) { + U256::one() + } else { + U256::zero() + }; + + if let Some(MemoryRegion { offset, size }) = output_region { + let copy_size = min(size.get(), result.output_data.len()); + if copy_size > 0 { + state.memory[offset..offset + copy_size] + .copy_from_slice(&result.output_data[..copy_size]); + } + } + + let gas_used = msg.gas - result.gas_left; + state.gas_left -= gas_used; + Ok(InstructionResolution::Continue) +} + +pub(crate) async fn create( + host: &mut H, + state: &mut ExecutionState, + create2: bool, +) -> anyhow::Result { + if state.message.is_static { + return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); + } + + let endowment = state.stack.pop(); + let init_code_offset = state.stack.pop(); + let init_code_size = state.stack.pop(); + + let region = + if let Ok(r) = memory::verify_memory_region(state, init_code_offset, init_code_size) { + r + } else { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + }; + + let call_kind = if create2 { + let salt = state.stack.pop(); + + if let Some(region) = ®ion { + let salt_cost = memory::num_words(region.size.get()) * 6; + state.gas_left -= salt_cost; + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + CallKind::Create2 { + salt: H256(salt.into()), + } + } else { + CallKind::Create + }; + + state.stack.push(U256::zero()); + state.return_data.clear(); + + if state.message.depth >= 1024 { + return Ok(InstructionResolution::Continue); + } + + if !endowment.is_zero() && host.get_balance(state.message.destination).await? < endowment { + return Ok(InstructionResolution::Continue); + } + + let msg = Message { + gas: if state.evm_revision >= Revision::Tangerine { + state.gas_left - state.gas_left / 64 + } else { + state.gas_left + }, + + is_static: false, + destination: Address::zero(), + + kind: call_kind, + input_data: if !init_code_size.is_zero() { + state.memory[init_code_offset.as_usize() + ..init_code_offset.as_usize() + init_code_size.as_usize()] + .to_vec() + .into() + } else { + Bytes::new() + }, + sender: state.message.destination, + depth: state.message.depth + 1, + value: endowment, + }; + let result = host.call(&msg).await?; + state.gas_left -= msg.gas - result.gas_left; + + state.return_data = result.output_data; + if result.status_code == StatusCode::Success { + *state.stack.get_mut(0) = address_to_u256( + result + .create_address + .ok_or_else(|| anyhow::anyhow!("expected create address"))?, + ); + } + + Ok(InstructionResolution::Continue) +} diff --git a/src/instructions/control.rs b/src/instructions/control.rs new file mode 100644 index 0000000..7454e9a --- /dev/null +++ b/src/instructions/control.rs @@ -0,0 +1,56 @@ +use super::*; +use crate::{interpreter::JumpdestMap, StatusCode}; + +#[inline] +pub(crate) fn ret(state: &mut ExecutionState, status_code: StatusCode) -> InstructionResolution { + let offset = *state.stack.get(0); + let size = *state.stack.get(1); + + if let Some(region) = if let Ok(r) = super::memory::verify_memory_region(state, offset, size) { + r + } else { + return InstructionResolution::Exit(StatusCode::OutOfGas); + } { + state.output_data = state.memory[region.offset..region.offset + region.size.get()] + .to_vec() + .into(); + } + + InstructionResolution::Exit(status_code) +} + +pub(crate) fn op_jump( + state: &mut ExecutionState, + jumpdest_map: &JumpdestMap, +) -> InstructionResolution { + let dst = state.stack.pop(); + if !jumpdest_map.contains(dst) { + return InstructionResolution::Exit(StatusCode::BadJumpDestination); + } + + InstructionResolution::Jump(dst.as_usize()) +} + +pub(crate) fn calldataload(state: &mut ExecutionState) { + let index = state.stack.pop(); + + let input_len = state.message.input_data.len(); + + state.stack.push({ + if index > U256::from(input_len) { + U256::zero() + } else { + let index_usize = index.as_usize(); + let end = core::cmp::min(index_usize + 32, input_len); + + let mut data = [0; 32]; + data[..end - index_usize].copy_from_slice(&state.message.input_data[index_usize..end]); + + data.into() + } + }); +} + +pub(crate) fn calldatasize(state: &mut ExecutionState) { + state.stack.push(state.message.input_data.len().into()); +} diff --git a/src/instructions/external.rs b/src/instructions/external.rs new file mode 100644 index 0000000..22d429b --- /dev/null +++ b/src/instructions/external.rs @@ -0,0 +1,369 @@ +use ethereum_types::H256; + +use super::{ + properties::{COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST}, + *, +}; +use crate::{ + common::{address_to_u256, u256_to_address}, + host::*, + instructions::properties::{ADDITIONAL_COLD_ACCOUNT_ACCESS_COST, COLD_SLOAD_COST}, + state::ExecutionState, +}; + +#[inline] +pub(crate) fn address(state: &mut ExecutionState) { + state.stack.push(address_to_u256(state.message.destination)); +} + +#[inline] +pub(crate) fn caller(state: &mut ExecutionState) { + state.stack.push(address_to_u256(state.message.sender)); +} + +#[inline] +pub(crate) fn callvalue(state: &mut ExecutionState) { + state.stack.push(state.message.value); +} + +#[inline] +pub(crate) async fn balance( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + let address = u256_to_address(state.stack.pop()); + + if state.evm_revision >= Revision::Berlin + && host.access_account(address).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + state.stack.push(host.get_balance(address).await?); + + Ok(InstructionResolution::Continue) +} + +#[inline] +pub(crate) async fn extcodesize( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + let address = u256_to_address(state.stack.pop()); + + if state.evm_revision >= Revision::Berlin + && host.access_account(address).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + state.stack.push(host.get_code_size(address).await?); + + Ok(InstructionResolution::Continue) +} + +#[inline] +pub(crate) async fn gasprice( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state.stack.push(host.get_tx_context().await?.tx_gas_price); + Ok(()) +} + +#[inline] +pub(crate) async fn origin( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(address_to_u256(host.get_tx_context().await?.tx_origin)); + Ok(()) +} + +#[inline] +pub(crate) async fn coinbase( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(address_to_u256(host.get_tx_context().await?.block_coinbase)); + Ok(()) +} + +#[inline] +pub(crate) async fn number( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_tx_context().await?.block_number.into()); + Ok(()) +} + +#[inline] +pub(crate) async fn timestamp( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_tx_context().await?.block_timestamp.into()); + Ok(()) +} + +#[inline] +pub(crate) async fn gaslimit( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_tx_context().await?.block_gas_limit.into()); + Ok(()) +} + +pub(crate) async fn difficulty( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_tx_context().await?.block_difficulty); + Ok(()) +} + +pub(crate) async fn chainid( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state.stack.push(host.get_tx_context().await?.chain_id); + Ok(()) +} + +pub(crate) async fn basefee( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_tx_context().await?.block_base_fee); + Ok(()) +} + +pub(crate) async fn selfbalance( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + state + .stack + .push(host.get_balance(state.message.destination).await?); + Ok(()) +} + +pub(crate) async fn blockhash( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result<()> { + let number = state.stack.pop(); + + let upper_bound = host.get_tx_context().await?.block_number; + let lower_bound = upper_bound.saturating_sub(256); + + let mut header = H256::zero(); + if number <= u64::MAX.into() { + let n = number.as_u64(); + if (lower_bound..upper_bound).contains(&n) { + header = host.get_block_hash(n).await?; + } + } + + state.stack.push(U256::from_big_endian(&header.0)); + + Ok(()) +} + +pub(crate) async fn log( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + if state.message.is_static { + return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); + } + + let offset = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(r) = memory::verify_memory_region(state, offset, size) { + r + } else { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + }; + + if let Some(region) = ®ion { + let cost = region.size.get() as i64 * 8; + state.gas_left -= cost; + if cost < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + let mut topics = [H256::zero(); N]; + for topic in &mut topics { + *topic = H256(state.stack.pop().into()); + } + + let data = if let Some(region) = region { + &state.memory[region.offset..region.offset + region.size.get()] + } else { + &[] + }; + host.emit_log(state.message.destination, data, &topics) + .await?; + Ok(InstructionResolution::Continue) +} + +#[inline] +pub(crate) async fn sload( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + let key = H256(state.stack.pop().into()); + + if state.evm_revision >= Revision::Berlin + && host.access_storage(state.message.destination, key).await? == AccessStatus::Cold + { + // The warm storage access cost is already applied (from the cost table). + // Here we need to apply additional cold storage access cost. + const ADDITIONAL_COLD_SLOAD_COST: u16 = COLD_SLOAD_COST - WARM_STORAGE_READ_COST; + state.gas_left -= i64::from(ADDITIONAL_COLD_SLOAD_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + state.stack.push(U256::from_big_endian( + host.get_storage(state.message.destination, key) + .await? + .as_bytes(), + )); + + Ok(InstructionResolution::Continue) +} + +#[inline] +pub(crate) async fn sstore( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + if state.message.is_static { + return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); + } + + if state.evm_revision >= Revision::Istanbul && state.gas_left <= 2300 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + + let key = H256(state.stack.pop().into()); + let value = H256(state.stack.pop().into()); + + let mut cost = 0; + if state.evm_revision >= Revision::Berlin + && host.access_storage(state.message.destination, key).await? == AccessStatus::Cold + { + cost = COLD_SLOAD_COST; + } + + let status = host + .set_storage(state.message.destination, key, value) + .await?; + + cost = match status { + StorageStatus::Unchanged | StorageStatus::ModifiedAgain => { + if state.evm_revision >= Revision::Berlin { + cost + WARM_STORAGE_READ_COST + } else if state.evm_revision == Revision::Istanbul { + 800 + } else if state.evm_revision == Revision::Constantinople { + 200 + } else { + 5000 + } + } + StorageStatus::Modified | StorageStatus::Deleted => { + if state.evm_revision >= Revision::Berlin { + cost + 5000 - COLD_SLOAD_COST + } else { + 5000 + } + } + StorageStatus::Added => cost + 20000, + }; + state.gas_left -= i64::from(cost); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + Ok(InstructionResolution::Continue) +} + +pub(crate) async fn selfdestruct( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + if state.message.is_static { + return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); + } + + let beneficiary = u256_to_address(state.stack.pop()); + + if state.evm_revision >= Revision::Berlin + && host.access_account(beneficiary).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + if state.evm_revision >= Revision::Tangerine + && (state.evm_revision == Revision::Tangerine + || !host.get_balance(state.message.destination).await?.is_zero()) + { + // After TANGERINE_WHISTLE apply additional cost of + // sending value to a non-existing account. + if !host.account_exists(beneficiary).await? { + state.gas_left -= 25000; + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + } + + host.selfdestruct(state.message.destination, beneficiary) + .await?; + Ok(InstructionResolution::Continue) +} + +#[cfg(test)] +mod tests { + use crate::common::u256_to_address; + use ethereum_types::Address; + use hex_literal::hex; + + #[test] + fn u256_to_address_conversion() { + assert_eq!( + u256_to_address(0x42.into()), + Address::from(hex!("0000000000000000000000000000000000000042")) + ); + } +} diff --git a/src/instructions/instruction_table.rs b/src/instructions/instruction_table.rs new file mode 100644 index 0000000..2efa2bf --- /dev/null +++ b/src/instructions/instruction_table.rs @@ -0,0 +1,42 @@ +use crate::{instructions::properties, Revision}; +use once_cell::sync::Lazy; + +#[derive(Clone, Copy, Debug)] +pub struct InstructionTableEntry { + pub gas_cost: u16, + pub stack_height_required: u8, + pub can_overflow_stack: bool, +} + +pub type InstructionTable = [Option; 256]; +pub type InstructionTables = [InstructionTable; Revision::len()]; + +pub static INSTRUCTION_TABLES: Lazy = Lazy::new(|| { + let mut table = [[None; 256]; Revision::len()]; + + for revision in Revision::iter() { + for (opcode, &cost) in properties::gas_costs(revision).iter().enumerate() { + if let Some(cost) = cost { + let stack_height_required = properties::PROPERTIES[opcode] + .unwrap() + .stack_height_required; + + // Because any instruction can increase stack height at most of 1, + // stack overflow can only happen if stack height is already at the limit. + assert!(properties::PROPERTIES[opcode].unwrap().stack_height_change <= 1); + + table[revision as usize][opcode] = Some(InstructionTableEntry { + gas_cost: cost, + stack_height_required, + can_overflow_stack: properties::PROPERTIES[opcode].unwrap().stack_height_change + > 0, + }); + } + } + } + table +}); + +pub fn get_baseline_instruction_table(revision: Revision) -> &'static InstructionTable { + &INSTRUCTION_TABLES[revision as usize] +} diff --git a/src/instructions/memory.rs b/src/instructions/memory.rs new file mode 100644 index 0000000..a1d5582 --- /dev/null +++ b/src/instructions/memory.rs @@ -0,0 +1,363 @@ +use super::{properties::*, *}; +use crate::{common::u256_to_address, host::AccessStatus, state::Stack, Host}; +use sha3::{Digest, Keccak256}; +use std::{cmp::min, num::NonZeroUsize}; + +const MAX_BUFFER_SIZE: u32 = u32::MAX; + +/// The size of the EVM 256-bit word. +const WORD_SIZE: i64 = 32; + +/// Returns number of words what would fit to provided number of bytes, +/// i.e. it rounds up the number bytes to number of words. +#[inline] +pub(crate) fn num_words(size_in_bytes: usize) -> i64 { + ((size_in_bytes as i64) + (WORD_SIZE - 1)) / WORD_SIZE +} + +#[inline] +pub(crate) fn mload(state: &mut ExecutionState) -> StatusCode { + let index = state.stack.pop(); + + let region = if let Ok(r) = + verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) + .map(|region| region.unwrap()) + { + r + } else { + return StatusCode::OutOfGas; + }; + + let value = + U256::from_big_endian(&state.memory[region.offset..region.offset + region.size.get()]); + + state.stack.push(value); + + StatusCode::Success +} + +#[inline] +pub(crate) fn mstore(state: &mut ExecutionState) -> StatusCode { + let index = state.stack.pop(); + let value = state.stack.pop(); + + let region = if let Ok(r) = + verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) + .map(|region| region.unwrap()) + { + r + } else { + return StatusCode::OutOfGas; + }; + + let mut b = [0; 32]; + value.to_big_endian(&mut b); + state.memory[region.offset..region.offset + 32].copy_from_slice(&b); + StatusCode::Success +} + +#[inline] +pub(crate) fn mstore8(state: &mut ExecutionState) -> StatusCode { + let index = state.stack.pop(); + let value = state.stack.pop(); + + let region = if let Ok(r) = + verify_memory_region_u64(state, index, NonZeroUsize::new(1).unwrap()) + .map(|region| region.unwrap()) + { + r + } else { + return StatusCode::OutOfGas; + }; + + let value = (value.low_u32() & 0xff) as u8; + + state.memory[region.offset] = value; + StatusCode::Success +} + +#[inline] +pub(crate) fn msize(state: &mut ExecutionState) { + state.stack.push(state.memory.len().into()); +} + +#[inline] + +pub(crate) fn verify_memory_region_u64( + state: &mut ExecutionState, + offset: U256, + size: NonZeroUsize, +) -> Result, ()> { + if offset > U256::from(MAX_BUFFER_SIZE) { + return Err(()); + } + + let new_size = offset.as_usize() + size.get(); + let current_size = state.memory.len(); + if new_size > current_size { + let new_words = num_words(new_size); + let current_words = (current_size / 32) as i64; + let new_cost = 3 * new_words + new_words * new_words / 512; + let current_cost = 3 * current_words + current_words * current_words / 512; + let cost = new_cost - current_cost; + + state.gas_left -= cost; + + if state.gas_left < 0 { + return Err(()); + } + + state + .memory + .resize((new_words * WORD_SIZE) as usize, Default::default()); + } + + Ok(Some(MemoryRegion { + offset: offset.as_usize(), + size, + })) +} + +pub(crate) struct MemoryRegion { + pub offset: usize, + pub size: NonZeroUsize, +} + +#[inline] +pub(crate) fn verify_memory_region( + state: &mut ExecutionState, + offset: U256, + size: U256, +) -> Result, ()> { + if size.is_zero() { + return Ok(None); + } + + if size > U256::from(MAX_BUFFER_SIZE) { + return Err(()); + } + + verify_memory_region_u64(state, offset, NonZeroUsize::new(size.as_usize()).unwrap()) +} + +#[inline] +pub(crate) fn calldatacopy(state: &mut ExecutionState) -> InstructionResolution { + let mem_index = state.stack.pop(); + let input_index = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(region) = verify_memory_region(state, mem_index, size) { + region + } else { + return InstructionResolution::Exit(StatusCode::OutOfGas); + }; + + if let Some(region) = ®ion { + let copy_cost = num_words(region.size.get()) * 3; + state.gas_left -= copy_cost; + if state.gas_left < 0 { + return InstructionResolution::Exit(StatusCode::OutOfGas); + } + + let input_len = state.message.input_data.len().into(); + + let src = core::cmp::min(input_len, input_index).as_usize(); + let copy_size = core::cmp::min(size, input_len - src).as_usize(); + + if copy_size > 0 { + state.memory[region.offset..region.offset + copy_size] + .copy_from_slice(&state.message.input_data[src..src + copy_size]); + } + + if region.size.get() - copy_size > 0 { + state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); + } + } + + InstructionResolution::Continue +} + +#[inline] +pub(crate) fn keccak256(state: &mut ExecutionState) -> InstructionResolution { + let index = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(region) = verify_memory_region(state, index, size) { + region + } else { + return InstructionResolution::Exit(StatusCode::OutOfGas); + }; + + state.stack.push(U256::from_big_endian(&*Keccak256::digest( + if let Some(region) = region { + let w = num_words(region.size.get()); + let cost = w * 6; + state.gas_left -= cost; + if state.gas_left < 0 { + return InstructionResolution::Exit(StatusCode::OutOfGas); + } + + &state.memory[region.offset..region.offset + region.size.get()] + } else { + &[] + }, + ))); + + InstructionResolution::Continue +} + +#[inline] +pub(crate) fn codesize(stack: &mut Stack, code: &[u8]) { + stack.push(code.len().into()) +} + +#[inline] +pub(crate) fn codecopy(state: &mut ExecutionState, code: &[u8]) -> InstructionResolution { + // TODO: Similar to calldatacopy(). + + let mem_index = state.stack.pop(); + let input_index = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { + r + } else { + return InstructionResolution::Exit(StatusCode::OutOfGas); + }; + + if let Some(region) = region { + let src = min(U256::from(code.len()), input_index).as_usize(); + let copy_size = min(region.size.get(), code.len() - src); + + let copy_cost = num_words(region.size.get()) * 3; + state.gas_left -= copy_cost; + if state.gas_left < 0 { + return InstructionResolution::Exit(StatusCode::OutOfGas); + } + + // TODO: Add unit tests for each combination of conditions. + if copy_size > 0 { + state.memory[region.offset..region.offset + copy_size] + .copy_from_slice(&code[src..src + copy_size]); + } + + if region.size.get() - copy_size > 0 { + state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); + } + } + + InstructionResolution::Continue +} + +#[inline] +pub(crate) async fn extcodecopy( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + let addr = u256_to_address(state.stack.pop()); + let mem_index = state.stack.pop(); + let input_index = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { + r + } else { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + }; + + if let Some(region) = ®ion { + let copy_cost = num_words(region.size.get()) * 3; + state.gas_left -= copy_cost; + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + if state.evm_revision >= Revision::Berlin + && host.access_account(addr).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + if let Some(region) = region { + let src = min(U256::from(MAX_BUFFER_SIZE), input_index).as_usize(); + + let num_bytes_copied = host + .copy_code( + addr, + src, + &mut state.memory[region.offset..region.offset + region.size.get()], + ) + .await?; + if region.size.get() - num_bytes_copied > 0 { + state.memory[region.offset + num_bytes_copied..region.offset + region.size.get()] + .fill(0); + } + } + Ok(InstructionResolution::Continue) +} + +#[inline] +pub(crate) fn returndatasize(state: &mut ExecutionState) { + state.stack.push(state.return_data.len().into()); +} + +#[inline] +pub(crate) fn returndatacopy(state: &mut ExecutionState) -> InstructionResolution { + let mem_index = state.stack.pop(); + let input_index = state.stack.pop(); + let size = state.stack.pop(); + + let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { + r + } else { + return InstructionResolution::Exit(StatusCode::OutOfGas); + }; + + if input_index > U256::from(state.return_data.len()) { + return InstructionResolution::Exit(StatusCode::InvalidMemoryAccess); + } + let src = input_index.as_usize(); + + if src + region.as_ref().map(|r| r.size.get()).unwrap_or(0) > state.return_data.len() { + return InstructionResolution::Exit(StatusCode::InvalidMemoryAccess); + } + + if let Some(region) = region { + let copy_cost = num_words(region.size.get()) * 3; + state.gas_left -= copy_cost; + if state.gas_left < 0 { + return InstructionResolution::Exit(StatusCode::OutOfGas); + } + + state.memory[region.offset..region.offset + region.size.get()] + .copy_from_slice(&state.return_data[src..src + region.size.get()]); + } + + InstructionResolution::Continue +} + +#[inline] +pub(crate) async fn extcodehash( + host: &mut H, + state: &mut ExecutionState, +) -> anyhow::Result { + let addr = u256_to_address(state.stack.pop()); + + if state.evm_revision >= Revision::Berlin + && host.access_account(addr).await? == AccessStatus::Cold + { + state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); + if state.gas_left < 0 { + return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); + } + } + + state + .stack + .push(U256::from_big_endian(&host.get_code_hash(addr).await?.0)); + Ok(InstructionResolution::Continue) +} diff --git a/src/instructions/mod.rs b/src/instructions/mod.rs new file mode 100644 index 0000000..bc8c423 --- /dev/null +++ b/src/instructions/mod.rs @@ -0,0 +1,25 @@ +use crate::{ + common::{Revision, StatusCode}, + state::ExecutionState, +}; +use ethereum_types::U256; + +pub(crate) mod arithmetic; +pub(crate) mod bitwise; +pub(crate) mod boolean; +pub(crate) mod call; +pub(crate) mod control; +pub(crate) mod external; +pub(crate) mod instruction_table; +pub(crate) mod memory; +pub(crate) mod properties; +pub(crate) mod stack_manip; + +#[must_use] +pub(crate) enum InstructionResolution { + Continue, + Exit(StatusCode), + Jump(usize), +} + +pub use properties::PROPERTIES; diff --git a/src/instructions/properties.rs b/src/instructions/properties.rs new file mode 100644 index 0000000..91eaf26 --- /dev/null +++ b/src/instructions/properties.rs @@ -0,0 +1,372 @@ +use once_cell::sync::Lazy; + +use crate::{common::Revision, opcode::*}; + +pub(crate) const COLD_SLOAD_COST: u16 = 2100; +pub(crate) const COLD_ACCOUNT_ACCESS_COST: u16 = 2600; +pub(crate) const WARM_STORAGE_READ_COST: u16 = 100; + +/// Additional cold account access cost. +/// +/// The warm access cost is unconditionally applied for every account access instruction. +/// If the access turns out to be cold, this cost must be applied additionally. +pub(crate) const ADDITIONAL_COLD_ACCOUNT_ACCESS_COST: u16 = + COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST; + +/// EVM instruction properties +#[derive(Clone, Copy, Debug)] +pub struct Properties { + /// The instruction name + pub name: &'static str, + /// The number of stack items the instruction accesses during execution. + pub stack_height_required: u8, + /// The stack height change caused by the instruction execution. Can be negative. + pub stack_height_change: i8, +} + +impl Properties { + fn new(name: &'static str, stack_height_required: u8, stack_height_change: i8) -> Self { + Self { + name, + stack_height_required, + stack_height_change, + } + } +} + +pub static PROPERTIES: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = [None; 256]; + + table[OpCode::STOP.to_usize()] = Some(Properties::new("STOP", 0, 0)); + table[OpCode::ADD.to_usize()] = Some(Properties::new("ADD", 2, -1)); + table[OpCode::MUL.to_usize()] = Some(Properties::new("MUL", 2, -1)); + table[OpCode::SUB.to_usize()] = Some(Properties::new("SUB", 2, -1)); + table[OpCode::DIV.to_usize()] = Some(Properties::new("DIV", 2, -1)); + table[OpCode::SDIV.to_usize()] = Some(Properties::new("SDIV", 2, -1)); + table[OpCode::MOD.to_usize()] = Some(Properties::new("MOD", 2, -1)); + table[OpCode::SMOD.to_usize()] = Some(Properties::new("SMOD", 2, -1)); + table[OpCode::ADDMOD.to_usize()] = Some(Properties::new("ADDMOD", 3, -2)); + table[OpCode::MULMOD.to_usize()] = Some(Properties::new("MULMOD", 3, -2)); + table[OpCode::EXP.to_usize()] = Some(Properties::new("EXP", 2, -1)); + table[OpCode::SIGNEXTEND.to_usize()] = Some(Properties::new("SIGNEXTEND", 2, -1)); + + table[OpCode::LT.to_usize()] = Some(Properties::new("LT", 2, -1)); + table[OpCode::GT.to_usize()] = Some(Properties::new("GT", 2, -1)); + table[OpCode::SLT.to_usize()] = Some(Properties::new("SLT", 2, -1)); + table[OpCode::SGT.to_usize()] = Some(Properties::new("SGT", 2, -1)); + table[OpCode::EQ.to_usize()] = Some(Properties::new("EQ", 2, -1)); + table[OpCode::ISZERO.to_usize()] = Some(Properties::new("ISZERO", 1, 0)); + table[OpCode::AND.to_usize()] = Some(Properties::new("AND", 2, -1)); + table[OpCode::OR.to_usize()] = Some(Properties::new("OR", 2, -1)); + table[OpCode::XOR.to_usize()] = Some(Properties::new("XOR", 2, -1)); + table[OpCode::NOT.to_usize()] = Some(Properties::new("NOT", 1, 0)); + table[OpCode::BYTE.to_usize()] = Some(Properties::new("BYTE", 2, -1)); + table[OpCode::SHL.to_usize()] = Some(Properties::new("SHL", 2, -1)); + table[OpCode::SHR.to_usize()] = Some(Properties::new("SHR", 2, -1)); + table[OpCode::SAR.to_usize()] = Some(Properties::new("SAR", 2, -1)); + + table[OpCode::KECCAK256.to_usize()] = Some(Properties::new("KECCAK256", 2, -1)); + + table[OpCode::ADDRESS.to_usize()] = Some(Properties::new("ADDRESS", 0, 1)); + table[OpCode::BALANCE.to_usize()] = Some(Properties::new("BALANCE", 1, 0)); + table[OpCode::ORIGIN.to_usize()] = Some(Properties::new("ORIGIN", 0, 1)); + table[OpCode::CALLER.to_usize()] = Some(Properties::new("CALLER", 0, 1)); + table[OpCode::CALLVALUE.to_usize()] = Some(Properties::new("CALLVALUE", 0, 1)); + table[OpCode::CALLDATALOAD.to_usize()] = Some(Properties::new("CALLDATALOAD", 1, 0)); + table[OpCode::CALLDATASIZE.to_usize()] = Some(Properties::new("CALLDATASIZE", 0, 1)); + table[OpCode::CALLDATACOPY.to_usize()] = Some(Properties::new("CALLDATACOPY", 3, -3)); + table[OpCode::CODESIZE.to_usize()] = Some(Properties::new("CODESIZE", 0, 1)); + table[OpCode::CODECOPY.to_usize()] = Some(Properties::new("CODECOPY", 3, -3)); + table[OpCode::GASPRICE.to_usize()] = Some(Properties::new("GASPRICE", 0, 1)); + table[OpCode::EXTCODESIZE.to_usize()] = Some(Properties::new("EXTCODESIZE", 1, 0)); + table[OpCode::EXTCODECOPY.to_usize()] = Some(Properties::new("EXTCODECOPY", 4, -4)); + table[OpCode::RETURNDATASIZE.to_usize()] = Some(Properties::new("RETURNDATASIZE", 0, 1)); + table[OpCode::RETURNDATACOPY.to_usize()] = Some(Properties::new("RETURNDATACOPY", 3, -3)); + table[OpCode::EXTCODEHASH.to_usize()] = Some(Properties::new("EXTCODEHASH", 1, 0)); + + table[OpCode::BLOCKHASH.to_usize()] = Some(Properties::new("BLOCKHASH", 1, 0)); + table[OpCode::COINBASE.to_usize()] = Some(Properties::new("COINBASE", 0, 1)); + table[OpCode::TIMESTAMP.to_usize()] = Some(Properties::new("TIMESTAMP", 0, 1)); + table[OpCode::NUMBER.to_usize()] = Some(Properties::new("NUMBER", 0, 1)); + table[OpCode::DIFFICULTY.to_usize()] = Some(Properties::new("DIFFICULTY", 0, 1)); + table[OpCode::GASLIMIT.to_usize()] = Some(Properties::new("GASLIMIT", 0, 1)); + table[OpCode::CHAINID.to_usize()] = Some(Properties::new("CHAINID", 0, 1)); + table[OpCode::SELFBALANCE.to_usize()] = Some(Properties::new("SELFBALANCE", 0, 1)); + table[OpCode::BASEFEE.to_usize()] = Some(Properties::new("BASEFEE", 0, 1)); + + table[OpCode::POP.to_usize()] = Some(Properties::new("POP", 1, -1)); + table[OpCode::MLOAD.to_usize()] = Some(Properties::new("MLOAD", 1, 0)); + table[OpCode::MSTORE.to_usize()] = Some(Properties::new("MSTORE", 2, -2)); + table[OpCode::MSTORE8.to_usize()] = Some(Properties::new("MSTORE8", 2, -2)); + table[OpCode::SLOAD.to_usize()] = Some(Properties::new("SLOAD", 1, 0)); + table[OpCode::SSTORE.to_usize()] = Some(Properties::new("SSTORE", 2, -2)); + table[OpCode::JUMP.to_usize()] = Some(Properties::new("JUMP", 1, -1)); + table[OpCode::JUMPI.to_usize()] = Some(Properties::new("JUMPI", 2, -2)); + table[OpCode::PC.to_usize()] = Some(Properties::new("PC", 0, 1)); + table[OpCode::MSIZE.to_usize()] = Some(Properties::new("MSIZE", 0, 1)); + table[OpCode::GAS.to_usize()] = Some(Properties::new("GAS", 0, 1)); + table[OpCode::JUMPDEST.to_usize()] = Some(Properties::new("JUMPDEST", 0, 0)); + + table[OpCode::PUSH1.to_usize()] = Some(Properties::new("PUSH1", 0, 1)); + table[OpCode::PUSH2.to_usize()] = Some(Properties::new("PUSH2", 0, 1)); + table[OpCode::PUSH3.to_usize()] = Some(Properties::new("PUSH3", 0, 1)); + table[OpCode::PUSH4.to_usize()] = Some(Properties::new("PUSH4", 0, 1)); + table[OpCode::PUSH5.to_usize()] = Some(Properties::new("PUSH5", 0, 1)); + table[OpCode::PUSH6.to_usize()] = Some(Properties::new("PUSH6", 0, 1)); + table[OpCode::PUSH7.to_usize()] = Some(Properties::new("PUSH7", 0, 1)); + table[OpCode::PUSH8.to_usize()] = Some(Properties::new("PUSH8", 0, 1)); + table[OpCode::PUSH9.to_usize()] = Some(Properties::new("PUSH9", 0, 1)); + table[OpCode::PUSH10.to_usize()] = Some(Properties::new("PUSH10", 0, 1)); + table[OpCode::PUSH11.to_usize()] = Some(Properties::new("PUSH11", 0, 1)); + table[OpCode::PUSH12.to_usize()] = Some(Properties::new("PUSH12", 0, 1)); + table[OpCode::PUSH13.to_usize()] = Some(Properties::new("PUSH13", 0, 1)); + table[OpCode::PUSH14.to_usize()] = Some(Properties::new("PUSH14", 0, 1)); + table[OpCode::PUSH15.to_usize()] = Some(Properties::new("PUSH15", 0, 1)); + table[OpCode::PUSH16.to_usize()] = Some(Properties::new("PUSH16", 0, 1)); + table[OpCode::PUSH17.to_usize()] = Some(Properties::new("PUSH17", 0, 1)); + table[OpCode::PUSH18.to_usize()] = Some(Properties::new("PUSH18", 0, 1)); + table[OpCode::PUSH19.to_usize()] = Some(Properties::new("PUSH19", 0, 1)); + table[OpCode::PUSH20.to_usize()] = Some(Properties::new("PUSH20", 0, 1)); + table[OpCode::PUSH21.to_usize()] = Some(Properties::new("PUSH21", 0, 1)); + table[OpCode::PUSH22.to_usize()] = Some(Properties::new("PUSH22", 0, 1)); + table[OpCode::PUSH23.to_usize()] = Some(Properties::new("PUSH23", 0, 1)); + table[OpCode::PUSH24.to_usize()] = Some(Properties::new("PUSH24", 0, 1)); + table[OpCode::PUSH25.to_usize()] = Some(Properties::new("PUSH25", 0, 1)); + table[OpCode::PUSH26.to_usize()] = Some(Properties::new("PUSH26", 0, 1)); + table[OpCode::PUSH27.to_usize()] = Some(Properties::new("PUSH27", 0, 1)); + table[OpCode::PUSH28.to_usize()] = Some(Properties::new("PUSH28", 0, 1)); + table[OpCode::PUSH29.to_usize()] = Some(Properties::new("PUSH29", 0, 1)); + table[OpCode::PUSH30.to_usize()] = Some(Properties::new("PUSH30", 0, 1)); + table[OpCode::PUSH31.to_usize()] = Some(Properties::new("PUSH31", 0, 1)); + table[OpCode::PUSH32.to_usize()] = Some(Properties::new("PUSH32", 0, 1)); + + table[OpCode::DUP1.to_usize()] = Some(Properties::new("DUP1", 1, 1)); + table[OpCode::DUP2.to_usize()] = Some(Properties::new("DUP2", 2, 1)); + table[OpCode::DUP3.to_usize()] = Some(Properties::new("DUP3", 3, 1)); + table[OpCode::DUP4.to_usize()] = Some(Properties::new("DUP4", 4, 1)); + table[OpCode::DUP5.to_usize()] = Some(Properties::new("DUP5", 5, 1)); + table[OpCode::DUP6.to_usize()] = Some(Properties::new("DUP6", 6, 1)); + table[OpCode::DUP7.to_usize()] = Some(Properties::new("DUP7", 7, 1)); + table[OpCode::DUP8.to_usize()] = Some(Properties::new("DUP8", 8, 1)); + table[OpCode::DUP9.to_usize()] = Some(Properties::new("DUP9", 9, 1)); + table[OpCode::DUP10.to_usize()] = Some(Properties::new("DUP10", 10, 1)); + table[OpCode::DUP11.to_usize()] = Some(Properties::new("DUP11", 11, 1)); + table[OpCode::DUP12.to_usize()] = Some(Properties::new("DUP12", 12, 1)); + table[OpCode::DUP13.to_usize()] = Some(Properties::new("DUP13", 13, 1)); + table[OpCode::DUP14.to_usize()] = Some(Properties::new("DUP14", 14, 1)); + table[OpCode::DUP15.to_usize()] = Some(Properties::new("DUP15", 15, 1)); + table[OpCode::DUP16.to_usize()] = Some(Properties::new("DUP16", 16, 1)); + + table[OpCode::SWAP1.to_usize()] = Some(Properties::new("SWAP1", 2, 0)); + table[OpCode::SWAP2.to_usize()] = Some(Properties::new("SWAP2", 3, 0)); + table[OpCode::SWAP3.to_usize()] = Some(Properties::new("SWAP3", 4, 0)); + table[OpCode::SWAP4.to_usize()] = Some(Properties::new("SWAP4", 5, 0)); + table[OpCode::SWAP5.to_usize()] = Some(Properties::new("SWAP5", 6, 0)); + table[OpCode::SWAP6.to_usize()] = Some(Properties::new("SWAP6", 7, 0)); + table[OpCode::SWAP7.to_usize()] = Some(Properties::new("SWAP7", 8, 0)); + table[OpCode::SWAP8.to_usize()] = Some(Properties::new("SWAP8", 9, 0)); + table[OpCode::SWAP9.to_usize()] = Some(Properties::new("SWAP9", 10, 0)); + table[OpCode::SWAP10.to_usize()] = Some(Properties::new("SWAP10", 11, 0)); + table[OpCode::SWAP11.to_usize()] = Some(Properties::new("SWAP11", 12, 0)); + table[OpCode::SWAP12.to_usize()] = Some(Properties::new("SWAP12", 13, 0)); + table[OpCode::SWAP13.to_usize()] = Some(Properties::new("SWAP13", 14, 0)); + table[OpCode::SWAP14.to_usize()] = Some(Properties::new("SWAP14", 15, 0)); + table[OpCode::SWAP15.to_usize()] = Some(Properties::new("SWAP15", 16, 0)); + table[OpCode::SWAP16.to_usize()] = Some(Properties::new("SWAP16", 17, 0)); + + table[OpCode::LOG0.to_usize()] = Some(Properties::new("LOG0", 2, -2)); + table[OpCode::LOG1.to_usize()] = Some(Properties::new("LOG1", 3, -3)); + table[OpCode::LOG2.to_usize()] = Some(Properties::new("LOG2", 4, -4)); + table[OpCode::LOG3.to_usize()] = Some(Properties::new("LOG3", 5, -5)); + table[OpCode::LOG4.to_usize()] = Some(Properties::new("LOG4", 6, -6)); + + table[OpCode::CREATE.to_usize()] = Some(Properties::new("CREATE", 3, -2)); + table[OpCode::CALL.to_usize()] = Some(Properties::new("CALL", 7, -6)); + table[OpCode::CALLCODE.to_usize()] = Some(Properties::new("CALLCODE", 7, -6)); + table[OpCode::RETURN.to_usize()] = Some(Properties::new("RETURN", 2, -2)); + table[OpCode::DELEGATECALL.to_usize()] = Some(Properties::new("DELEGATECALL", 6, -5)); + table[OpCode::CREATE2.to_usize()] = Some(Properties::new("CREATE2", 4, -3)); + table[OpCode::STATICCALL.to_usize()] = Some(Properties::new("STATICCALL", 6, -5)); + table[OpCode::REVERT.to_usize()] = Some(Properties::new("REVERT", 2, -2)); + table[OpCode::INVALID.to_usize()] = Some(Properties::new("INVALID", 0, 0)); + table[OpCode::SELFDESTRUCT.to_usize()] = Some(Properties::new("SELFDESTRUCT", 1, -1)); + + table +}); + +#[allow(clippy::needless_range_loop)] +static FRONTIER_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = [None; 256]; + + table[OpCode::STOP.to_usize()] = Some(0); + table[OpCode::ADD.to_usize()] = Some(3); + table[OpCode::MUL.to_usize()] = Some(5); + table[OpCode::SUB.to_usize()] = Some(3); + table[OpCode::DIV.to_usize()] = Some(5); + table[OpCode::SDIV.to_usize()] = Some(5); + table[OpCode::MOD.to_usize()] = Some(5); + table[OpCode::SMOD.to_usize()] = Some(5); + table[OpCode::ADDMOD.to_usize()] = Some(8); + table[OpCode::MULMOD.to_usize()] = Some(8); + table[OpCode::EXP.to_usize()] = Some(10); + table[OpCode::SIGNEXTEND.to_usize()] = Some(5); + table[OpCode::LT.to_usize()] = Some(3); + table[OpCode::GT.to_usize()] = Some(3); + table[OpCode::SLT.to_usize()] = Some(3); + table[OpCode::SGT.to_usize()] = Some(3); + table[OpCode::EQ.to_usize()] = Some(3); + table[OpCode::ISZERO.to_usize()] = Some(3); + table[OpCode::AND.to_usize()] = Some(3); + table[OpCode::OR.to_usize()] = Some(3); + table[OpCode::XOR.to_usize()] = Some(3); + table[OpCode::NOT.to_usize()] = Some(3); + table[OpCode::BYTE.to_usize()] = Some(3); + table[OpCode::KECCAK256.to_usize()] = Some(30); + table[OpCode::ADDRESS.to_usize()] = Some(2); + table[OpCode::BALANCE.to_usize()] = Some(20); + table[OpCode::ORIGIN.to_usize()] = Some(2); + table[OpCode::CALLER.to_usize()] = Some(2); + table[OpCode::CALLVALUE.to_usize()] = Some(2); + table[OpCode::CALLDATALOAD.to_usize()] = Some(3); + table[OpCode::CALLDATASIZE.to_usize()] = Some(2); + table[OpCode::CALLDATACOPY.to_usize()] = Some(3); + table[OpCode::CODESIZE.to_usize()] = Some(2); + table[OpCode::CODECOPY.to_usize()] = Some(3); + table[OpCode::GASPRICE.to_usize()] = Some(2); + table[OpCode::EXTCODESIZE.to_usize()] = Some(20); + table[OpCode::EXTCODECOPY.to_usize()] = Some(20); + table[OpCode::BLOCKHASH.to_usize()] = Some(20); + table[OpCode::COINBASE.to_usize()] = Some(2); + table[OpCode::TIMESTAMP.to_usize()] = Some(2); + table[OpCode::NUMBER.to_usize()] = Some(2); + table[OpCode::DIFFICULTY.to_usize()] = Some(2); + table[OpCode::GASLIMIT.to_usize()] = Some(2); + table[OpCode::POP.to_usize()] = Some(2); + table[OpCode::MLOAD.to_usize()] = Some(3); + table[OpCode::MSTORE.to_usize()] = Some(3); + table[OpCode::MSTORE8.to_usize()] = Some(3); + table[OpCode::SLOAD.to_usize()] = Some(50); + table[OpCode::SSTORE.to_usize()] = Some(0); + table[OpCode::JUMP.to_usize()] = Some(8); + table[OpCode::JUMPI.to_usize()] = Some(10); + table[OpCode::PC.to_usize()] = Some(2); + table[OpCode::MSIZE.to_usize()] = Some(2); + + table[OpCode::GAS.to_usize()] = Some(2); + table[OpCode::JUMPDEST.to_usize()] = Some(1); + + for op in OpCode::PUSH1.to_usize()..=OpCode::PUSH32.to_usize() { + table[op] = Some(3); + } + + for op in OpCode::DUP1.to_usize()..=OpCode::DUP16.to_usize() { + table[op] = Some(3); + } + + for op in OpCode::SWAP1.to_usize()..=OpCode::SWAP16.to_usize() { + table[op] = Some(3); + } + + for (i, op) in (OpCode::LOG0.to_usize()..=OpCode::LOG4.to_usize()) + .into_iter() + .enumerate() + { + table[op] = Some((1 + i as u16) * 375); + } + + table[OpCode::CREATE.to_usize()] = Some(32000); + table[OpCode::CALL.to_usize()] = Some(40); + table[OpCode::CALLCODE.to_usize()] = Some(40); + table[OpCode::RETURN.to_usize()] = Some(0); + table[OpCode::INVALID.to_usize()] = Some(0); + table[OpCode::SELFDESTRUCT.to_usize()] = Some(0); + + table +}); + +static HOMESTEAD_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *FRONTIER_GAS_COSTS; + table[OpCode::DELEGATECALL.to_usize()] = Some(40); + table +}); + +static TANGERINE_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *HOMESTEAD_GAS_COSTS; + table[OpCode::BALANCE.to_usize()] = Some(400); + table[OpCode::EXTCODESIZE.to_usize()] = Some(700); + table[OpCode::EXTCODECOPY.to_usize()] = Some(700); + table[OpCode::SLOAD.to_usize()] = Some(200); + table[OpCode::CALL.to_usize()] = Some(700); + table[OpCode::CALLCODE.to_usize()] = Some(700); + table[OpCode::DELEGATECALL.to_usize()] = Some(700); + table[OpCode::SELFDESTRUCT.to_usize()] = Some(5000); + table +}); + +static SPURIOUS_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| *TANGERINE_GAS_COSTS); + +static BYZANTIUM_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *SPURIOUS_GAS_COSTS; + table[OpCode::RETURNDATASIZE.to_usize()] = Some(2); + table[OpCode::RETURNDATACOPY.to_usize()] = Some(3); + table[OpCode::STATICCALL.to_usize()] = Some(700); + table[OpCode::REVERT.to_usize()] = Some(0); + table +}); + +static CONSTANTINOPLE_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *BYZANTIUM_GAS_COSTS; + table[OpCode::SHL.to_usize()] = Some(3); + table[OpCode::SHR.to_usize()] = Some(3); + table[OpCode::SAR.to_usize()] = Some(3); + table[OpCode::EXTCODEHASH.to_usize()] = Some(400); + table[OpCode::CREATE2.to_usize()] = Some(32000); + table +}); + +static PETERSBURG_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| *CONSTANTINOPLE_GAS_COSTS); + +static ISTANBUL_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *PETERSBURG_GAS_COSTS; + table[OpCode::BALANCE.to_usize()] = Some(700); + table[OpCode::CHAINID.to_usize()] = Some(2); + table[OpCode::EXTCODEHASH.to_usize()] = Some(700); + table[OpCode::SELFBALANCE.to_usize()] = Some(5); + table[OpCode::SLOAD.to_usize()] = Some(800); + table +}); + +static BERLIN_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *ISTANBUL_GAS_COSTS; + table[OpCode::EXTCODESIZE.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::EXTCODECOPY.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::EXTCODEHASH.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::BALANCE.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::CALL.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::CALLCODE.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::DELEGATECALL.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::STATICCALL.to_usize()] = Some(WARM_STORAGE_READ_COST); + table[OpCode::SLOAD.to_usize()] = Some(WARM_STORAGE_READ_COST); + table +}); + +static LONDON_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| { + let mut table = *BERLIN_GAS_COSTS; + table[OpCode::BASEFEE.to_usize()] = Some(2); + table +}); + +pub fn gas_costs(revision: Revision) -> &'static [Option; 256] { + match revision { + Revision::Frontier => &FRONTIER_GAS_COSTS, + Revision::Homestead => &HOMESTEAD_GAS_COSTS, + Revision::Tangerine => &TANGERINE_GAS_COSTS, + Revision::Spurious => &SPURIOUS_GAS_COSTS, + Revision::Byzantium => &BYZANTIUM_GAS_COSTS, + Revision::Constantinople => &CONSTANTINOPLE_GAS_COSTS, + Revision::Petersburg => &PETERSBURG_GAS_COSTS, + Revision::Istanbul => &ISTANBUL_GAS_COSTS, + Revision::Berlin => &BERLIN_GAS_COSTS, + Revision::London => &LONDON_GAS_COSTS, + } +} diff --git a/src/instructions/stack_manip.rs b/src/instructions/stack_manip.rs new file mode 100644 index 0000000..2d32bb3 --- /dev/null +++ b/src/instructions/stack_manip.rs @@ -0,0 +1,20 @@ +use super::*; +use crate::state::*; + +pub(crate) fn load_push(stack: &mut Stack, code: &[u8]) -> usize { + stack.push(U256::from_big_endian(&code[..N])); + N +} + +pub(crate) fn dup(stack: &mut Stack) { + stack.push(*stack.get(N - 1)); +} + +pub(crate) fn swap(stack: &mut Stack) { + stack.swap_top(N); +} + +#[inline] +pub(crate) fn pop(stack: &mut Stack) { + stack.pop(); +} diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..255b97a --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,483 @@ +use self::instruction_table::*; +use crate::{ + instructions::{control::*, stack_manip::*, *}, + state::*, + tracing::Tracer, + *, +}; +use ethereum_types::U256; + +fn check_requirements( + instruction_table: &InstructionTable, + state: &mut ExecutionState, + op: OpCode, +) -> StatusCode { + let metrics = if let Some(v) = instruction_table[op.to_usize()] { + v + } else { + return StatusCode::UndefinedInstruction; + }; + + state.gas_left -= metrics.gas_cost as i64; + if state.gas_left < 0 { + return StatusCode::OutOfGas; + } + + let stack_size = state.stack.len(); + if stack_size == Stack::limit() { + if metrics.can_overflow_stack { + return StatusCode::StackOverflow; + } + } else if stack_size < metrics.stack_height_required.into() { + return StatusCode::StackUnderflow; + } + + StatusCode::Success +} + +pub struct JumpdestMap(Vec); + +impl JumpdestMap { + pub fn analyze(code: &[u8]) -> Self { + let mut jumpdest_map = vec![false; code.len()]; + + let mut i = 0; + while i < code.len() { + let opcode = OpCode(code[i]); + i += match opcode { + OpCode::JUMPDEST => { + jumpdest_map[i] = true; + 1 + } + OpCode::PUSH1 + | OpCode::PUSH2 + | OpCode::PUSH3 + | OpCode::PUSH4 + | OpCode::PUSH5 + | OpCode::PUSH6 + | OpCode::PUSH7 + | OpCode::PUSH8 + | OpCode::PUSH9 + | OpCode::PUSH10 + | OpCode::PUSH11 + | OpCode::PUSH12 + | OpCode::PUSH13 + | OpCode::PUSH14 + | OpCode::PUSH15 + | OpCode::PUSH16 + | OpCode::PUSH17 + | OpCode::PUSH18 + | OpCode::PUSH19 + | OpCode::PUSH20 + | OpCode::PUSH21 + | OpCode::PUSH22 + | OpCode::PUSH23 + | OpCode::PUSH24 + | OpCode::PUSH25 + | OpCode::PUSH26 + | OpCode::PUSH27 + | OpCode::PUSH28 + | OpCode::PUSH29 + | OpCode::PUSH30 + | OpCode::PUSH31 + | OpCode::PUSH32 => opcode.to_usize() - OpCode::PUSH1.to_usize() + 1, + _ => 1, + } + } + + Self(jumpdest_map) + } + + pub fn contains(&self, dst: U256) -> bool { + dst < self.0.len().into() && self.0[dst.as_usize()] + } +} + +/// Code with analysis. +pub struct AnalyzedCode { + jumpdest_map: JumpdestMap, + code: Bytes, +} + +impl AnalyzedCode { + /// Analyze code and prepare it for execution. + pub fn analyze(code: impl Into) -> Self { + let code = code.into(); + Self { + jumpdest_map: JumpdestMap::analyze(&code), + code, + } + } + + /// Execute analyzed EVM bytecode. + pub async fn execute( + &self, + host: &mut H, + tracer: &mut T, + message: Message, + revision: Revision, + ) -> anyhow::Result { + let state = &mut ExecutionState::new(message, revision); + self.execute_with_state(host, tracer, state).await + } + + pub(crate) async fn execute_with_state( + &self, + host: &mut H, + tracer: &mut T, + state: &mut ExecutionState, + ) -> anyhow::Result { + tracer.notify_execution_start(state.evm_revision, state.message.clone(), self.code.clone()); + + let instruction_table = get_baseline_instruction_table(state.evm_revision); + + let mut status = StatusCode::Success; + + let mut pc = 0; + + loop { + // Reached the end of code + if pc >= self.code.len() { + debug_assert_eq!(pc, self.code.len()); + break; + } + + let op = OpCode(self.code[pc]); + + tracer.notify_instruction_start(pc, op, state); + + status = check_requirements(instruction_table, state, op); + if !matches!(status, StatusCode::Success) { + break; + } + + let mut res = InstructionResolution::Continue; + + match op { + OpCode::STOP => { + res = InstructionResolution::Exit(StatusCode::Success); + } + OpCode::ADD => { + arithmetic::add(&mut state.stack); + } + OpCode::MUL => { + arithmetic::mul(&mut state.stack); + } + OpCode::SUB => { + arithmetic::sub(&mut state.stack); + } + OpCode::DIV => { + arithmetic::div(&mut state.stack); + } + OpCode::SDIV => { + arithmetic::sdiv(&mut state.stack); + } + OpCode::MOD => { + arithmetic::modulo(&mut state.stack); + } + OpCode::SMOD => { + arithmetic::smod(&mut state.stack); + } + OpCode::ADDMOD => { + arithmetic::addmod(&mut state.stack); + } + OpCode::MULMOD => { + arithmetic::mulmod(&mut state.stack); + } + OpCode::EXP => { + let status_code = arithmetic::exp(state); + if status_code != StatusCode::Success { + res = InstructionResolution::Exit(status_code); + } + } + OpCode::SIGNEXTEND => { + arithmetic::signextend(&mut state.stack); + } + OpCode::LT => { + boolean::lt(&mut state.stack); + } + OpCode::GT => { + boolean::gt(&mut state.stack); + } + OpCode::SLT => { + boolean::slt(&mut state.stack); + } + OpCode::SGT => { + boolean::sgt(&mut state.stack); + } + OpCode::EQ => { + boolean::eq(&mut state.stack); + } + OpCode::ISZERO => { + boolean::iszero(&mut state.stack); + } + OpCode::AND => { + boolean::and(&mut state.stack); + } + OpCode::OR => { + boolean::or(&mut state.stack); + } + OpCode::XOR => { + boolean::xor(&mut state.stack); + } + OpCode::NOT => { + boolean::not(&mut state.stack); + } + OpCode::BYTE => { + bitwise::byte(&mut state.stack); + } + OpCode::SHL => { + bitwise::shl(&mut state.stack); + } + OpCode::SHR => { + bitwise::shr(&mut state.stack); + } + OpCode::SAR => { + bitwise::sar(&mut state.stack); + } + + OpCode::KECCAK256 => { + res = memory::keccak256(state); + } + OpCode::ADDRESS => { + external::address(state); + } + OpCode::BALANCE => { + res = external::balance(host, state).await?; + } + OpCode::ORIGIN => { + external::origin(host, state).await?; + } + OpCode::CALLER => { + external::caller(state); + } + OpCode::CALLVALUE => { + external::callvalue(state); + } + OpCode::CALLDATALOAD => { + calldataload(state); + } + OpCode::CALLDATASIZE => { + calldatasize(state); + } + OpCode::CALLDATACOPY => { + res = memory::calldatacopy(state); + } + OpCode::CODESIZE => { + memory::codesize(&mut state.stack, &*self.code); + } + OpCode::CODECOPY => { + res = memory::codecopy(state, &*self.code); + } + OpCode::GASPRICE => { + external::gasprice(host, state).await?; + } + OpCode::EXTCODESIZE => { + res = external::extcodesize(host, state).await?; + } + OpCode::EXTCODECOPY => { + res = memory::extcodecopy(host, state).await?; + } + OpCode::RETURNDATASIZE => { + memory::returndatasize(state); + } + OpCode::RETURNDATACOPY => { + res = memory::returndatacopy(state); + } + OpCode::EXTCODEHASH => { + res = memory::extcodehash(host, state).await?; + } + OpCode::BLOCKHASH => { + external::blockhash(host, state).await?; + } + OpCode::COINBASE => { + external::coinbase(host, state).await?; + } + OpCode::TIMESTAMP => { + external::timestamp(host, state).await?; + } + OpCode::NUMBER => { + external::number(host, state).await?; + } + OpCode::DIFFICULTY => { + external::difficulty(host, state).await?; + } + OpCode::GASLIMIT => { + external::gaslimit(host, state).await?; + } + OpCode::CHAINID => { + external::chainid(host, state).await?; + } + OpCode::SELFBALANCE => { + external::selfbalance(host, state).await?; + } + OpCode::BASEFEE => { + external::basefee(host, state).await?; + } + OpCode::POP => { + stack_manip::pop(&mut state.stack); + } + OpCode::MLOAD => { + let status_code = memory::mload(state); + if !matches!(status_code, StatusCode::Success) { + res = InstructionResolution::Exit(status_code); + } + } + OpCode::MSTORE => { + let status_code = memory::mstore(state); + if !matches!(status_code, StatusCode::Success) { + res = InstructionResolution::Exit(status_code); + } + } + OpCode::MSTORE8 => { + let status_code = memory::mstore8(state); + if !matches!(status_code, StatusCode::Success) { + res = InstructionResolution::Exit(status_code); + } + } + + OpCode::JUMP => { + res = op_jump(state, &self.jumpdest_map); + } + OpCode::JUMPI => { + let v = state.stack.get(1); + if !v.is_zero() { + res = op_jump(state, &self.jumpdest_map); + } else { + state.stack.pop(); + } + state.stack.pop(); + } + OpCode::PC => state.stack.push(pc.into()), + OpCode::MSIZE => memory::msize(state), + OpCode::SLOAD => { + res = external::sload(host, state).await?; + } + OpCode::SSTORE => { + res = external::sstore(host, state).await?; + } + OpCode::GAS => state.stack.push(state.gas_left.into()), + OpCode::JUMPDEST => {} + OpCode::PUSH1 => pc += load_push::<1>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH2 => pc += load_push::<2>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH3 => pc += load_push::<3>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH4 => pc += load_push::<4>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH5 => pc += load_push::<5>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH6 => pc += load_push::<6>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH7 => pc += load_push::<7>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH8 => pc += load_push::<8>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH9 => pc += load_push::<9>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH10 => pc += load_push::<10>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH11 => pc += load_push::<11>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH12 => pc += load_push::<12>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH13 => pc += load_push::<13>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH14 => pc += load_push::<14>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH15 => pc += load_push::<15>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH16 => pc += load_push::<16>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH17 => pc += load_push::<17>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH18 => pc += load_push::<18>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH19 => pc += load_push::<19>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH20 => pc += load_push::<20>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH21 => pc += load_push::<21>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH22 => pc += load_push::<22>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH23 => pc += load_push::<23>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH24 => pc += load_push::<24>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH25 => pc += load_push::<25>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH26 => pc += load_push::<26>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH27 => pc += load_push::<27>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH28 => pc += load_push::<28>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH29 => pc += load_push::<29>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH30 => pc += load_push::<30>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH31 => pc += load_push::<31>(&mut state.stack, &self.code[pc + 1..]), + OpCode::PUSH32 => pc += load_push::<32>(&mut state.stack, &self.code[pc + 1..]), + + OpCode::DUP1 => dup::<1>(&mut state.stack), + OpCode::DUP2 => dup::<2>(&mut state.stack), + OpCode::DUP3 => dup::<3>(&mut state.stack), + OpCode::DUP4 => dup::<4>(&mut state.stack), + OpCode::DUP5 => dup::<5>(&mut state.stack), + OpCode::DUP6 => dup::<6>(&mut state.stack), + OpCode::DUP7 => dup::<7>(&mut state.stack), + OpCode::DUP8 => dup::<8>(&mut state.stack), + OpCode::DUP9 => dup::<9>(&mut state.stack), + OpCode::DUP10 => dup::<10>(&mut state.stack), + OpCode::DUP11 => dup::<11>(&mut state.stack), + OpCode::DUP12 => dup::<12>(&mut state.stack), + OpCode::DUP13 => dup::<13>(&mut state.stack), + OpCode::DUP14 => dup::<14>(&mut state.stack), + OpCode::DUP15 => dup::<15>(&mut state.stack), + OpCode::DUP16 => dup::<16>(&mut state.stack), + + OpCode::SWAP1 => swap::<1>(&mut state.stack), + OpCode::SWAP2 => swap::<2>(&mut state.stack), + OpCode::SWAP3 => swap::<3>(&mut state.stack), + OpCode::SWAP4 => swap::<4>(&mut state.stack), + OpCode::SWAP5 => swap::<5>(&mut state.stack), + OpCode::SWAP6 => swap::<6>(&mut state.stack), + OpCode::SWAP7 => swap::<7>(&mut state.stack), + OpCode::SWAP8 => swap::<8>(&mut state.stack), + OpCode::SWAP9 => swap::<9>(&mut state.stack), + OpCode::SWAP10 => swap::<10>(&mut state.stack), + OpCode::SWAP11 => swap::<11>(&mut state.stack), + OpCode::SWAP12 => swap::<12>(&mut state.stack), + OpCode::SWAP13 => swap::<13>(&mut state.stack), + OpCode::SWAP14 => swap::<14>(&mut state.stack), + OpCode::SWAP15 => swap::<15>(&mut state.stack), + OpCode::SWAP16 => swap::<16>(&mut state.stack), + + OpCode::LOG0 => res = external::log::<_, 0>(host, state).await?, + OpCode::LOG1 => res = external::log::<_, 1>(host, state).await?, + OpCode::LOG2 => res = external::log::<_, 2>(host, state).await?, + OpCode::LOG3 => res = external::log::<_, 3>(host, state).await?, + OpCode::LOG4 => res = external::log::<_, 4>(host, state).await?, + + OpCode::CREATE => res = call::create(host, state, false).await?, + OpCode::CALL => res = call::call(host, state, CallKind::Call, false).await?, + OpCode::CALLCODE => { + res = call::call(host, state, CallKind::CallCode, false).await? + } + OpCode::RETURN => res = ret(state, StatusCode::Success), + OpCode::DELEGATECALL => { + res = call::call(host, state, CallKind::DelegateCall, false).await? + } + OpCode::STATICCALL => res = call::call(host, state, CallKind::Call, true).await?, + OpCode::CREATE2 => res = call::create(host, state, true).await?, + OpCode::REVERT => res = ret(state, StatusCode::Revert), + OpCode::INVALID => { + res = InstructionResolution::Exit(StatusCode::InvalidInstruction) + } + OpCode::SELFDESTRUCT => { + res = external::selfdestruct(host, state).await?; + } + other => { + unreachable!("reached unhandled opcode: {}", other); + } + } + + match res { + InstructionResolution::Continue => pc += 1, + InstructionResolution::Exit(code) => { + status = code; + break; + } + InstructionResolution::Jump(offset) => pc = offset, + } + } + + let gas_left = match status { + StatusCode::Success | StatusCode::Revert => state.gas_left, + _ => 0, + }; + + let output = Output { + status_code: status, + gas_left, + output_data: state.output_data.clone(), + create_address: None, + }; + + tracer.notify_execution_end(&output); + + Ok(output) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bf8a986 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,59 @@ +//! Fast EVM implementation with full async support. Port of [evmone](https://github.com/ethereum/evmone) to Rust. +//! +//! # Usage +//! ``` +//! # tokio::runtime::Runtime::new().unwrap().block_on(async { +//! use evmodin::{*, host::*, util::*, tracing::*}; +//! use ethereum_types::*; +//! use hex_literal::hex; +//! +//! let my_code = Bytecode::new() +//! .mstore8_value(0, b'h') +//! .mstore8_value(1, b'e') +//! .mstore8_value(2, b'l') +//! .mstore8_value(3, b'l') +//! .mstore8_value(4, b'o') +//! .ret(0, 5) +//! .build(); +//! let message = Message { +//! kind: CallKind::Call, +//! is_static: true, +//! depth: 0, +//! gas: 200, +//! destination: Address::zero(), +//! sender: Address::zero(), +//! input_data: vec![].into(), +//! value: U256::zero(), +//! }; +//! +//! assert_eq!( +//! AnalyzedCode::analyze(my_code) +//! .execute(&mut DummyHost, &mut NoopTracer, message, Revision::London) +//! .await +//! .unwrap(), +//! Output { +//! status_code: StatusCode::Success, +//! gas_left: 146, +//! output_data: b"hello".to_vec().into(), +//! create_address: None, +//! } +//! ) +//! # }) +//! ``` +use bytes::Bytes; +pub use common::{CallKind, Message, Output, Revision, StatusCode}; +pub use host::Host; +pub use interpreter::AnalyzedCode; +pub use opcode::OpCode; + +mod common; +pub mod host; +#[doc(hidden)] +pub mod instructions; +mod interpreter; +pub mod opcode; +mod state; +pub mod tracing; + +#[cfg(feature = "util")] +pub mod util; diff --git a/src/opcode.rs b/src/opcode.rs new file mode 100644 index 0000000..a69a6d5 --- /dev/null +++ b/src/opcode.rs @@ -0,0 +1,335 @@ +use std::{borrow::Cow, fmt::Display}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OpCode(pub u8); + +impl OpCode { + #[inline] + pub const fn to_u8(self) -> u8 { + self.0 + } + + #[inline] + pub const fn to_usize(self) -> usize { + self.to_u8() as usize + } +} + +impl OpCode { + pub const STOP: OpCode = OpCode(0x00); + pub const ADD: OpCode = OpCode(0x01); + pub const MUL: OpCode = OpCode(0x02); + pub const SUB: OpCode = OpCode(0x03); + pub const DIV: OpCode = OpCode(0x04); + pub const SDIV: OpCode = OpCode(0x05); + pub const MOD: OpCode = OpCode(0x06); + pub const SMOD: OpCode = OpCode(0x07); + pub const ADDMOD: OpCode = OpCode(0x08); + pub const MULMOD: OpCode = OpCode(0x09); + pub const EXP: OpCode = OpCode(0x0a); + pub const SIGNEXTEND: OpCode = OpCode(0x0b); + + pub const LT: OpCode = OpCode(0x10); + pub const GT: OpCode = OpCode(0x11); + pub const SLT: OpCode = OpCode(0x12); + pub const SGT: OpCode = OpCode(0x13); + pub const EQ: OpCode = OpCode(0x14); + pub const ISZERO: OpCode = OpCode(0x15); + pub const AND: OpCode = OpCode(0x16); + pub const OR: OpCode = OpCode(0x17); + pub const XOR: OpCode = OpCode(0x18); + pub const NOT: OpCode = OpCode(0x19); + pub const BYTE: OpCode = OpCode(0x1a); + pub const SHL: OpCode = OpCode(0x1b); + pub const SHR: OpCode = OpCode(0x1c); + pub const SAR: OpCode = OpCode(0x1d); + + pub const KECCAK256: OpCode = OpCode(0x20); + + pub const ADDRESS: OpCode = OpCode(0x30); + pub const BALANCE: OpCode = OpCode(0x31); + pub const ORIGIN: OpCode = OpCode(0x32); + pub const CALLER: OpCode = OpCode(0x33); + pub const CALLVALUE: OpCode = OpCode(0x34); + pub const CALLDATALOAD: OpCode = OpCode(0x35); + pub const CALLDATASIZE: OpCode = OpCode(0x36); + pub const CALLDATACOPY: OpCode = OpCode(0x37); + pub const CODESIZE: OpCode = OpCode(0x38); + pub const CODECOPY: OpCode = OpCode(0x39); + pub const GASPRICE: OpCode = OpCode(0x3a); + pub const EXTCODESIZE: OpCode = OpCode(0x3b); + pub const EXTCODECOPY: OpCode = OpCode(0x3c); + pub const RETURNDATASIZE: OpCode = OpCode(0x3d); + pub const RETURNDATACOPY: OpCode = OpCode(0x3e); + pub const EXTCODEHASH: OpCode = OpCode(0x3f); + + pub const BLOCKHASH: OpCode = OpCode(0x40); + pub const COINBASE: OpCode = OpCode(0x41); + pub const TIMESTAMP: OpCode = OpCode(0x42); + pub const NUMBER: OpCode = OpCode(0x43); + pub const DIFFICULTY: OpCode = OpCode(0x44); + pub const GASLIMIT: OpCode = OpCode(0x45); + pub const CHAINID: OpCode = OpCode(0x46); + pub const SELFBALANCE: OpCode = OpCode(0x47); + pub const BASEFEE: OpCode = OpCode(0x48); + + pub const POP: OpCode = OpCode(0x50); + pub const MLOAD: OpCode = OpCode(0x51); + pub const MSTORE: OpCode = OpCode(0x52); + pub const MSTORE8: OpCode = OpCode(0x53); + pub const SLOAD: OpCode = OpCode(0x54); + pub const SSTORE: OpCode = OpCode(0x55); + pub const JUMP: OpCode = OpCode(0x56); + pub const JUMPI: OpCode = OpCode(0x57); + pub const PC: OpCode = OpCode(0x58); + pub const MSIZE: OpCode = OpCode(0x59); + pub const GAS: OpCode = OpCode(0x5a); + pub const JUMPDEST: OpCode = OpCode(0x5b); + + pub const PUSH1: OpCode = OpCode(0x60); + pub const PUSH2: OpCode = OpCode(0x61); + pub const PUSH3: OpCode = OpCode(0x62); + pub const PUSH4: OpCode = OpCode(0x63); + pub const PUSH5: OpCode = OpCode(0x64); + pub const PUSH6: OpCode = OpCode(0x65); + pub const PUSH7: OpCode = OpCode(0x66); + pub const PUSH8: OpCode = OpCode(0x67); + pub const PUSH9: OpCode = OpCode(0x68); + pub const PUSH10: OpCode = OpCode(0x69); + pub const PUSH11: OpCode = OpCode(0x6a); + pub const PUSH12: OpCode = OpCode(0x6b); + pub const PUSH13: OpCode = OpCode(0x6c); + pub const PUSH14: OpCode = OpCode(0x6d); + pub const PUSH15: OpCode = OpCode(0x6e); + pub const PUSH16: OpCode = OpCode(0x6f); + pub const PUSH17: OpCode = OpCode(0x70); + pub const PUSH18: OpCode = OpCode(0x71); + pub const PUSH19: OpCode = OpCode(0x72); + pub const PUSH20: OpCode = OpCode(0x73); + pub const PUSH21: OpCode = OpCode(0x74); + pub const PUSH22: OpCode = OpCode(0x75); + pub const PUSH23: OpCode = OpCode(0x76); + pub const PUSH24: OpCode = OpCode(0x77); + pub const PUSH25: OpCode = OpCode(0x78); + pub const PUSH26: OpCode = OpCode(0x79); + pub const PUSH27: OpCode = OpCode(0x7a); + pub const PUSH28: OpCode = OpCode(0x7b); + pub const PUSH29: OpCode = OpCode(0x7c); + pub const PUSH30: OpCode = OpCode(0x7d); + pub const PUSH31: OpCode = OpCode(0x7e); + pub const PUSH32: OpCode = OpCode(0x7f); + pub const DUP1: OpCode = OpCode(0x80); + pub const DUP2: OpCode = OpCode(0x81); + pub const DUP3: OpCode = OpCode(0x82); + pub const DUP4: OpCode = OpCode(0x83); + pub const DUP5: OpCode = OpCode(0x84); + pub const DUP6: OpCode = OpCode(0x85); + pub const DUP7: OpCode = OpCode(0x86); + pub const DUP8: OpCode = OpCode(0x87); + pub const DUP9: OpCode = OpCode(0x88); + pub const DUP10: OpCode = OpCode(0x89); + pub const DUP11: OpCode = OpCode(0x8a); + pub const DUP12: OpCode = OpCode(0x8b); + pub const DUP13: OpCode = OpCode(0x8c); + pub const DUP14: OpCode = OpCode(0x8d); + pub const DUP15: OpCode = OpCode(0x8e); + pub const DUP16: OpCode = OpCode(0x8f); + pub const SWAP1: OpCode = OpCode(0x90); + pub const SWAP2: OpCode = OpCode(0x91); + pub const SWAP3: OpCode = OpCode(0x92); + pub const SWAP4: OpCode = OpCode(0x93); + pub const SWAP5: OpCode = OpCode(0x94); + pub const SWAP6: OpCode = OpCode(0x95); + pub const SWAP7: OpCode = OpCode(0x96); + pub const SWAP8: OpCode = OpCode(0x97); + pub const SWAP9: OpCode = OpCode(0x98); + pub const SWAP10: OpCode = OpCode(0x99); + pub const SWAP11: OpCode = OpCode(0x9a); + pub const SWAP12: OpCode = OpCode(0x9b); + pub const SWAP13: OpCode = OpCode(0x9c); + pub const SWAP14: OpCode = OpCode(0x9d); + pub const SWAP15: OpCode = OpCode(0x9e); + pub const SWAP16: OpCode = OpCode(0x9f); + pub const LOG0: OpCode = OpCode(0xa0); + pub const LOG1: OpCode = OpCode(0xa1); + pub const LOG2: OpCode = OpCode(0xa2); + pub const LOG3: OpCode = OpCode(0xa3); + pub const LOG4: OpCode = OpCode(0xa4); + + pub const CREATE: OpCode = OpCode(0xf0); + pub const CALL: OpCode = OpCode(0xf1); + pub const CALLCODE: OpCode = OpCode(0xf2); + pub const RETURN: OpCode = OpCode(0xf3); + pub const DELEGATECALL: OpCode = OpCode(0xf4); + pub const CREATE2: OpCode = OpCode(0xf5); + + pub const STATICCALL: OpCode = OpCode(0xfa); + + pub const REVERT: OpCode = OpCode(0xfd); + pub const INVALID: OpCode = OpCode(0xfe); + pub const SELFDESTRUCT: OpCode = OpCode(0xff); +} + +impl OpCode { + pub const fn name(&self) -> &'static str { + match *self { + OpCode::STOP => "STOP", + OpCode::ADD => "ADD", + OpCode::MUL => "MUL", + OpCode::SUB => "SUB", + OpCode::DIV => "DIV", + OpCode::SDIV => "SDIV", + OpCode::MOD => "MOD", + OpCode::SMOD => "SMOD", + OpCode::ADDMOD => "ADDMOD", + OpCode::MULMOD => "MULMOD", + OpCode::EXP => "EXP", + OpCode::SIGNEXTEND => "SIGNEXTEND", + OpCode::LT => "LT", + OpCode::GT => "GT", + OpCode::SLT => "SLT", + OpCode::SGT => "SGT", + OpCode::EQ => "EQ", + OpCode::ISZERO => "ISZERO", + OpCode::AND => "AND", + OpCode::OR => "OR", + OpCode::XOR => "XOR", + OpCode::NOT => "NOT", + OpCode::BYTE => "BYTE", + OpCode::SHL => "SHL", + OpCode::SHR => "SHR", + OpCode::SAR => "SAR", + OpCode::KECCAK256 => "KECCAK256", + OpCode::ADDRESS => "ADDRESS", + OpCode::BALANCE => "BALANCE", + OpCode::ORIGIN => "ORIGIN", + OpCode::CALLER => "CALLER", + OpCode::CALLVALUE => "CALLVALUE", + OpCode::CALLDATALOAD => "CALLDATALOAD", + OpCode::CALLDATASIZE => "CALLDATASIZE", + OpCode::CALLDATACOPY => "CALLDATACOPY", + OpCode::CODESIZE => "CODESIZE", + OpCode::CODECOPY => "CODECOPY", + OpCode::GASPRICE => "GASPRICE", + OpCode::EXTCODESIZE => "EXTCODESIZE", + OpCode::EXTCODECOPY => "EXTCODECOPY", + OpCode::RETURNDATASIZE => "RETURNDATASIZE", + OpCode::RETURNDATACOPY => "RETURNDATACOPY", + OpCode::EXTCODEHASH => "EXTCODEHASH", + OpCode::BLOCKHASH => "BLOCKHASH", + OpCode::COINBASE => "COINBASE", + OpCode::TIMESTAMP => "TIMESTAMP", + OpCode::NUMBER => "NUMBER", + OpCode::DIFFICULTY => "DIFFICULTY", + OpCode::GASLIMIT => "GASLIMIT", + OpCode::CHAINID => "CHAINID", + OpCode::SELFBALANCE => "SELFBALANCE", + OpCode::BASEFEE => "BASEFEE", + OpCode::POP => "POP", + OpCode::MLOAD => "MLOAD", + OpCode::MSTORE => "MSTORE", + OpCode::MSTORE8 => "MSTORE8", + OpCode::SLOAD => "SLOAD", + OpCode::SSTORE => "SSTORE", + OpCode::JUMP => "JUMP", + OpCode::JUMPI => "JUMPI", + OpCode::PC => "PC", + OpCode::MSIZE => "MSIZE", + OpCode::GAS => "GAS", + OpCode::JUMPDEST => "JUMPDEST", + OpCode::PUSH1 => "PUSH1", + OpCode::PUSH2 => "PUSH2", + OpCode::PUSH3 => "PUSH3", + OpCode::PUSH4 => "PUSH4", + OpCode::PUSH5 => "PUSH5", + OpCode::PUSH6 => "PUSH6", + OpCode::PUSH7 => "PUSH7", + OpCode::PUSH8 => "PUSH8", + OpCode::PUSH9 => "PUSH9", + OpCode::PUSH10 => "PUSH10", + OpCode::PUSH11 => "PUSH11", + OpCode::PUSH12 => "PUSH12", + OpCode::PUSH13 => "PUSH13", + OpCode::PUSH14 => "PUSH14", + OpCode::PUSH15 => "PUSH15", + OpCode::PUSH16 => "PUSH16", + OpCode::PUSH17 => "PUSH17", + OpCode::PUSH18 => "PUSH18", + OpCode::PUSH19 => "PUSH19", + OpCode::PUSH20 => "PUSH20", + OpCode::PUSH21 => "PUSH21", + OpCode::PUSH22 => "PUSH22", + OpCode::PUSH23 => "PUSH23", + OpCode::PUSH24 => "PUSH24", + OpCode::PUSH25 => "PUSH25", + OpCode::PUSH26 => "PUSH26", + OpCode::PUSH27 => "PUSH27", + OpCode::PUSH28 => "PUSH28", + OpCode::PUSH29 => "PUSH29", + OpCode::PUSH30 => "PUSH30", + OpCode::PUSH31 => "PUSH31", + OpCode::PUSH32 => "PUSH32", + OpCode::DUP1 => "DUP1", + OpCode::DUP2 => "DUP2", + OpCode::DUP3 => "DUP3", + OpCode::DUP4 => "DUP4", + OpCode::DUP5 => "DUP5", + OpCode::DUP6 => "DUP6", + OpCode::DUP7 => "DUP7", + OpCode::DUP8 => "DUP8", + OpCode::DUP9 => "DUP9", + OpCode::DUP10 => "DUP10", + OpCode::DUP11 => "DUP11", + OpCode::DUP12 => "DUP12", + OpCode::DUP13 => "DUP13", + OpCode::DUP14 => "DUP14", + OpCode::DUP15 => "DUP15", + OpCode::DUP16 => "DUP16", + OpCode::SWAP1 => "SWAP1", + OpCode::SWAP2 => "SWAP2", + OpCode::SWAP3 => "SWAP3", + OpCode::SWAP4 => "SWAP4", + OpCode::SWAP5 => "SWAP5", + OpCode::SWAP6 => "SWAP6", + OpCode::SWAP7 => "SWAP7", + OpCode::SWAP8 => "SWAP8", + OpCode::SWAP9 => "SWAP9", + OpCode::SWAP10 => "SWAP10", + OpCode::SWAP11 => "SWAP11", + OpCode::SWAP12 => "SWAP12", + OpCode::SWAP13 => "SWAP13", + OpCode::SWAP14 => "SWAP14", + OpCode::SWAP15 => "SWAP15", + OpCode::SWAP16 => "SWAP16", + OpCode::LOG0 => "LOG0", + OpCode::LOG1 => "LOG1", + OpCode::LOG2 => "LOG2", + OpCode::LOG3 => "LOG3", + OpCode::LOG4 => "LOG4", + OpCode::CREATE => "CREATE", + OpCode::CALL => "CALL", + OpCode::CALLCODE => "CALLCODE", + OpCode::RETURN => "RETURN", + OpCode::DELEGATECALL => "DELEGATECALL", + OpCode::CREATE2 => "CREATE2", + OpCode::STATICCALL => "STATICCALL", + OpCode::REVERT => "REVERT", + OpCode::INVALID => "INVALID", + OpCode::SELFDESTRUCT => "SELFDESTRUCT", + _ => "UNDEFINED", + } + } +} + +impl Display for OpCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = self.name(); + + let n = if name == "UNDEFINED" { + Cow::Owned(format!("UNDEFINED(0x{:02x})", self.0)) + } else { + Cow::Borrowed(name) + }; + write!(f, "{}", n) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..733ddfc --- /dev/null +++ b/src/state.rs @@ -0,0 +1,101 @@ +use crate::common::{Message, Revision}; +use arrayvec::ArrayVec; +use bytes::Bytes; +use ethereum_types::U256; +use serde::Serialize; + +const SIZE: usize = 1024; + +#[derive(Clone, Debug, Default, Serialize)] +pub struct Stack(pub ArrayVec); + +impl Stack { + pub const fn limit() -> usize { + SIZE + } + + #[inline] + fn get_pos(&self, pos: usize) -> usize { + self.len() - 1 - pos + } + + pub fn get(&self, pos: usize) -> &U256 { + &self.0[self.get_pos(pos)] + } + + pub fn get_mut(&mut self, pos: usize) -> &mut U256 { + let pos = self.get_pos(pos); + &mut self.0[pos] + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn push(&mut self, v: U256) { + self.0.push(v) + } + + pub fn pop(&mut self) -> U256 { + self.0.pop().expect("underflow") + } + + pub fn swap_top(&mut self, pos: usize) { + let top = self.0.len() - 1; + let pos = self.get_pos(pos); + self.0.swap(top, pos); + } +} + +pub type Memory = Vec; + +/// Execution state +#[derive(Debug)] +pub struct ExecutionState { + pub(crate) gas_left: i64, + pub(crate) stack: Stack, + pub(crate) memory: Memory, + pub(crate) message: Message, + pub(crate) evm_revision: Revision, + pub(crate) return_data: Bytes, + pub(crate) output_data: Bytes, + pub(crate) current_block_cost: u32, +} + +impl ExecutionState { + pub fn new(message: Message, evm_revision: Revision) -> Self { + Self { + gas_left: message.gas, + stack: Default::default(), + memory: Memory::with_capacity(4 * 1024), + message, + evm_revision, + return_data: Default::default(), + output_data: Bytes::new(), + current_block_cost: 0, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stack() { + let mut stack = Stack::default(); + + let items = [0xde, 0xad, 0xbe, 0xef]; + + for (i, item) in items.iter().copied().enumerate() { + stack.push(item.into()); + assert_eq!(stack.len(), i + 1); + } + + assert_eq!(*stack.get(2), 0xad.into()); + + assert_eq!(stack.pop(), 0xef.into()); + + assert_eq!(*stack.get(2), 0xde.into()); + } +} diff --git a/src/tracing/mod.rs b/src/tracing/mod.rs new file mode 100644 index 0000000..7bc78ae --- /dev/null +++ b/src/tracing/mod.rs @@ -0,0 +1,119 @@ +use super::*; +use crate::state::*; +use serde::Serialize; + +/// Passed into execution context to collect metrics. +pub trait Tracer { + /// Called when execution starts. + fn notify_execution_start(&mut self, revision: Revision, message: Message, code: Bytes); + /// Called on each instruction. + fn notify_instruction_start(&mut self, pc: usize, opcode: OpCode, state: &ExecutionState); + /// Called when execution ends. + fn notify_execution_end(&mut self, output: &Output); +} + +/// Tracer which does nothing. +pub struct NoopTracer; + +impl Tracer for NoopTracer { + fn notify_execution_start(&mut self, _: Revision, _: Message, _: Bytes) {} + + fn notify_instruction_start(&mut self, _: usize, _: OpCode, _: &ExecutionState) {} + + fn notify_execution_end(&mut self, _: &Output) {} +} + +#[derive(Serialize)] +struct ExecutionStart { + pub depth: i32, + pub rev: Revision, + #[serde(rename = "static")] + pub is_static: bool, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct InstructionStart { + pub pc: usize, + pub op: u8, + pub op_name: &'static str, + pub gas: i64, + pub stack: Stack, + pub memory_size: usize, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ExecutionEnd { + pub error: Option, + pub gas: i64, + pub gas_used: i64, + pub output: String, +} + +struct TracerContext { + message: Message, + code: Bytes, +} + +/// Tracer which prints to stdout. +#[derive(Default)] +pub struct StdoutTracer { + execution_stack: Vec, +} + +impl Tracer for StdoutTracer { + fn notify_execution_start(&mut self, revision: Revision, message: Message, code: Bytes) { + println!( + "{}", + serde_json::to_string(&ExecutionStart { + depth: message.depth, + rev: revision, + is_static: message.is_static, + }) + .unwrap() + ); + self.execution_stack.push(TracerContext { message, code }); + } + + fn notify_instruction_start(&mut self, pc: usize, _: OpCode, state: &ExecutionState) { + let context = self.execution_stack.last().unwrap(); + let opcode = OpCode(context.code[pc]); + println!( + "{}", + serde_json::to_string(&InstructionStart { + pc, + op: opcode.0, + op_name: opcode.name(), + gas: state.gas_left, + stack: state.stack.clone(), + memory_size: state.memory.len() + }) + .unwrap() + ) + } + + fn notify_execution_end(&mut self, output: &Output) { + let context = self.execution_stack.pop().unwrap(); + let error = match output.status_code { + StatusCode::Success => None, + other => Some(other.to_string()), + }; + let (gas_left, gas_used) = if error.is_none() { + (output.gas_left, context.message.gas - output.gas_left) + } else { + (0, context.message.gas) + }; + + println!( + "{}", + serde_json::to_string(&ExecutionEnd { + error, + gas: gas_left, + gas_used, + output: hex::encode(&output.output_data), + }) + .unwrap() + ) + } +} diff --git a/src/util/bytecode.rs b/src/util/bytecode.rs new file mode 100644 index 0000000..c9ccc28 --- /dev/null +++ b/src/util/bytecode.rs @@ -0,0 +1,236 @@ +use crate::opcode::*; +use core::iter::repeat; +use ethereum_types::U256; + +/// EVM bytecode builder. +#[derive(Clone, Debug)] +pub struct Bytecode { + inner: Vec, +} + +impl Bytecode { + pub const fn new() -> Self { + Self { inner: Vec::new() } + } + + pub fn append(mut self, b: impl IntoIterator) -> Self { + self.inner.append(&mut b.into_iter().collect::>()); + self + } + + pub fn append_bc(mut self, b: impl Into) -> Self { + self.inner.append(&mut b.into().build()); + self + } + + pub fn repeat(mut self, n: usize) -> Self { + self.inner = repeat(self.inner.into_iter()).take(n).flatten().collect(); + self + } + + pub fn pushv(self, value: impl Into) -> Self { + let value = value.into(); + let b = <[u8; 32]>::from(value) + .iter() + .skip_while(|&&v| v == 0) + .copied() + .collect::>(); + + self.pushb(b) + } + + pub fn pushb(mut self, b: impl IntoIterator) -> Self { + let mut b = b.into_iter().collect::>(); + + if b.is_empty() { + b.push(0); + } + + self.inner + .extend_from_slice(&[(b.len() + OpCode::PUSH1.to_usize() - 1) as u8]); + self.inner.append(&mut b); + + self + } + + pub fn opcode(mut self, opcode: OpCode) -> Self { + self.inner.push(opcode.to_u8()); + self + } + + pub fn ret(mut self, index: impl Into, size: impl Into) -> Self { + self = self.pushv(size); + self = self.pushv(index); + self = self.opcode(OpCode::RETURN); + self + } + + pub fn mstore(mut self, index: impl Into) -> Self { + self = self.pushv(index); + self = self.opcode(OpCode::MSTORE); + self + } + + pub fn mstore_value(mut self, index: impl Into, value: impl Into) -> Self { + self = self.pushv(value); + self = self.pushv(index); + self = self.opcode(OpCode::MSTORE); + self + } + + pub fn mstore8(mut self, index: impl Into) -> Self { + self = self.pushv(index); + self = self.opcode(OpCode::MSTORE8); + self + } + + pub fn mstore8_value(mut self, index: impl Into, value: impl Into) -> Self { + self = self.pushv(value); + self = self.pushv(index); + self = self.opcode(OpCode::MSTORE8); + self + } + + pub fn ret_top(self) -> Self { + self.mstore(0).ret(0, 0x20) + } + + pub fn jump(self, target: impl Into) -> Self { + self.pushv(target).opcode(OpCode::JUMP) + } + + pub fn jumpi(self, target: impl Into, condition: impl Into) -> Self { + self.append(condition.into().build()) + .append(target.into().build()) + .opcode(OpCode::JUMPI) + } + + pub fn sstore(self, index: impl Into, value: impl Into) -> Self { + self.pushv(value).pushv(index).opcode(OpCode::SSTORE) + } + + pub fn sload(self, index: impl Into) -> Self { + self.pushv(index).opcode(OpCode::SLOAD) + } + + pub fn build(self) -> Vec { + self.inner + } +} + +impl From for Bytecode { + fn from(value: U256) -> Self { + Self::new().pushv(value) + } +} + +impl From for Bytecode { + fn from(opcode: OpCode) -> Self { + Self::new().opcode(opcode) + } +} + +impl From<[u8; N]> for Bytecode { + fn from(inner: [u8; N]) -> Self { + Self { + inner: Vec::from(&inner as &[u8]), + } + } +} + +impl From> for Bytecode { + fn from(inner: Vec) -> Self { + Self { inner } + } +} + +impl IntoIterator for Bytecode { + type Item = u8; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +pub struct CallInstruction { + op: OpCode, + address: U256, + gas: U256, + value: U256, + input: U256, + input_size: U256, + output: U256, + output_size: U256, +} + +impl CallInstruction { + fn new(op: OpCode, address: impl Into) -> Self { + Self { + op, + address: address.into(), + gas: 0.into(), + value: 0.into(), + input: 0.into(), + input_size: 0.into(), + output: 0.into(), + output_size: 0.into(), + } + } + + pub fn delegatecall(address: impl Into) -> Self { + Self::new(OpCode::DELEGATECALL, address) + } + + pub fn staticcall(address: impl Into) -> Self { + Self::new(OpCode::STATICCALL, address) + } + + pub fn call(address: impl Into) -> Self { + Self::new(OpCode::CALL, address) + } + + pub fn callcode(address: impl Into) -> Self { + Self::new(OpCode::CALLCODE, address) + } + + pub fn opcode(&self) -> OpCode { + self.op + } + + pub fn gas(mut self, gas: impl Into) -> Self { + self.gas = gas.into(); + self + } + + pub fn value(mut self, value: impl Into) -> Self { + self.value = value.into(); + self + } + + pub fn input(mut self, index: impl Into, size: impl Into) -> Self { + self.input = index.into(); + self.input_size = size.into(); + self + } + + pub fn output(mut self, index: impl Into, size: impl Into) -> Self { + self.output = index.into(); + self.output_size = size.into(); + self + } +} + +impl From for Bytecode { + fn from(call: CallInstruction) -> Self { + let mut b = Bytecode::new() + .pushv(call.output_size) + .pushv(call.output) + .pushv(call.input_size) + .pushv(call.input); + if call.op == OpCode::CALL || call.op == OpCode::CALLCODE { + b = b.pushv(call.value); + } + b.pushv(call.address).pushv(call.gas).opcode(call.op) + } +} diff --git a/src/util/mocked_host.rs b/src/util/mocked_host.rs new file mode 100644 index 0000000..6fdccb7 --- /dev/null +++ b/src/util/mocked_host.rs @@ -0,0 +1,346 @@ +use crate::{host::*, *}; +use async_trait::async_trait; +use bytes::Bytes; +use ethereum_types::{Address, H256, U256}; +use hex_literal::hex; +use parking_lot::Mutex; +use std::{cmp::min, collections::HashMap}; + +/// LOG record. +#[derive(Clone, Debug, PartialEq)] +pub struct LogRecord { + /// The address of the account which created the log. + pub creator: Address, + + /// The data attached to the log. + pub data: Bytes, + + /// The log topics. + pub topics: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SelfdestructRecord { + /// The address of the account which has self-destructed. + pub selfdestructed: Address, + + /// The address of the beneficiary account. + pub beneficiary: Address, +} + +#[derive(Clone, Debug, Default)] +pub struct StorageValue { + pub value: H256, + pub dirty: bool, + pub access_status: AccessStatus, +} + +#[derive(Clone, Debug, Default)] +pub struct Account { + /// The account nonce. + pub nonce: u64, + /// The account code. + pub code: Bytes, + /// The code hash. Can be a value not related to the actual code. + pub code_hash: H256, + /// The account balance. + pub balance: U256, + /// The account storage map. + pub storage: HashMap, +} + +const MAX_RECORDED_ACCOUNT_ACCESSES: usize = 200; +const MAX_RECORDED_CALLS: usize = 100; + +#[derive(Clone, Debug, Default)] +pub struct Records { + /// The copy of call inputs for the recorded_calls record. + pub call_inputs: Vec, + + pub blockhashes: Vec, + pub account_accesses: Vec
, + pub calls: Vec, + pub logs: Vec, + pub selfdestructs: Vec, +} + +#[derive(Debug)] +pub struct MockedHost { + pub accounts: HashMap, + pub tx_context: TxContext, + pub block_hash: H256, + pub call_result: Output, + pub recorded: Mutex, +} + +impl Clone for MockedHost { + fn clone(&self) -> Self { + Self { + accounts: self.accounts.clone(), + tx_context: self.tx_context.clone(), + block_hash: self.block_hash, + call_result: self.call_result.clone(), + recorded: Mutex::new(self.recorded.lock().clone()), + } + } +} + +impl Default for MockedHost { + fn default() -> Self { + Self { + accounts: Default::default(), + tx_context: TxContext { + tx_gas_price: U256::zero(), + tx_origin: Address::zero(), + block_coinbase: Address::zero(), + block_number: 0, + block_timestamp: 0, + block_gas_limit: 0, + block_difficulty: U256::zero(), + chain_id: U256::zero(), + block_base_fee: U256::zero(), + }, + block_hash: H256::zero(), + call_result: Output { + status_code: StatusCode::Success, + gas_left: 0, + output_data: Bytes::new(), + create_address: Some(Address::zero()), + }, + recorded: Default::default(), + } + } +} + +impl Records { + fn record_account_access(&mut self, address: Address) { + if self.account_accesses.len() < MAX_RECORDED_ACCOUNT_ACCESSES { + self.account_accesses.push(address) + } + } +} + +#[async_trait] +impl crate::Host for MockedHost { + async fn account_exists(&self, address: ethereum_types::Address) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + Ok(self.accounts.contains_key(&address)) + } + + async fn get_storage( + &self, + address: ethereum_types::Address, + key: H256, + ) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + Ok(self + .accounts + .get(&address) + .and_then(|account| account.storage.get(&key).map(|value| value.value)) + .unwrap_or_else(H256::zero)) + } + + async fn set_storage( + &mut self, + address: ethereum_types::Address, + key: H256, + value: H256, + ) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + // Get the reference to the old value. + // This will create the account in case it was not present. + // This is convenient for unit testing and standalone EVM execution to preserve the + // storage values after the execution terminates. + let old = self + .accounts + .entry(address) + .or_default() + .storage + .entry(key) + .or_default(); + + // Follow https://eips.ethereum.org/EIPS/eip-1283 specification. + // WARNING! This is not complete implementation as refund is not handled here. + + if old.value == value { + return Ok(StorageStatus::Unchanged); + } + + let status = if !old.dirty { + old.dirty = true; + if old.value.is_zero() { + StorageStatus::Added + } else if !value.is_zero() { + StorageStatus::Modified + } else { + StorageStatus::Deleted + } + } else { + StorageStatus::ModifiedAgain + }; + + old.value = value; + + Ok(status) + } + + async fn get_balance( + &self, + address: ethereum_types::Address, + ) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + Ok(self + .accounts + .get(&address) + .map(|acc| acc.balance) + .unwrap_or_else(U256::zero)) + } + + async fn get_code_size( + &self, + address: ethereum_types::Address, + ) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + Ok(self + .accounts + .get(&address) + .map(|acc| acc.code.len().into()) + .unwrap_or_else(U256::zero)) + } + + async fn get_code_hash(&self, address: ethereum_types::Address) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + Ok(self + .accounts + .get(&address) + .map(|acc| acc.code_hash) + .unwrap_or_else(H256::zero)) + } + + async fn copy_code( + &self, + address: Address, + code_offset: usize, + buffer: &mut [u8], + ) -> anyhow::Result { + self.recorded.lock().record_account_access(address); + + Ok(self + .accounts + .get(&address) + .map(|acc| { + let code = &acc.code; + + if code_offset >= code.len() { + return 0; + } + + let n = min(buffer.len(), code.len() - code_offset); + + buffer[..n].copy_from_slice(&code[code_offset..code_offset + n]); + + n + }) + .unwrap_or(0)) + } + + async fn selfdestruct( + &mut self, + address: ethereum_types::Address, + beneficiary: ethereum_types::Address, + ) -> anyhow::Result<()> { + let mut r = self.recorded.lock(); + + r.record_account_access(address); + r.selfdestructs.push(SelfdestructRecord { + selfdestructed: address, + beneficiary, + }); + + Ok(()) + } + + async fn call(&mut self, msg: &Message) -> anyhow::Result { + let mut r = self.recorded.lock(); + + r.record_account_access(msg.destination); + + if r.calls.len() < MAX_RECORDED_CALLS { + r.calls.push(msg.clone()); + let call_msg = msg; + if !call_msg.input_data.is_empty() { + r.call_inputs.push(call_msg.input_data.clone()); + } + } + Ok(self.call_result.clone()) + } + + async fn get_tx_context(&self) -> anyhow::Result { + Ok(self.tx_context.clone()) + } + + async fn get_block_hash(&self, block_number: u64) -> anyhow::Result { + self.recorded.lock().blockhashes.push(block_number); + Ok(self.block_hash) + } + + async fn emit_log( + &mut self, + address: ethereum_types::Address, + data: &[u8], + topics: &[H256], + ) -> anyhow::Result<()> { + self.recorded.lock().logs.push(LogRecord { + creator: address, + data: data.to_vec().into(), + topics: topics.to_vec(), + }); + Ok(()) + } + + async fn access_account( + &mut self, + address: ethereum_types::Address, + ) -> anyhow::Result { + let mut r = self.recorded.lock(); + + // Check if the address have been already accessed. + let already_accessed = r.account_accesses.iter().any(|&a| a == address); + + r.record_account_access(address); + + if address.0 >= hex!("0000000000000000000000000000000000000001") + && address.0 <= hex!("0000000000000000000000000000000000000009") + { + return Ok(AccessStatus::Warm); + } + + Ok(if already_accessed { + AccessStatus::Warm + } else { + AccessStatus::Cold + }) + } + + async fn access_storage( + &mut self, + address: ethereum_types::Address, + key: H256, + ) -> anyhow::Result { + let value = self + .accounts + .entry(address) + .or_default() + .storage + .entry(key) + .or_default(); + let access_status = value.access_status; + value.access_status = AccessStatus::Warm; + Ok(access_status) + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..107f106 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,6 @@ +mod bytecode; +pub mod mocked_host; +mod tester; + +pub use bytecode::*; +pub use tester::*; diff --git a/src/util/tester.rs b/src/util/tester.rs new file mode 100644 index 0000000..eaee9f4 --- /dev/null +++ b/src/util/tester.rs @@ -0,0 +1,279 @@ +use crate::{ + tracing::*, + util::{mocked_host::*, *}, + *, +}; +use bytes::Bytes; +use educe::Educe; +use ethereum_types::{Address, U256}; +use std::{future::Future, pin::Pin, sync::Arc}; + +async fn exec(host: &mut MockedHost, revision: Revision, message: Message, code: Bytes) -> Output { + // Add EIP-2929 tweak. + if revision >= Revision::Berlin { + host.access_account(message.sender).await.unwrap(); + host.access_account(message.destination).await.unwrap(); + } + AnalyzedCode::analyze(code) + .execute(host, &mut StdoutTracer::default(), message, revision) + .await + .unwrap() +} + +#[derive(Clone, Copy, Debug)] +enum GasCheck { + Used(i64), + Left(i64), +} + +#[allow(clippy::type_complexity)] +#[derive(Clone)] +enum ApplyHostFn { + Sync(Arc), + Async( + Arc< + dyn Fn(MockedHost, Message) -> Pin>> + + 'static, + >, + ), +} + +/// Tester that executes EVM bytecode with `MockedHost` context and runs set checks. +#[derive(Clone, Educe)] +#[educe(Debug)] +#[must_use] +pub struct EvmTester { + host: MockedHost, + #[educe(Debug(ignore))] + apply_host_fns: Vec, + #[educe(Debug(ignore))] + inspect_output_fn: Arc, + #[educe(Debug(ignore))] + inspect_host_fn: Arc, + #[educe(Debug(ignore))] + inspect_fn: Arc, + revision: Revision, + message: Message, + code: Bytes, + gas_check: Option, + expected_status_codes: Option>, + expected_output_data: Option>, +} + +impl Default for EvmTester { + fn default() -> Self { + Self::new() + } +} + +impl EvmTester { + /// Create new `EvmTester`. + pub fn new() -> Self { + Self { + host: MockedHost::default(), + apply_host_fns: vec![], + inspect_output_fn: Arc::new(|_| ()), + inspect_host_fn: Arc::new(|_, _| ()), + inspect_fn: Arc::new(|_, _, _| ()), + revision: Revision::Byzantium, + message: Message { + kind: CallKind::Call, + is_static: false, + depth: 0, + gas: i64::MAX, + destination: Address::zero(), + sender: Address::zero(), + input_data: Bytes::new(), + value: 0.into(), + }, + code: Bytes::new(), + gas_check: None, + expected_status_codes: None, + expected_output_data: None, + } + } + + /// Set code to be executed. + pub fn code(mut self, code: impl Into) -> Self { + self.code = code.into().build().into(); + self + } + + /// Queue function that will modify the host before execution. + pub fn apply_host_fn(mut self, host_fn: impl Fn(&mut MockedHost, &Message) + 'static) -> Self { + self.apply_host_fns + .push(ApplyHostFn::Sync(Arc::new(host_fn))); + self + } + + /// Queue function that will asynchronously modify the host before execution. + pub fn apply_host_fn_async(mut self, host_fn: F) -> Self + where + F: Fn(MockedHost, Message) -> Fut + 'static, + Fut: Future + 'static, + { + let f = Arc::new(host_fn); + self.apply_host_fns + .push(ApplyHostFn::Async(Arc::new(move |host, msg| { + Box::pin({ + let f = f.clone(); + async move { (f)(host, msg).await } + }) + }))); + self + } + + /// Set EVM revision for this tester. + pub fn revision(mut self, revision: Revision) -> Self { + self.revision = revision; + self + } + + /// Set message depth. + pub fn depth(mut self, depth: u16) -> Self { + self.message.depth = depth.into(); + self + } + + /// Set provided gas. + pub fn gas(mut self, gas: i64) -> Self { + self.message.gas = gas; + self + } + + /// Set static message flag. + pub fn set_static(mut self, is_static: bool) -> Self { + self.message.is_static = is_static; + self + } + + /// Set message destination. + pub fn destination(mut self, destination: impl Into
) -> Self { + self.message.destination = destination.into(); + self + } + + /// Set message sender. + pub fn sender(mut self, sender: impl Into
) -> Self { + self.message.sender = sender.into(); + self + } + + /// Set message sender. + pub fn value(mut self, value: impl Into) -> Self { + self.message.value = value.into(); + self + } + + /// Check how much gas will be used. Mutually exclusive with `EvmTester::gas_left`. + pub fn gas_used(mut self, expected_gas_used: i64) -> Self { + self.gas_check = Some(GasCheck::Used(expected_gas_used)); + self + } + + /// Check how much gas will be left after execution. Mutually exclusive with `EvmTester::gas_used`. + pub fn gas_left(mut self, expected_gas_left: i64) -> Self { + self.gas_check = Some(GasCheck::Left(expected_gas_left)); + self + } + + /// Set provided input data. + pub fn input(mut self, input: impl Into) -> Self { + self.message.input_data = input.into(); + self + } + + /// Check returned status. + pub fn status(mut self, expected_status_code: StatusCode) -> Self { + self.expected_status_codes = Some(vec![expected_status_code]); + self + } + + /// Check returned status to be one of these. + pub fn status_one_of(mut self, expected_status_code: [StatusCode; N]) -> Self { + self.expected_status_codes = Some(expected_status_code.to_vec()); + self + } + + /// Check output to be equal to provided integer. + pub fn output_value(mut self, expected_output_data: impl Into) -> Self { + let mut data = [0; 32]; + expected_output_data.into().to_big_endian(&mut data); + self.expected_output_data = Some(data.to_vec()); + self + } + + /// Check output data to be equal to provided byte string. + pub fn output_data(mut self, expected_output_data: impl Into>) -> Self { + self.expected_output_data = Some(expected_output_data.into()); + self + } + + /// Inspect output with provided function. + pub fn inspect_output(mut self, inspect_output_fn: impl Fn(&[u8]) + 'static) -> Self { + self.inspect_output_fn = Arc::new(inspect_output_fn); + self + } + + /// Inspect host with provided function. + pub fn inspect_host(mut self, f: impl Fn(&MockedHost, &Message) + 'static) -> Self { + self.inspect_host_fn = Arc::new(f); + self + } + + /// Inspect host and output with provided function. + pub fn inspect(mut self, f: impl Fn(&MockedHost, &Message, &[u8]) + 'static) -> Self { + self.inspect_fn = Arc::new(f); + self + } + + /// Execute provided code, run checks and return bytecode returned by EVM. + pub async fn check_and_get_result(mut self) -> Output { + println!("Executing code: {}", hex::encode(&self.code)); + let mut host = self.host; + for f in self.apply_host_fns { + match f { + ApplyHostFn::Sync(f) => { + (f)(&mut host, &self.message); + } + ApplyHostFn::Async(f) => { + let (h, m) = (f)(host, self.message).await; + host = h; + self.message = m; + } + } + } + let output = exec(&mut host, self.revision, self.message.clone(), self.code).await; + + if let Some(status_codes) = self.expected_status_codes { + if !status_codes.iter().any(|&s| s == output.status_code) { + panic!( + "Status code mismatch: {}, but must be one of {:?}", + output.status_code, status_codes + ); + } + } + + if let Some(gas_check) = self.gas_check { + match gas_check { + GasCheck::Used(used) => assert_eq!(self.message.gas - output.gas_left, used), + GasCheck::Left(left) => assert_eq!(output.gas_left, left), + } + } + + if let Some(expected_data) = &self.expected_output_data { + assert_eq!(&*output.output_data, expected_data); + } + + (self.inspect_output_fn)(&*output.output_data); + (self.inspect_host_fn)(&host, &self.message); + (self.inspect_fn)(&host, &self.message, &*output.output_data); + + output + } + + /// Execute provided code and run checks. + pub async fn check(self) { + self.check_and_get_result().await; + } +} diff --git a/tests/basefee.rs b/tests/basefee.rs new file mode 100644 index 0000000..7c07b6b --- /dev/null +++ b/tests/basefee.rs @@ -0,0 +1,35 @@ +use evmodin::{opcode::*, util::*, *}; + +#[tokio::test] +async fn basefee_pre_london() { + EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().opcode(OpCode::BASEFEE)) + .status(StatusCode::UndefinedInstruction) + .check() + .await +} + +#[tokio::test] +async fn basefee_nominal_case() { + // https://eips.ethereum.org/EIPS/eip-3198#nominal-case + let t = EvmTester::new() + .revision(Revision::London) + .apply_host_fn(|host, _| { + host.tx_context.block_base_fee = 7.into(); + }); + t.clone() + .code(Bytecode::new().opcode(OpCode::BASEFEE).opcode(OpCode::STOP)) + .status(StatusCode::Success) + .gas_used(2) + .check() + .await; + + t.clone() + .code(Bytecode::new().opcode(OpCode::BASEFEE).ret_top()) + .status(StatusCode::Success) + .gas_used(17) + .output_value(7) + .check() + .await +} diff --git a/tests/call.rs b/tests/call.rs new file mode 100644 index 0000000..8708f4c --- /dev/null +++ b/tests/call.rs @@ -0,0 +1,1248 @@ +use bytes::Bytes; +use core::iter::repeat_with; +use ethereum_types::{Address, H256, U256}; +use evmodin::{opcode::*, util::*, *}; +use hex_literal::hex; + +#[tokio::test] +async fn delegatecall() { + let mut value = H256::zero(); + value.0[17] = 0xfe; + + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("6001600003600052")) // m[0] = 0xffffff... + .append(hex!("600560046003600260016103e8f4")) // DELEGATECALL(1000, 0x01, ...) + .append(hex!("60086000f3")), + ) + .apply_host_fn(|host, _| { + host.call_result.output_data = (&hex!("0a0b0c") as &[u8]).into(); + host.call_result.gas_left = 1; + }) + .value(value.0) + .gas(1700) + .gas_used(1690) + .status(StatusCode::Success) + .output_data(hex!("ffffffff0a0b0cff")) + .inspect_host(move |host, _| { + let gas_left = 1700 - 736; + + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + let call_msg = r.calls.last().unwrap(); + assert_eq!(call_msg.gas, gas_left - gas_left / 64); + assert_eq!(call_msg.input_data.len(), 3); + assert_eq!(<[u8; 32]>::from(call_msg.value)[17], 0xfe); + }) + .check() + .await +} + +/// Checks if DELEGATECALL forwards the "static" flag. +#[tokio::test] +async fn delegatecall_static() { + EvmTester::new() + .set_static(true) + .code(Bytecode::new().append_bc(CallInstruction::delegatecall(0).gas(1))) + .status(StatusCode::Success) + .gas_used(719) + .inspect_host(|host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + let call_msg = r.calls.last().unwrap(); + assert_eq!(call_msg.gas, 1); + assert!(call_msg.is_static); + }) + .check() + .await +} + +#[tokio::test] +async fn delegatecall_oog_depth_limit() { + let t = EvmTester::new() + .revision(Revision::Homestead) + .depth(1024) + .code( + Bytecode::new() + .append_bc(CallInstruction::delegatecall(0).gas(16)) + .ret_top(), + ); + + t.clone() + .status(StatusCode::Success) + .gas_used(73) + .output_value(0) + .check() + .await; + + t.clone().gas(73).status(StatusCode::OutOfGas).check().await; +} + +#[tokio::test] +async fn create() { + let address = Address::zero(); + + EvmTester::new() + .apply_host_fn(move |host, _| { + host.accounts.entry(address).or_default().balance = 1.into(); + + host.call_result.output_data = (&hex!("0a0b0c") as &[u8]).into(); + host.call_result + .create_address + .get_or_insert_with(Address::zero) + .0[10] = 0xcc; + host.call_result.gas_left = 200000; + }) + .gas(300000) + .code(hex!("602060006001f0600155")) + .gas_used(115816) + .status(StatusCode::Success) + .inspect_host(move |host, _| { + let mut key = H256::zero(); + key.0[31] = 1; + assert_eq!(host.accounts[&address].storage[&key].value.0[22], 0xcc); + + let r = host.recorded.lock(); + assert_eq!(r.calls.len(), 1); + assert_eq!(r.calls.last().unwrap().input_data.len(), 0x20); + }) + .check() + .await +} + +#[tokio::test] +async fn create_gas() { + for rev in [Revision::Homestead, Revision::Tangerine] { + EvmTester::new() + .revision(rev) + .gas(50000) + .code(hex!("60008080f0")) + .status(StatusCode::Success) + .gas_used(if rev == Revision::Homestead { + 50000 + } else { + 49719 + }) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + assert_eq!(r.calls.len(), 1); + assert_eq!( + r.calls.last().unwrap().gas, + if rev == Revision::Homestead { + 17991 + } else { + 17710 + } + ); + }) + .check() + .await + } +} + +#[tokio::test] +async fn create2() { + let address = Address::zero(); + EvmTester::new() + .revision(Revision::Constantinople) + .apply_host_fn(move |host, _| { + host.accounts.entry(address).or_default().balance = 1.into(); + + host.call_result.output_data = (&hex!("0a0b0c") as &[u8]).into(); + host.call_result + .create_address + .get_or_insert_with(Address::zero) + .0[10] = 0xc2; + host.call_result.gas_left = 200000; + }) + .gas(300000) + .code(hex!("605a604160006001f5600155")) + .gas_used(115817) + .status(StatusCode::Success) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + + let call_msg = r.calls.last().unwrap(); + assert_eq!( + call_msg.kind, + CallKind::Create2 { + salt: H256(U256::from(0x5a).into()) + } + ); + assert_eq!(call_msg.gas, 263775); + + assert_eq!( + host.accounts[&address].storage[&H256(U256::from(1).into())] + .value + .0[22], + 0xc2 + ); + + assert_eq!(call_msg.input_data.len(), 0x41); + }) + .check() + .await +} + +#[tokio::test] +async fn create2_salt_cost() { + let t = EvmTester::new() + .revision(Revision::Constantinople) + .code(hex!("600060208180f5")); + + t.clone() + .gas(32021) + .status(StatusCode::Success) + .gas_left(0) + .inspect_host(|host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + assert_eq!( + r.calls.last().unwrap().kind, + CallKind::Create2 { salt: H256::zero() } + ); + assert_eq!(r.calls.last().unwrap().depth, 1); + }) + .check() + .await; + + t.clone() + .gas(32021 - 1) + .status(StatusCode::OutOfGas) + .gas_left(0) + .inspect_host(|host, _| { + // No another CREATE2. + assert_eq!(host.recorded.lock().calls.len(), 0) + }) + .check() + .await +} + +#[tokio::test] +async fn create_balance_too_low() { + for op in [OpCode::CREATE, OpCode::CREATE2] { + EvmTester::new() + .revision(Revision::Constantinople) + .apply_host_fn(|host, _| { + host.accounts.entry(Address::zero()).or_default().balance = 1.into(); + }) + .code( + Bytecode::new() + .pushv(2) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(op) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(0) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls, []); + }) + .check() + .await + } +} + +#[tokio::test] +async fn create_failure() { + for op in [OpCode::CREATE, OpCode::CREATE2] { + let mut create_address = Address::zero(); + create_address.0[19] = 0xce; + let t = EvmTester::new() + .apply_host_fn(move |host, _| { + host.call_result.create_address = Some(create_address); + }) + .revision(Revision::Constantinople) + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(op) + .ret_top(), + ); + + t.clone() + .apply_host_fn(|host, _| { + host.call_result.status_code = StatusCode::Success; + }) + .status(StatusCode::Success) + .output_data(H256::from(create_address).to_fixed_bytes()) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + assert_eq!( + r.calls.last().unwrap().kind, + if op == OpCode::CREATE { + CallKind::Create + } else { + CallKind::Create2 { salt: H256::zero() } + } + ); + }) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.call_result.status_code = StatusCode::Revert; + }) + .status(StatusCode::Success) + .output_value(0) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + assert_eq!( + r.calls.last().unwrap().kind, + if op == OpCode::CREATE { + CallKind::Create + } else { + CallKind::Create2 { salt: H256::zero() } + } + ); + }) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.call_result.status_code = StatusCode::Failure; + }) + .status(StatusCode::Success) + .output_value(0) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + assert_eq!( + r.calls.last().unwrap().kind, + if op == OpCode::CREATE { + CallKind::Create + } else { + CallKind::Create2 { salt: H256::zero() } + } + ); + }) + .check() + .await; + } +} + +#[tokio::test] +async fn call_failing_with_value() { + for op in [OpCode::CALL, OpCode::CALLCODE] { + let t = EvmTester::new() + .apply_host_fn(|host, _| { + host.accounts + .entry(hex!("00000000000000000000000000000000000000aa").into()) + .or_default(); + }) + .code( + Bytecode::new() + .pushv(0xff) + .pushv(0) + .opcode(OpCode::DUP2) + .opcode(OpCode::DUP2) + .pushv(1) + .pushv(0xaa) + .pushv(0x8000) + .opcode(op) + .opcode(OpCode::POP), + ); + + // Fails on balance check. + t.clone() + .gas(12000) + .status(StatusCode::Success) + .gas_used(7447) + .inspect_host(|host, _| { + // There was no call(). + assert_eq!(host.recorded.lock().calls, []); + }) + .check() + .await; + + // Fails on value transfer additional cost - minimum gas limit that triggers this condition. + t.clone() + .gas(747) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + // There was no call(). + assert_eq!(host.recorded.lock().calls, []); + }) + .check() + .await; + + // Fails on value transfer additional cost - maximum gas limit that triggers this condition. + t.clone() + .gas(744 + 9000) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + // There was no call(). + assert_eq!(host.recorded.lock().calls, []); + }) + .check() + .await; + } +} + +#[tokio::test] +async fn call_with_value() { + let call_sender = hex!("5e4d00000000000000000000000000000000d4e5").into(); + let call_dst = hex!("00000000000000000000000000000000000000aa").into(); + + EvmTester::new() + .code(hex!("60ff600060ff6000600160aa618000f150")) + .destination(call_sender) + .apply_host_fn(move |host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + host.accounts.entry(call_dst).or_default(); + host.call_result.gas_left = 1.into(); + }) + .gas(40000) + .gas_used(7447 + 32082) + .status(StatusCode::Success) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::Call, + is_static: false, + depth: 1, + gas: 32083, + destination: call_dst, + sender: call_sender, + input_data: vec![0; 255].into(), + value: 1.into(), + }] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn call_with_value_depth_limit() { + let mut call_dst = Address::zero(); + call_dst.0[19] = 0xaa; + + EvmTester::new() + .depth(1024) + .apply_host_fn(move |host, _| { + host.accounts.entry(call_dst).or_default(); + }) + .code(hex!("60ff600060ff6000600160aa618000f150")) + .gas_used(7447) + .status(StatusCode::Success) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls, []); + }) + .check() + .await +} + +#[tokio::test] +async fn call_depth_limit() { + for op in [ + OpCode::CALL, + OpCode::CALLCODE, + OpCode::DELEGATECALL, + OpCode::STATICCALL, + OpCode::CREATE, + OpCode::CREATE2, + ] { + EvmTester::new() + .revision(Revision::Constantinople) + .depth(1024) + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(op) + .ret_top() + .opcode(OpCode::INVALID), + ) + .status(StatusCode::Success) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls, []); + }) + .output_value(0) + .check() + .await + } +} + +#[tokio::test] +async fn call_output() { + for op in [ + OpCode::CALL, + OpCode::CALLCODE, + OpCode::DELEGATECALL, + OpCode::STATICCALL, + ] { + let call_output = Bytes::from_static(&hex!("0a0b")); + + let t = EvmTester::new() + .apply_host_fn({ + let call_output = call_output.clone(); + move |host, _| { + host.accounts.entry(Address::zero()).or_default().balance = 1.into(); + host.call_result.output_data = call_output.clone(); + } + }) + .inspect_host(move |host, _| { + assert_eq!(host.call_result.output_data, call_output); + assert!(core::ptr::eq( + host.call_result.output_data.as_ptr(), + call_output.as_ptr() + )); + }); + + let code_prefix_output_1 = Bytecode::new() + .pushv(1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .pushb(hex!("7fffffffffffffff")); + let code_prefix_output_0 = Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .pushb(hex!("7fffffffffffffff")); + let code_suffix = Bytecode::new().ret(0, 3); + + t.clone() + .code( + Bytecode::new() + .append_bc(code_prefix_output_1) + .opcode(op) + .append_bc(code_suffix.clone()), + ) + .status(StatusCode::Success) + .output_data(hex!("000a00")) + .check() + .await; + + t.clone() + .code( + Bytecode::new() + .append_bc(code_prefix_output_0) + .opcode(op) + .append_bc(code_suffix.clone()), + ) + .status(StatusCode::Success) + .output_data(hex!("000000")) + .check() + .await; + } +} + +#[tokio::test] +async fn call_high_gas() { + for call_opcode in [OpCode::CALL, OpCode::CALLCODE, OpCode::DELEGATECALL] { + let mut call_dst = Address::zero(); + call_dst.0[19] = 0xaa; + + EvmTester::new() + .revision(Revision::Homestead) + .apply_host_fn(move |host, _| { + host.accounts.entry(call_dst).or_default(); + }) + .gas(5000) + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0xaa) + .pushv(0x134c) + .opcode(call_opcode), + ) + .status(StatusCode::OutOfGas) + .check() + .await + } +} + +#[tokio::test] +async fn call_value_zero_to_nonexistent_account() { + let call_gas = 6000; + + let gas_left = 1000; + + EvmTester::new() + .apply_host_fn(move |host, _| { + host.call_result.gas_left = gas_left; + }) + .code( + Bytecode::new() + .pushv(0x40) + .pushv(0) + .pushv(0x40) + .pushv(0) + .pushv(0) + .pushv(0xaa) + .pushv(call_gas) + .opcode(OpCode::CALL) + .opcode(OpCode::POP), + ) + .gas(9000) + .gas_used(729 + (call_gas - gas_left)) + .status(StatusCode::Success) + .inspect_host(|host, _| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::Call, + is_static: false, + depth: 1, + gas: 6000, + destination: hex!("00000000000000000000000000000000000000aa").into(), + sender: Address::zero(), + input_data: vec![0; 64].into(), + value: 0.into(), + }] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn call_new_account_creation_cost() { + let call_dst: Address = hex!("00000000000000000000000000000000000000ad").into(); + let destination: Address = hex!("0000000000000000000000000000000000000003").into(); + + let t = EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .opcode(OpCode::CALLDATALOAD) + .pushb(call_dst.0) + .pushv(0) + .opcode(OpCode::CALL) + .ret_top(), + ) + .destination(destination); + + t.clone() + .revision(Revision::Tangerine) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 0.into(); + }) + .input(&hex!("00") as &[u8]) + .status(StatusCode::Success) + .gas_used(25000 + 739) + .output_value(1) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + call_dst, // Account exist? + call_dst, // Call. + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + }) + .input(&hex!("0000000000000000000000000000000000000000000000000000000000000001") as &[u8]) + .status(StatusCode::Success) + .gas_used(25000 + 9000 + 739) + .output_value(1) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::Call, + is_static: false, + depth: 1, + gas: 2300, + destination: call_dst, + sender: destination, + input_data: vec![].into(), + value: 1.into(), + }] + ); + assert_eq!( + host.recorded.lock().account_accesses, + [ + call_dst, // Account exist? + msg.destination, // Balance. + call_dst // Call. + ] + ) + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 0.into(); + }) + .input(&hex!("00") as &[u8]) + .status(StatusCode::Success) + .gas_used(739) + .output_value(1) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::Call, + is_static: false, + depth: 1, + gas: 0, + destination: call_dst, + sender: destination, + input_data: vec![].into(), + value: 0.into(), + }] + ); + assert_eq!( + host.recorded.lock().account_accesses, + [ + call_dst // Call. + ] + ) + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + }) + .input(&hex!("0000000000000000000000000000000000000000000000000000000000000001") as &[u8]) + .status(StatusCode::Success) + .gas_used(25000 + 9000 + 739) + .output_value(1) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::Call, + is_static: false, + depth: 1, + gas: 2300, + destination: call_dst, + sender: destination, + input_data: vec![].into(), + value: 1.into(), + }] + ); + assert_eq!( + host.recorded.lock().account_accesses, + [ + call_dst, // Account exist? + msg.destination, // Balance. + call_dst // Call. + ] + ) + }) + .check() + .await +} + +#[tokio::test] +async fn callcode_new_account_create() { + let code = hex!("60008080806001600061c350f250"); + let call_sender = hex!("5e4d00000000000000000000000000000000d4e5").into(); + + EvmTester::new() + .destination(call_sender) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + host.call_result.gas_left = 1; + }) + .gas(100000) + .code(code) + .gas_used(59722) + .status(StatusCode::Success) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().calls, + [Message { + kind: CallKind::CallCode, + is_static: false, + depth: 1, + gas: 52300, + destination: Address::zero(), + sender: call_sender, + input_data: vec![].into(), + value: 1.into(), + }] + ); + }) + .check() + .await +} + +/// Performs a CALL then OOG in the same code block. +#[tokio::test] +async fn call_then_oog() { + let call_dst = 0xaa; + + let mut code = Bytecode::new().append_bc( + CallInstruction::call(call_dst) + .gas(254) + .value(0) + .input(0, 0x40) + .output(0, 0x40), + ); + + for _ in 0..4 { + code = code.opcode(OpCode::DUP1).opcode(OpCode::ADD); + } + + code = code.opcode(OpCode::POP); + + EvmTester::new() + .apply_host_fn(move |host, _| { + let mut address = Address::zero(); + address.0[19] = call_dst; + host.accounts.entry(address).or_default(); + + host.call_result.status_code = StatusCode::Failure; + host.call_result.gas_left = 0; + }) + .code(code) + .gas(1000) + .gas_used(1000) + .gas_left(0) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls.len(), 1); + assert_eq!(host.recorded.lock().calls[0].gas, 254); + }) + .check() + .await +} + +/// Performs a CALLCODE then OOG in the same code block. +#[tokio::test] +async fn callcode_then_oog() { + let call_dst = 0xaa; + + let mut code = Bytecode::new().append_bc( + CallInstruction::callcode(call_dst) + .gas(100) + .value(0) + .input(0, 3) + .output(3, 9), + ); + + for _ in 0..4 { + code = code.opcode(OpCode::DUP1).opcode(OpCode::ADD); + } + + code = code.opcode(OpCode::POP); + + EvmTester::new() + .apply_host_fn(move |host, _| { + let mut address = Address::zero(); + address.0[19] = call_dst; + host.accounts.entry(address).or_default(); + + host.call_result.status_code = StatusCode::Failure; + host.call_result.gas_left = 0; + }) + .code(code) + .gas(825) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls.len(), 1); + assert_eq!(host.recorded.lock().calls[0].gas, 100); + }) + .check() + .await +} + +/// Performs a CALL then OOG in the same code block. +#[tokio::test] +async fn delegatecall_then_oog() { + let call_dst = 0xaa; + + let mut code = Bytecode::new().append_bc( + CallInstruction::delegatecall(call_dst) + .gas(254) + .input(0, 64) + .output(0, 64), + ); + + for _ in 0..4 { + code = code.opcode(OpCode::DUP1).opcode(OpCode::ADD); + } + + code = code.opcode(OpCode::POP); + + EvmTester::new() + .apply_host_fn(move |host, _| { + let mut address = Address::zero(); + address.0[19] = call_dst; + host.accounts.entry(address).or_default(); + + host.call_result.status_code = StatusCode::Failure; + host.call_result.gas_left = 0; + }) + .code(code) + .gas(1000) + .gas_used(1000) + .gas_left(0) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls.len(), 1); + assert_eq!(host.recorded.lock().calls[0].gas, 254); + }) + .check() + .await +} + +/// Performs a STATICCALL then OOG in the same code block. +#[tokio::test] +async fn staticcall_then_oog() { + let call_dst = 0xaa; + + let mut code = Bytecode::new().append_bc( + CallInstruction::staticcall(call_dst) + .gas(254) + .input(0, 0x40) + .output(0, 0x40), + ); + + for _ in 0..4 { + code = code.opcode(OpCode::DUP1).opcode(OpCode::ADD); + } + + code = code.opcode(OpCode::POP); + + EvmTester::new() + .apply_host_fn(move |host, _| { + let mut address = Address::zero(); + address.0[19] = call_dst; + host.accounts.entry(address).or_default(); + + host.call_result.status_code = StatusCode::Failure; + host.call_result.gas_left = 0; + }) + .code(code) + .gas(1000) + .status(StatusCode::OutOfGas) + .gas_used(1000) + .gas_left(0) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().calls.len(), 1); + assert_eq!(host.recorded.lock().calls[0].gas, 254); + }) + .check() + .await +} + +#[tokio::test] +async fn staticcall_input() { + EvmTester::new() + .code( + Bytecode::new() + .mstore_value(3, 0x010203) + .append_bc(CallInstruction::staticcall(0).gas(0xee).input(32, 3)), + ) + .inspect_host(|host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.calls.len(), 1); + assert_eq!(r.calls[0].gas, 0xee); + assert_eq!(r.calls[0].input_data[..], hex!("010203")); + }) + .check() + .await +} + +#[tokio::test] +async fn call_with_value_low_gas() { + for op in [OpCode::CALL, OpCode::CALLCODE] { + EvmTester::new() + .apply_host_fn(|host, _| { + // Create the call destination account. + host.accounts.entry(Address::zero()).or_default(); + }) + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(op) + .opcode(OpCode::POP), + ) + .gas(9721) + .status(StatusCode::Success) + .gas_left(2300 - 2) + .check() + .await + } +} + +#[tokio::test] +async fn call_oog_after_balance_check() { + // Create the call destination account. + for op in [OpCode::CALL, OpCode::CALLCODE] { + EvmTester::new() + .apply_host_fn(|host, _| { + // Create the call destination account. + host.accounts.entry(Address::zero()).or_default(); + }) + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(op) + .opcode(OpCode::SELFDESTRUCT), + ) + .gas(12420) + .status(StatusCode::OutOfGas) + .check() + .await + } +} + +#[tokio::test] +async fn call_oog_after_depth_check() { + // Create the call destination account. + let t = EvmTester::new() + .apply_host_fn(|host, _| { + host.accounts.entry(Address::zero()).or_default(); + }) + .depth(1024); + + for op in [OpCode::CALL, OpCode::CALLCODE] { + t.clone() + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(op) + .opcode(OpCode::SELFDESTRUCT), + ) + .gas(12420) + .status(StatusCode::OutOfGas) + .check() + .await + } + + let t = t.clone().revision(Revision::Tangerine).code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .opcode(OpCode::CALL) + .opcode(OpCode::SELFDESTRUCT), + ); + + t.clone() + .gas(721) + .status(StatusCode::OutOfGas) + .check() + .await; + + t.clone() + .gas(721 + 5000 - 1) + .status(StatusCode::OutOfGas) + .check() + .await; +} + +#[tokio::test] +async fn create_oog_after() { + for op in [OpCode::CREATE, OpCode::CREATE2] { + EvmTester::new() + .revision(Revision::Constantinople) + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .pushv(0) + .pushv(0) + .opcode(op) + .opcode(OpCode::SELFDESTRUCT), + ) + .gas(39000) + .status(StatusCode::OutOfGas) + .check() + .await + } +} + +#[tokio::test] +async fn returndatasize_before_call() { + EvmTester::new() + .code(hex!("3d60005360016000f3")) + .gas_used(17) + .output_data([0]) + .check() + .await +} + +#[tokio::test] +async fn returndatasize() { + let call_res_output_len = 13; + + let t = EvmTester::new() + .apply_host_fn(move |host, _| { + host.call_result.output_data = repeat_with(rand::random) + .take(call_res_output_len as usize) + .collect::>() + .into() + }) + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DELEGATECALL) + .opcode(OpCode::RETURNDATASIZE) + .mstore8(0) + .pushv(1) + .pushv(0) + .opcode(OpCode::RETURN), + ); + + t.clone() + .gas_used(735) + .output_data([call_res_output_len]) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.call_result.output_data = vec![0; 1].into(); + host.call_result.status_code = StatusCode::Failure; + }) + .gas_used(735) + .output_data([1]) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.call_result.output_data = Bytes::new(); + host.call_result.status_code = StatusCode::InternalError; + }) + .gas_used(735) + .output_data([0]) + .check() + .await; +} + +#[tokio::test] +async fn returndatacopy() { + let call_output = hex!("0102030405060700000000000000000000000000000000000000000000000000"); + + EvmTester::new() + .apply_host_fn(move |host, _| { + host.call_result.output_data = Bytes::from(call_output.to_vec()); + }) + .code(hex!("600080808060aa60fff4506020600060003e60206000f3")) + .gas_used(999) + .output_data(call_output) + .check() + .await +} + +#[tokio::test] +async fn returndatacopy_empty() { + EvmTester::new() + .code(hex!("600080808060aa60fff4600080803e60016000f3")) + .gas_used(994) + .output_data([0]) + .check() + .await +} + +#[tokio::test] +async fn returndatacopy_cost() { + let t = EvmTester::new() + .code(hex!("60008080808080fa6001600060003e")) + .apply_host_fn(|host, _| { + host.call_result.output_data = vec![0].into(); + }); + t.clone().gas(736).status(StatusCode::Success).check().await; + t.clone() + .gas(735) + .status(StatusCode::OutOfGas) + .check() + .await; +} + +#[tokio::test] +async fn returndatacopy_outofrange() { + for code in [ + hex!("60008080808080fa6002600060003e"), + hex!("60008080808080fa6001600160003e"), + hex!("60008080808080fa6000600260003e"), + ] { + EvmTester::new() + .apply_host_fn(|host, _| { + host.call_result.output_data = vec![0].into(); + }) + .code(code) + .gas(735) + .status(StatusCode::InvalidMemoryAccess) + .check() + .await + } +} diff --git a/tests/eip2929.rs b/tests/eip2929.rs new file mode 100644 index 0000000..4a600ff --- /dev/null +++ b/tests/eip2929.rs @@ -0,0 +1,419 @@ +use ethereum_types::{H256, U256}; +use evmodin::{host::*, opcode::*, util::*, *}; +use hex_literal::hex; + +#[tokio::test] +async fn eip2929_case1() { + // https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a#case-1 + EvmTester::new() + .revision(Revision::Berlin) + .sender(hex!("0000000000000000000000000000000000000000")) + .destination(hex!("000000000000000000000000636F6E7472616374")) + .gas(13653) + .code(hex!("60013f5060023b506003315060f13f5060f23b5060f3315060f23f5060f33b5060f1315032315030315000")) + .status(StatusCode::Success) + .gas_used(8653) + .output_data([]) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + msg.sender, + msg.destination, + hex!("0000000000000000000000000000000000000001").into(), + hex!("0000000000000000000000000000000000000001").into(), + hex!("0000000000000000000000000000000000000002").into(), + hex!("0000000000000000000000000000000000000002").into(), + hex!("0000000000000000000000000000000000000003").into(), + hex!("0000000000000000000000000000000000000003").into(), + hex!("00000000000000000000000000000000000000f1").into(), + hex!("00000000000000000000000000000000000000f1").into(), + hex!("00000000000000000000000000000000000000f2").into(), + hex!("00000000000000000000000000000000000000f2").into(), + hex!("00000000000000000000000000000000000000f3").into(), + hex!("00000000000000000000000000000000000000f3").into(), + hex!("00000000000000000000000000000000000000f2").into(), + hex!("00000000000000000000000000000000000000f2").into(), + hex!("00000000000000000000000000000000000000f3").into(), + hex!("00000000000000000000000000000000000000f3").into(), + hex!("00000000000000000000000000000000000000f1").into(), + hex!("00000000000000000000000000000000000000f1").into(), + hex!("0000000000000000000000000000000000000000").into(), + hex!("0000000000000000000000000000000000000000").into(), + msg.destination, + msg.destination, + ] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn eip2929_case2() { + // https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a#case-2 + EvmTester::new() + .revision(Revision::Berlin) + .sender(hex!("0000000000000000000000000000000000000000")) + .destination(hex!("000000000000000000000000636F6E7472616374")) + .code(hex!( + "60006000600060ff3c60006000600060ff3c600060006000303c00" + )) + .status(StatusCode::Success) + .gas_used(2835) + .output_data([]) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + msg.sender, + msg.destination, + hex!("00000000000000000000000000000000000000ff").into(), + hex!("00000000000000000000000000000000000000ff").into(), + msg.destination, + ] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn eip2929_case3() { + // https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a#case-3 + EvmTester::new() + .revision(Revision::Berlin) + .sender(hex!("0000000000000000000000000000000000000000")) + .destination(hex!("000000000000000000000000636F6E7472616374")) + .code(hex!("60015450601160015560116002556011600255600254600154")) + .status(StatusCode::Success) + .gas_used(44529) + .output_data([]) + .check() + .await +} + +#[tokio::test] +async fn eip2929_case4() { + // https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a#case-4 + EvmTester::new() + .revision(Revision::Berlin) + .sender(hex!("0000000000000000000000000000000000000000")) + .destination(hex!("000000000000000000000000636F6E7472616374")) + .code(hex!( + "60008080808060046000f15060008080808060ff6000f15060008080808060ff6000fa50" + )) + .status(StatusCode::Success) + .gas_used(2869) + .output_data([]) + .check() + .await +} + +#[tokio::test] +async fn eip2929_op_oog() { + for (op, gas) in [ + (OpCode::BALANCE, 2603), + (OpCode::EXTCODESIZE, 2603), + (OpCode::EXTCODEHASH, 2603), + ] { + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().pushv(0x0a).opcode(op)); + + t.clone() + .gas(gas) + .status(StatusCode::Success) + .gas_used(gas) + .check() + .await; + + t.clone() + .gas(gas - 1) + .status(StatusCode::OutOfGas) + .gas_used(gas - 1) + .check() + .await; + } +} + +#[tokio::test] +async fn eip2929_extcodecopy_oog() { + let t = EvmTester::new().revision(Revision::Berlin).code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .pushv(0xa) + .opcode(OpCode::EXTCODECOPY), + ); + + t.clone() + .gas(2612) + .status(StatusCode::Success) + .gas_used(2612) + .check() + .await; + + t.clone() + .gas(2611) + .status(StatusCode::OutOfGas) + .gas_used(2611) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_sload_cold() { + let key = H256(U256::one().into()); + + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().pushv(1).opcode(OpCode::SLOAD)) + .apply_host_fn(move |host, msg| { + let mut st = host + .accounts + .entry(msg.destination) + .or_default() + .storage + .entry(key) + .or_default(); + st.value = H256(U256::from(2).into()); + assert_eq!(st.access_status, AccessStatus::Cold); + }); + + t.clone() + .gas(2103) + .status(StatusCode::Success) + .gas_used(2103) + .inspect_host(move |host, msg| { + assert_eq!( + host.accounts[&msg.destination].storage[&key].access_status, + AccessStatus::Warm + ); + }) + .check() + .await; + + t.clone() + .gas(2102) + .status(StatusCode::OutOfGas) + .gas_used(2102) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_sload_two_slots() { + let key0 = H256(U256::from(0).into()); + let key1 = H256(U256::from(1).into()); + + EvmTester::new() + .revision(Revision::Berlin) + .code( + Bytecode::new() + .pushv(key0.0) + .opcode(OpCode::SLOAD) + .opcode(OpCode::POP) + .pushv(key1.0) + .opcode(OpCode::SLOAD) + .opcode(OpCode::POP), + ) + .gas(30000) + .status(StatusCode::Success) + .gas_used(4210) + .inspect_host(move |host, msg| { + assert_eq!( + host.accounts[&msg.destination].storage[&key0].access_status, + AccessStatus::Warm + ); + assert_eq!( + host.accounts[&msg.destination].storage[&key1].access_status, + AccessStatus::Warm + ); + }) + .check() + .await +} + +#[tokio::test] +async fn eip2929_sload_warm() { + let key = H256(U256::from(1).into()); + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().pushv(1).opcode(OpCode::SLOAD)) + .apply_host_fn(move |host, msg| { + let st = host + .accounts + .entry(msg.destination) + .or_default() + .storage + .entry(key) + .or_default(); + st.value = H256(U256::from(2).into()); + st.access_status = AccessStatus::Warm; + }); + + t.clone() + .gas(103) + .status(StatusCode::Success) + .gas_used(103) + .inspect_host(move |host, msg| { + assert_eq!( + host.accounts[&msg.destination].storage[&key].access_status, + AccessStatus::Warm + ); + }) + .check() + .await; + + t.clone() + .gas(102) + .status(StatusCode::OutOfGas) + .gas_used(102) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_sstore_modify_cold() { + let key = H256(U256::from(1).into()); + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().sstore(1, 3)) + .apply_host_fn(move |host, msg| { + host.accounts + .entry(msg.destination) + .or_default() + .storage + .entry(key) + .or_default() + .value = H256(U256::from(2).into()); + }); + + t.clone() + .gas(5006) + .status(StatusCode::Success) + .gas_used(5006) + .inspect_host(move |host, msg| { + assert_eq!( + host.accounts[&msg.destination].storage[&key].value, + H256(U256::from(3).into()) + ); + assert_eq!( + host.accounts[&msg.destination].storage[&key].access_status, + AccessStatus::Warm + ); + }) + .check() + .await; + + t.clone() + .gas(5005) + .status(StatusCode::OutOfGas) + .gas_used(5005) + .inspect_host(move |host, msg| { + // The storage will be modified anyway, because the cost is checked after. + assert_eq!( + host.accounts[&msg.destination].storage[&key].value, + H256(U256::from(3).into()) + ); + assert_eq!( + host.accounts[&msg.destination].storage[&key].access_status, + AccessStatus::Warm + ); + }) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_selfdestruct_cold_beneficiary() { + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().pushv(0xbe).opcode(OpCode::SELFDESTRUCT)); + + t.clone() + .gas(7603) + .status(StatusCode::Success) + .gas_used(7603) + .check() + .await; + + t.clone() + .gas(7602) + .status(StatusCode::OutOfGas) + .gas_used(7602) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_selfdestruct_warm_beneficiary() { + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(Bytecode::new().pushv(0xbe).opcode(OpCode::SELFDESTRUCT)) + .apply_host_fn_async(|mut host, msg| async { + host.access_account(hex!("00000000000000000000000000000000000000be").into()) + .await + .unwrap(); + + (host, msg) + }); + + t.clone() + .gas(5003) + .status(StatusCode::Success) + .gas_used(5003) + .check() + .await; + + t.clone() + .gas(5002) + .status(StatusCode::OutOfGas) + .gas_used(5002) + .check() + .await; +} + +#[tokio::test] +async fn eip2929_delegatecall_cold() { + let t = EvmTester::new() + .revision(Revision::Berlin) + .code(CallInstruction::delegatecall(0xde)); + + t.clone() + .gas(2618) + .status(StatusCode::Success) + .gas_used(2618) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + msg.sender, + msg.destination, + hex!("00000000000000000000000000000000000000de").into(), + hex!("00000000000000000000000000000000000000de").into(), + ] + ); + }) + .check() + .await; + + t.clone() + .gas(2617) + .status(StatusCode::OutOfGas) + .gas_used(2617) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + msg.sender, + msg.destination, + hex!("00000000000000000000000000000000000000de").into(), + ] + ); + }) + .check() + .await; +} diff --git a/tests/execute.rs b/tests/execute.rs new file mode 100644 index 0000000..bf9989b --- /dev/null +++ b/tests/execute.rs @@ -0,0 +1,1423 @@ +#![allow(clippy::needless_range_loop)] + +use core::iter::repeat; +use ethereum_types::U256; +use evmodin::{opcode::*, util::*, *}; +use hex_literal::hex; +use std::cmp::max; + +#[tokio::test] +async fn empty_code() { + for gas in [0, 1] { + EvmTester::new() + .code(hex!("")) + .gas(gas) + .gas_used(0) + .status(StatusCode::Success) + .check() + .await + } +} + +#[tokio::test] +async fn push_and_pop() { + EvmTester::new() + .code( + Bytecode::new() + .pushb(hex!("0102")) + .opcode(OpCode::POP) + .pushb(hex!("010203040506070809")) + .opcode(OpCode::POP), + ) + .gas(11) + .gas_used(10) + .status(StatusCode::Success) + .check() + .await +} + +#[tokio::test] +async fn stack_underflow() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .opcode(OpCode::POP) + .pushv(1) + .opcode(OpCode::POP) + .opcode(OpCode::POP), + ) + .gas(13) + .status(StatusCode::StackUnderflow) + .check() + .await; + + EvmTester::new() + .code(Bytecode::new().opcode(OpCode::NOT)) + .status(StatusCode::StackUnderflow) + .check() + .await; +} + +#[tokio::test] +async fn add() { + EvmTester::new() + .code(hex!("6007600d0160005260206000f3")) + .gas(25) + .gas_used(24) + .status(StatusCode::Success) + .output_value(20) + .check() + .await +} + +#[tokio::test] +async fn dup() { + // 0 7 3 5 + // 0 7 3 5 3 5 + // 0 7 3 5 3 5 5 7 + // 0 7 3 5 20 + // 0 7 3 5 (20 0) + // 0 7 3 5 3 0 + EvmTester::new() + .code(hex!("6000600760036005818180850101018452602084f3")) + .gas(48) + .status(StatusCode::Success) + .output_value(20) + .check() + .await +} + +#[tokio::test] +async fn dup_all_1() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .append(hex!("808182838485868788898a8b8c8d8e8f")) + .append(hex!("01010101010101010101010101010101")) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(17) + .check() + .await +} + +#[tokio::test] +async fn dup_stack_overflow() { + let b = Bytecode::new() + .pushv(1) + .append(hex!("808182838485868788898a8b8c8d8e8f")) + .append(repeat(0x8f).take(1024 - 17)); + + EvmTester::new() + .code(b.clone()) + .status(StatusCode::Success) + .check() + .await; + + EvmTester::new() + .code(b.append([0x8f])) + .status(StatusCode::StackOverflow) + .check() + .await; +} + +#[tokio::test] +async fn dup_stack_underflow() { + for i in 0..16 { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .repeat(i) + .opcode(OpCode(OpCode::DUP1.0 + i as u8)), + ) + .status(StatusCode::StackUnderflow) + .check() + .await + } +} + +#[tokio::test] +async fn sub_and_swap() { + EvmTester::new() + .code(hex!("600180810380829052602090f3")) + .gas(33) + .status(StatusCode::Success) + .gas_left(0) + .output_value(1) + .check() + .await +} + +#[tokio::test] +async fn memory_and_not() { + EvmTester::new() + .code(hex!("600060018019815381518252800190f3")) + .gas(42) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!("00fe")) + .check() + .await +} + +#[tokio::test] +async fn msize() { + EvmTester::new() + .code(hex!("60aa6022535960005360016000f3")) + .gas(29) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!("40")) + .check() + .await +} + +#[tokio::test] +async fn gas() { + EvmTester::new() + .code(hex!("5a5a5a010160005360016000f3")) + .gas(40) + .status(StatusCode::Success) + .gas_left(13) + .output_data([38 + 36 + 34]) + .check() + .await +} + +#[tokio::test] +async fn arith() { + // x = (0 - 1) * 3 + // y = 17 s/ x + // z = 17 s% x + // a = 17 * x + z + // iszero + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("60116001600003600302")) // 17 -3 + .append(hex!("808205")) // 17 -3 -5 + .append(hex!("818307")) // 17 -3 -5 2 + .append(hex!("910201")) // 17 17 + .append(hex!("0315")) // 1 + .append(hex!("60005360016000f3")), + ) + .gas(100) + .status(StatusCode::Success) + .gas_left(26) + .output_data([1]) + .check() + .await +} + +#[tokio::test] +async fn comparison() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("60006001808203808001")) // 0 1 -1 -2 + .append(hex!("828210600053")) // m[0] = -1 < 1 + .append(hex!("828211600153")) // m[1] = -1 > 1 + .append(hex!("828212600253")) // m[2] = -1 s< 1 + .append(hex!("828213600353")) // m[3] = -1 s> 1 + .append(hex!("828214600453")) // m[4] = -1 == 1 + .append(hex!("818112600553")) // m[5] = -2 s< -1 + .append(hex!("818113600653")) // m[6] = -2 s> -1 + .append(hex!("60076000f3")), + ) + .status(StatusCode::Success) + .gas_used(138) + .output_data(hex!("00010100000100")) + .check() + .await +} + +#[allow(clippy::identity_op)] +#[tokio::test] +async fn bitwise() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("60aa60ff")) // aa ff + .append(hex!("818116600053")) // m[0] = aa & ff + .append(hex!("818117600153")) // m[1] = aa | ff + .append(hex!("818118600253")) // m[2] = aa ^ ff + .append(hex!("60036000f3")), + ) + .gas(60) + .gas_left(0) + .output_data([0xaa & 0xff, 0xaa | 0xff, 0xaa ^ 0xff]) + .check() + .await +} + +#[tokio::test] +async fn jump() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("60be600053")) // m[0] = be + .append(hex!("60fa")) // fa + .append(hex!("60055801")) // PC + 5 + .append(hex!("56")) // JUMP + .append(hex!("5050")) // POP x2 + .append(hex!("5b")) // JUMPDEST + .append(hex!("600153")) // m[1] = fa + .append(hex!("60026000f3")), // RETURN(0,2) + ) + .gas(44) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!("befa")) + .check() + .await +} + +#[tokio::test] +async fn jumpi() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("5a600557")) // GAS 5 JUMPI + .append(hex!("00")) // STOP + .append(hex!("5b60016000f3")), // JUMPDEST RETURN(0,1) + ) + .gas(25) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!("00")) + .check() + .await +} + +#[tokio::test] +async fn jumpi_else() { + EvmTester::new() + .code( + Bytecode::new() + .opcode(OpCode::COINBASE) + .opcode(OpCode::DUP1) + .opcode(OpCode::JUMPI), + ) + .gas(16) + .status(StatusCode::Success) + .gas_used(15) + .output_data(hex!("")) + .check() + .await +} + +#[tokio::test] +async fn jumpi_at_the_end() { + EvmTester::new() + .code(hex!("5b6001600057")) + .gas(1000) + .status(StatusCode::OutOfGas) + .gas_used(1000) + .check() + .await +} + +#[tokio::test] +async fn bad_jumpdest() { + for opcode in [OpCode::JUMP, OpCode::JUMPI] { + for hex in [hex!("4345"), hex!("4342")] { + EvmTester::new() + .code(Bytecode::new().append(hex).opcode(opcode)) + .apply_host_fn(|host, _| { + host.tx_context.block_number = 1; + host.tx_context.block_gas_limit = 0; + host.tx_context.block_timestamp = 0x80000000; + }) + .status(StatusCode::BadJumpDestination) + .gas_left(0) + .check() + .await; + } + } +} + +#[tokio::test] +async fn jump_to_block_beginning() { + EvmTester::new() + .code(Bytecode::new().jumpi(U256::zero(), OpCode::MSIZE).jump(4)) + .status(StatusCode::BadJumpDestination) + .check() + .await +} + +#[tokio::test] +async fn jumpi_stack() { + for input in [&hex!("") as &[u8], &hex!("ee") as &[u8]] { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0xde) + .jumpi(U256::from(6), OpCode::CALLDATASIZE) + .opcode(OpCode::JUMPDEST) + .ret_top(), + ) + .input(input) + .output_value(0xde) + .check() + .await + } +} + +#[tokio::test] +async fn jump_over_jumpdest() { + // The code contains 2 consecutive JUMPDESTs. The JUMP at the beginning lands on the second one. + EvmTester::new() + .code( + Bytecode::new() + .pushv(4) + .opcode(OpCode::JUMP) + .opcode(OpCode::JUMPDEST) + .opcode(OpCode::JUMPDEST), + ) + .status(StatusCode::Success) + .gas_used(3 + 8 + 1) + .check() + .await +} + +#[tokio::test] +async fn pc_sum() { + EvmTester::new() + .code( + Bytecode::new() + .opcode(OpCode::PC) + .opcode(OpCode::PC) + .opcode(OpCode::PC) + .opcode(OpCode::PC) + .opcode(OpCode::ADD) + .opcode(OpCode::ADD) + .opcode(OpCode::ADD) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(6) + .check() + .await +} + +#[tokio::test] +async fn pc_after_jump_1() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(3) + .opcode(OpCode::JUMP) + .opcode(OpCode::JUMPDEST) + .opcode(OpCode::PC) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(4) + .check() + .await +} + +#[tokio::test] +async fn pc_after_jump_2() { + for (input, output) in [(&hex!("") as &[u8], 6), (&hex!("ff") as &[u8], 11)] { + EvmTester::new() + .code( + Bytecode::new() + .opcode(OpCode::CALLDATASIZE) + .pushv(9) + .opcode(OpCode::JUMPI) + .pushv(12) + .opcode(OpCode::PC) + .opcode(OpCode::SWAP1) + .opcode(OpCode::JUMP) + .opcode(OpCode::JUMPDEST) + .opcode(OpCode::GAS) + .opcode(OpCode::PC) + .opcode(OpCode::JUMPDEST) + .ret_top(), + ) + .input(input) + .status(StatusCode::Success) + .output_value(output) + .check() + .await + } +} + +#[tokio::test] +async fn byte() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("63aabbccdd")) // aabbccdd + .append(hex!("8060001a")) // DUP 1 BYTE + .append(hex!("600053")) // m[0] = 00 + .append(hex!("80601c1a")) // DUP 28 BYTE + .append(hex!("600253")) // m[2] = aa + .append(hex!("80601f1a")) // DUP 31 BYTE + .append(hex!("600453")) // m[4] = dd + .append(hex!("8060201a")) // DUP 32 BYTE + .append(hex!("600653")) // m[6] = 00 + .append(hex!("60076000f3")), // RETURN(0,7) + ) + .gas(72) + .status(StatusCode::Success) + .gas_left(0) + .inspect_output(|output| { + assert_eq!(output.len(), 7); + assert_eq!(output[0], 0); + assert_eq!(output[2], 0xaa); + assert_eq!(output[4], 0xdd); + assert_eq!(output[6], 0); + }) + .check() + .await +} + +#[tokio::test] +async fn byte_overflow() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::NOT) + .pushv(32) + .opcode(OpCode::BYTE) + .ret_top(), + ) + .output_value(0) + .check() + .await; + + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::NOT) + .pushb(hex!("ffffffffffffffffffffffffffffffffffff")) + .opcode(OpCode::BYTE) + .ret_top(), + ) + .output_value(0) + .check() + .await; +} + +#[tokio::test] +async fn addmod_mulmod() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!( + "7fcdeb8272fc01d4d50a6ec165d2ea477af19b9b2c198459f59079583b97e88a66" + )) + .append(hex!( + "7f52e7e7a03b86f534d2e338aa1bb05ba3539cb2f51304cdbce69ce2d422c456ca" + )) + .append(hex!( + "7fe0f2f0cae05c220260e1724bdc66a0f83810bd1217bd105cb2da11e257c6cdf6" + )) + .append(hex!("82828208")) // DUP DUP DUP ADDMOD + .append(hex!("600052")) // m[0..] + .append(hex!("82828209")) // DUP DUP DUP MULMOD + .append(hex!("602052")) // m[32..] + .append(hex!("60406000f3")), // RETURN(0,64) + ) + .gas(67) + .status(StatusCode::Success) + .gas_left(0) + .inspect_output(|output| { + assert_eq!( + &output[..32], + hex!("65ef55f81fe142622955e990252cb5209a11d4db113d842408fd9c7ae2a29a5a") + ); + assert_eq!( + &output[32..], + hex!("34e04890131a297202753cae4c72efd508962c9129aed8b08c8e87ab425b7258") + ); + }) + .check() + .await +} + +#[tokio::test] +async fn divmod() { + // Div and mod the -1 by the input and return. + EvmTester::new() + .code(hex!("600035600160000381810460005281810660205260406000f3")) + .input(&hex!("0d") as &[u8]) + .status(StatusCode::Success) + .gas_used(61) + .inspect_output(|output| { + assert_eq!( + &output[..32], + hex!("0000000000000000000000000000000000000000000000000000000000000013") + ); + assert_eq!( + &output[32..], + hex!("08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + ); + }) + .check() + .await +} + +#[tokio::test] +async fn div_by_zero() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .pushv(0xff) + .opcode(OpCode::DIV) + .opcode(OpCode::SDIV) + .ret_top(), + ) + .status(StatusCode::Success) + .gas_used(34) + .output_value(0) + .check() + .await +} + +#[tokio::test] +async fn mod_by_zero() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .pushv(0xeffe) + .opcode(OpCode::MOD) + .opcode(OpCode::SMOD) + .ret_top(), + ) + .status(StatusCode::Success) + .gas_used(34) + .output_value(0) + .check() + .await +} + +#[tokio::test] +async fn addmod_mulmod_by_zero() { + EvmTester::new() + .code(hex!("6000358080808008091560005260206000f3")) + .status(StatusCode::Success) + .gas_used(52) + .inspect_output(|output| { + assert_eq!(output.len(), 32); + assert_eq!(output[31], 1); + }) + .check() + .await; +} + +#[tokio::test] +async fn signextend() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("62017ffe")) // 017ffe + .append(hex!("8060000b")) // DUP SIGNEXTEND(0) + .append(hex!("600052")) // m[0..] + .append(hex!("8060010b")) // DUP SIGNEXTEND(1) + .append(hex!("602052")) // m[32..] + .append(hex!("60406000f3")), // RETURN(0,64) + ) + .gas(49) + .status(StatusCode::Success) + .gas_left(0) + .inspect_output(|output| { + assert_eq!( + &output[..32], + hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe") + ); + assert_eq!( + &output[32..], + hex!("0000000000000000000000000000000000000000000000000000000000007ffe") + ); + }) + .check() + .await; +} + +#[tokio::test] +async fn signextend_31() { + for (code, output) in [ + ( + hex!("61010160000360081c601e0b60005260206000f3"), + &hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe") as &[u8], + ), + ( + hex!("61010160000360081c601f0b60005260206000f3"), + &hex!("00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe") as &[u8], + ), + ] { + EvmTester::new() + .code(code) + .revision(Revision::Constantinople) + .status(StatusCode::Success) + .gas_used(38) + .output_value(output) + .check() + .await; + } +} + +#[tokio::test] +async fn exp() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("612019")) // 0x2019 + .append(hex!("6003")) // 3 + .append(hex!("0a")) // EXP + .append(hex!("600052")) // m[0..] + .append(hex!("60206000f3")), // RETURN(0,32) + ) + .gas(131) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!( + "263cf24662b24c371a647c1340022619306e431bf3a4298d4b5998a3f1c1aaa3" + )) + .check() + .await +} + +#[tokio::test] +async fn exp_1_0() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .pushv(1) + .opcode(OpCode::EXP) + .ret_top(), + ) + .gas(31) + .status(StatusCode::Success) + .gas_used(31) + .output_value(1) + .check() + .await +} + +#[tokio::test] +async fn exp_0_0() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .pushv(0) + .opcode(OpCode::EXP) + .ret_top(), + ) + .gas(31) + .status(StatusCode::Success) + .gas_used(31) + .output_value(1) + .check() + .await +} + +#[tokio::test] +async fn exp_oog() { + let code = hex!("6001600003800a"); + EvmTester::new() + .code(code) + .gas(1622) + .status(StatusCode::Success) + .gas_left(0) + .check() + .await; + EvmTester::new() + .code(code) + .gas(1621) + .status(StatusCode::OutOfGas) + .gas_left(0) + .check() + .await; +} + +#[tokio::test] +async fn exp_pre_spurious_dragon() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("62012019")) // 0x012019 + .append(hex!("6003")) // 3 + .append(hex!("0a")) // EXP + .append(hex!("600052")) // m[0..] + .append(hex!("60206000f3")), // RETURN(0,32) + ) + .revision(Revision::Tangerine) + .gas(131 - 70) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!( + "422ea3761c4f6517df7f102bb18b96abf4735099209ca21256a6b8ac4d1daaa3" + )) + .check() + .await; +} + +#[tokio::test] +async fn calldataload() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("600335")) // CALLDATALOAD(3) + .append(hex!("600052")) // m[0..] + .append(hex!("600a6000f3")), // RETURN(0,10) + ) + .gas(21) + .input(&hex!("0102030405") as &[u8]) + .status(StatusCode::Success) + .gas_left(0) + .output_data(hex!("04050000000000000000")) + .check() + .await +} + +#[tokio::test] +async fn calldataload_outofrange() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .opcode(OpCode::CALLDATALOAD) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(U256::zero()) + .check() + .await +} + +#[tokio::test] +async fn calldatacopy() { + let code = Bytecode::new() + .append(hex!("366001600037")) // CALLDATASIZE 1 0 CALLDATACOPY + .append(hex!("600a6000f3")); + EvmTester::new() + .code(code.clone()) + .input(&hex!("0102030405") as &[u8]) + .status(StatusCode::Success) + .gas_used(23) + .output_data(hex!("02030405000000000000")) + .check() + .await; + + EvmTester::new() + .code(code.clone()) + .status(StatusCode::Success) + .gas_used(20) + .check() + .await; + + EvmTester::new() + .code(hex!("60ff66fffffffffffffa60003760ff6000f3")) + .status(StatusCode::Success) + .gas_used(66) + .output_data([0; 0xff]) + .check() + .await; +} + +#[tokio::test] +async fn address() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("30600052")) // ADDRESS MSTORE(0) + .append(hex!("600a600af3")), // RETURN(10,10) + ) + .destination(hex!("cc00000000000000000000000000000000000000")) + .status(StatusCode::Success) + .gas(17) + .gas_left(0) + .output_data(hex!("0000cc00000000000000")) + .check() + .await +} + +#[tokio::test] +async fn caller_callvalue() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("333401600052")) // CALLER CALLVALUE ADD MSTORE(0) + .append(hex!("600a600af3")), // RETURN(10,10) + ) + .sender(hex!("dd00000000000000000000000000000000000000")) + .value(hex!( + "00000000000000000000000000ee000000000000000000000000000000000000" + )) + .status(StatusCode::Success) + .gas(22) + .gas_left(0) + .output_data(hex!("0000ddee000000000000")) + .check() + .await +} + +#[tokio::test] +async fn undefined() { + EvmTester::new() + .code(hex!("2a")) + .gas(1) + .status(StatusCode::UndefinedInstruction) + .gas_left(0) + .check() + .await +} + +#[tokio::test] +async fn invalid() { + EvmTester::new() + .code(hex!("fe")) + .gas(1) + .status(StatusCode::InvalidInstruction) + .gas_left(0) + .check() + .await +} + +#[tokio::test] +async fn keccak256() { + EvmTester::new() + .code(hex!("6108006103ff2060005260206000f3")) + .status(StatusCode::Success) + .gas_used(738) + .output_data(hex!( + "aeffb38c06e111d84216396baefeb7fed397f303d5cb84a33f1e8b485c4a22da" + )) + .check() + .await +} + +#[tokio::test] +async fn keccak256_empty() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::KECCAK256) + .ret_top(), + ) + .output_data(hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + )) + .check() + .await +} + +#[tokio::test] +async fn revert() { + EvmTester::new() + .code( + Bytecode::new() + .append(hex!("60ee8053")) // m[ee] == e + .append(hex!("600260edfd")), // REVERT(ee,1) + ) + .gas_used(39) + .status(StatusCode::Revert) + .output_data(hex!("00ee")) + .check() + .await +} + +#[tokio::test] +async fn return_empty_buffer_at_offset_0() { + EvmTester::new() + .code( + Bytecode::new() + .opcode(OpCode::MSIZE) + .opcode(OpCode::DUP1) + .opcode(OpCode::RETURN), + ) + .gas_used(5) + .check() + .await +} + +#[tokio::test] +async fn return_empty_buffer_at_high_offset() { + for (opcode, status) in [ + (OpCode::RETURN, StatusCode::Success), + (OpCode::REVERT, StatusCode::Revert), + ] { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DIFFICULTY) + .opcode(opcode), + ) + .apply_host_fn(|host, _| { + host.tx_context.block_difficulty = + hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1").into() + }) + .status(status) + .check() + .await; + } +} + +#[tokio::test] +async fn shl() { + EvmTester::new() + .code(hex!("600560011b6000526001601ff3")) + .revision(Revision::Constantinople) + .gas_used(24) + .status(StatusCode::Success) + .output_data([5 << 1]) + .check() + .await +} + +#[tokio::test] +async fn shr() { + EvmTester::new() + .code(hex!("600560011c6000526001601ff3")) + .revision(Revision::Constantinople) + .gas_used(24) + .status(StatusCode::Success) + .output_data([5 >> 1]) + .check() + .await +} + +#[tokio::test] +async fn sar() { + EvmTester::new() + .code(hex!("600160000360021d60005260016000f3")) + .revision(Revision::Constantinople) + .gas_used(30) + .status(StatusCode::Success) + .output_data([0xff]) + .check() + .await // MSB of (-1 >> 2) == -1 +} + +#[tokio::test] +async fn sar_01() { + EvmTester::new() + .code(hex!("600060011d60005260016000f3")) + .revision(Revision::Constantinople) + .gas_used(24) + .status(StatusCode::Success) + .output_data([0]) + .check() + .await +} + +#[tokio::test] +async fn shift_overflow() { + for op in [OpCode::SHL, OpCode::SHR, OpCode::SAR] { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::NOT) + .pushv(0x100) + .opcode(op) + .ret_top(), + ) + .revision(Revision::Constantinople) + .inspect_output(move |output| { + assert_eq!( + output.iter().copied().map(u64::from).sum::(), + if op == OpCode::SAR { 32 * 0xff } else { 0 } + ); + }) + .check() + .await + } +} + +#[tokio::test] +async fn undefined_instruction_analysis_overflow() { + let undefined_opcode = OpCode(0x0c); + EvmTester::new() + .code(Bytecode::new().opcode(undefined_opcode)) + .status(StatusCode::UndefinedInstruction) + .check() + .await +} + +#[tokio::test] +async fn abort() { + for r in Revision::iter() { + EvmTester::new() + .code(hex!("fe")) + .revision(r) + .status(StatusCode::InvalidInstruction) + .check() + .await + } +} + +#[tokio::test] +async fn staticmode() { + for op in [ + OpCode::SSTORE, + OpCode::LOG0, + OpCode::LOG1, + OpCode::LOG2, + OpCode::LOG3, + OpCode::LOG4, + OpCode::CALL, + OpCode::CREATE, + OpCode::CREATE2, + OpCode::SELFDESTRUCT, + ] { + let mut code_prefix = Bytecode::new().pushv(1); + for _ in 0..6 { + code_prefix = code_prefix.opcode(OpCode::DUP1); + } + + EvmTester::new() + .code(code_prefix.opcode(op)) + .revision(Revision::Constantinople) + .set_static(true) + .status(StatusCode::StaticModeViolation) + .gas_left(0) + .check() + .await + } +} + +#[tokio::test] +async fn memory_big_allocation() { + const SIZE: usize = 256 * 1024 + 1; + EvmTester::new() + .code(Bytecode::new().ret(0, SIZE)) + .status(StatusCode::Success) + .output_data([0; SIZE]) + .check() + .await +} + +#[tokio::test] +async fn memory_grow_mstore8() { + let code = Bytecode::new() + .pushv(0) + .opcode(OpCode::CALLDATALOAD) + .pushv(0) + .opcode(OpCode::JUMPDEST) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::MSTORE8) + .pushv(1) + .opcode(OpCode::ADD) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP3) + .opcode(OpCode::EQ) + .opcode(OpCode::ISZERO) + .pushv(5) + .opcode(OpCode::JUMPI) + .opcode(OpCode::MSIZE) + .pushv(0) + .opcode(OpCode::RETURN); + + const SIZE: usize = 4 * 1024 + 256 + 1; + let input = hex!("0000000000000000000000000000000000000000000000000000000000001101").to_vec(); + + EvmTester::new() + .code(code) + .input(input) + .status(StatusCode::Success) + .inspect_output(|output| { + assert_eq!(output.len(), ((SIZE + 31) / 32) * 32); + for i in 0..SIZE { + assert_eq!(output[i] as usize, i % 256); + } + + for i in SIZE..output.len() { + assert_eq!(output[i], 0); + } + }) + .check() + .await +} + +#[tokio::test] +async fn mstore8_memory_cost() { + for (gas, status) in [(12, StatusCode::Success), (11, StatusCode::OutOfGas)] { + EvmTester::new() + .code(Bytecode::new().pushv(0).mstore8(0)) + .gas(gas) + .status(status) + .check() + .await + } +} + +#[tokio::test] +async fn keccak256_memory_cost() { + for (gas, status) in [(45, StatusCode::Success), (44, StatusCode::OutOfGas)] { + EvmTester::new() + .code(Bytecode::new().pushv(1).pushv(0).opcode(OpCode::KECCAK256)) + .gas(gas) + .status(status) + .check() + .await + } +} + +#[tokio::test] +async fn calldatacopy_memory_cost() { + for (gas, status) in [(18, StatusCode::Success), (17, StatusCode::OutOfGas)] { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(OpCode::CALLDATACOPY), + ) + .gas(gas) + .status(status) + .check() + .await + } +} + +const MAX_CODE_SIZE: usize = 0x6000; + +#[tokio::test] +async fn max_code_size_push1() { + let mut code = Bytecode::new(); + for _ in 0..MAX_CODE_SIZE / 2 { + code = code.pushv(1); + } + let code = code.build(); + assert_eq!(code.len(), MAX_CODE_SIZE); + + EvmTester::new() + .code(code.clone()) + .status(StatusCode::StackOverflow) + .check() + .await; + EvmTester::new() + .code(code[..code.len() - 1].to_vec()) + .status(StatusCode::StackOverflow) + .check() + .await; +} + +#[tokio::test] +async fn reverse_16_stack_items() { + // This test puts values 1, 2, ... , 16 on the stack and then reverse them with SWAP opcodes. + // This uses all variants of SWAP instruction. + + let n = 16; + let mut code = Bytecode::new(); + for i in 1..=n { + code = code.pushv(i); + } + code = code.pushv(0); // Temporary stack item. + code = code + .opcode(OpCode::SWAP16) + .opcode(OpCode::SWAP1) + .opcode(OpCode::SWAP16); // Swap 1 and 16. + code = code + .opcode(OpCode::SWAP15) + .opcode(OpCode::SWAP2) + .opcode(OpCode::SWAP15); // Swap 2 and 15. + code = code + .opcode(OpCode::SWAP14) + .opcode(OpCode::SWAP3) + .opcode(OpCode::SWAP14); + code = code + .opcode(OpCode::SWAP13) + .opcode(OpCode::SWAP4) + .opcode(OpCode::SWAP13); + code = code + .opcode(OpCode::SWAP12) + .opcode(OpCode::SWAP5) + .opcode(OpCode::SWAP12); + code = code + .opcode(OpCode::SWAP11) + .opcode(OpCode::SWAP6) + .opcode(OpCode::SWAP11); + code = code + .opcode(OpCode::SWAP10) + .opcode(OpCode::SWAP7) + .opcode(OpCode::SWAP10); + code = code + .opcode(OpCode::SWAP9) + .opcode(OpCode::SWAP8) + .opcode(OpCode::SWAP9); + code = code.opcode(OpCode::POP); + for i in 0..n { + code = code.mstore8(i); + } + code = code.ret(0, n); + + EvmTester::new() + .code(code) + .status(StatusCode::Success) + .output_data(hex!("0102030405060708090a0b0c0d0e0f10")) + .check() + .await +} + +#[tokio::test] +async fn memory_access() { + struct MemoryAccessOpcode { + opcode: OpCode, + memory_index_arg: i8, + memory_size_arg: i8, + } + + impl From<(OpCode, i8, i8)> for MemoryAccessOpcode { + fn from((opcode, memory_index_arg, memory_size_arg): (OpCode, i8, i8)) -> Self { + Self { + opcode, + memory_index_arg, + memory_size_arg, + } + } + } + + struct MemoryAccessParams { + index: u64, + size: u64, + } + + impl From<(u64, u64)> for MemoryAccessParams { + fn from((index, size): (u64, u64)) -> Self { + Self { index, size } + } + } + + let memory_access_opcodes: Vec = vec![ + (OpCode::KECCAK256, 0, 1), + (OpCode::CALLDATACOPY, 0, 2), + (OpCode::CODECOPY, 0, 2), + (OpCode::MLOAD, 0, -1), + (OpCode::MSTORE, 0, -1), + (OpCode::MSTORE8, 0, -1), + (OpCode::EXTCODECOPY, 1, 3), + (OpCode::RETURNDATACOPY, 0, 2), + (OpCode::LOG0, 0, 1), + (OpCode::LOG1, 0, 1), + (OpCode::LOG2, 0, 1), + (OpCode::LOG3, 0, 1), + (OpCode::LOG4, 0, 1), + (OpCode::RETURN, 0, 1), + (OpCode::REVERT, 0, 1), + (OpCode::CALL, 3, 4), + (OpCode::CALL, 5, 6), + (OpCode::CALLCODE, 3, 4), + (OpCode::CALLCODE, 5, 6), + (OpCode::DELEGATECALL, 2, 3), + (OpCode::DELEGATECALL, 4, 5), + (OpCode::STATICCALL, 2, 3), + (OpCode::STATICCALL, 4, 5), + (OpCode::CREATE, 1, 2), + (OpCode::CREATE2, 1, 2), + ] + .into_iter() + .map(MemoryAccessOpcode::from) + .collect(); + + let memory_access_test_cases: Vec = vec![ + (0, 0x100000000), + (0x80000000, 0x80000000), + (0x100000000, 0), + (0x100000000, 1), + (0x100000000, 0x100000000), + ] + .into_iter() + .map(MemoryAccessParams::from) + .collect(); + + let metrics = &*evmodin::instructions::PROPERTIES; + + for p in memory_access_test_cases { + let push_size = format!("64{:0>10x}", p.size); + let push_index = format!("64{:0>10x}", p.index); + + for t in &memory_access_opcodes { + let num_args = metrics[t.opcode.to_usize()].unwrap().stack_height_required as i8; + let mut h = max(num_args, t.memory_size_arg + 1); + let mut code = Bytecode::new(); + + if t.memory_size_arg >= 0 { + h -= 1; + while h != t.memory_size_arg { + code = code.pushv(0); + h -= 1; + } + + code = code.append(hex::decode(&push_size).unwrap()); + } else if p.index == 0 || p.size == 0 { + continue; // Skip opcodes not having SIZE argument. + } + + h -= 1; + while h != t.memory_index_arg { + code = code.pushv(0); + h -= 1; + } + + code = code.append(hex::decode(&push_index).unwrap()); + + while h != 0 { + code = code.pushv(0); + h -= 1; + } + + code = code.opcode(t.opcode); + + let gas = 8796294610952; + + println!( + "offset = {:#02x} size = {:#02x} opcode {}", + p.index, p.size, t.opcode + ); + + let tester = EvmTester::new() + .code(code) + .gas(gas) + .revision(Revision::Constantinople); + + if p.size == 0 { + // It is allowed to request 0 size memory at very big offset. + assert_ne!( + tester + .status(if t.opcode == OpCode::REVERT { + StatusCode::Revert + } else { + StatusCode::Success + }) + .check_and_get_result() + .await + .gas_left, + 0 + ); + } else { + if t.opcode == OpCode::RETURNDATACOPY { + // In case of RETURNDATACOPY the "invalid memory access" might also be returned. + tester.status_one_of([StatusCode::OutOfGas, StatusCode::InvalidMemoryAccess]) + } else { + tester.status(StatusCode::OutOfGas) + } + .gas_left(0) + .check() + .await; + } + } + } +} diff --git a/tests/state.rs b/tests/state.rs new file mode 100644 index 0000000..685493b --- /dev/null +++ b/tests/state.rs @@ -0,0 +1,1262 @@ +use ethereum_types::{Address, H256}; +use evmodin::{ + opcode::*, + util::{mocked_host::*, *}, + *, +}; +use hex_literal::hex; + +#[tokio::test] +async fn code() { + // CODESIZE 2 0 CODECOPY RETURN(0,9) + let code = hex!("38600260003960096000f3"); + EvmTester::new() + .code(code) + .gas_used(23) + .output_data(&code[2..11]) + .check() + .await +} + +#[tokio::test] +async fn codecopy_combinations() { + // The CODECOPY arguments are provided in calldata: first byte is index, second byte is size. + // The whole copied code is returned. + let code = Bytecode::new() + .pushv(0) + .opcode(OpCode::CALLDATALOAD) + .pushv(1) + .opcode(OpCode::BYTE) + .opcode(OpCode::DUP1) + .pushv(0) + .opcode(OpCode::CALLDATALOAD) + .pushv(0) + .opcode(OpCode::BYTE) + .pushv(0) + .opcode(OpCode::CODECOPY) + .pushv(0) + .opcode(OpCode::RETURN) + .build(); + + assert_eq!(code.len(), 0x13); + + for (input, output) in [ + (hex!("0013"), code.clone()), + (hex!("0012"), code[..0x12].to_vec()), + (hex!("0014"), code.iter().copied().chain([0]).collect()), + (hex!("1300"), vec![]), + (hex!("1400"), vec![]), + (hex!("1200"), vec![]), + (hex!("1301"), hex!("00").to_vec()), + (hex!("1401"), hex!("00").to_vec()), + (hex!("1201"), code[0x12..0x12 + 1].to_vec()), + ] { + EvmTester::new() + .code(code.clone()) + .input(input.to_vec()) + .output_data(output) + .check() + .await + } +} + +#[tokio::test] +async fn storage() { + EvmTester::new() + .code( + Bytecode::new() + .sstore(0xee, 0xff) + .sload(0xee) + .mstore8(0) + .ret(0, 1), + ) + .gas(100000) + .status(StatusCode::Success) + .gas_left(99776 - 20000) + .output_data(hex!("ff")) + .check() + .await +} + +#[tokio::test] +async fn sstore_pop_stack() { + EvmTester::new() + .code(hex!("60008060015560005360016000f3")) + .gas(100000) + .status(StatusCode::Success) + .output_data(hex!("00")) + .check() + .await +} + +#[tokio::test] +async fn sload_cost_pre_tangerine_whistle() { + EvmTester::new() + .code(hex!("60008054")) + .revision(Revision::Homestead) + .apply_host_fn(|host, message| { + host.accounts.entry(message.destination).or_default(); + }) + .gas(56) + .status(StatusCode::Success) + .gas_left(0) + .inspect_host(|host, message| { + assert_eq!(host.accounts[&message.destination].storage.len(), 0); + }) + .check() + .await +} + +#[tokio::test] +async fn sstore_out_of_block_gas() { + for (gas, status) in [ + // Barely enough gas to execute successfully. + (20011, StatusCode::Success), + // Out of block gas - 1 too low. + (20010, StatusCode::OutOfGas), + // Out of block gas - 2 too low. + (20009, StatusCode::OutOfGas), + // SSTORE instructions out of gas. + (20008, StatusCode::OutOfGas), + ] { + EvmTester::new() + .code(Bytecode::new().pushv(0).sstore(0, 1).opcode(OpCode::POP)) + .gas(gas) + .status(status) + .check() + .await + } +} + +#[tokio::test] +async fn sstore_cost() { + for revision in [ + Revision::Byzantium, + Revision::Constantinople, + Revision::Petersburg, + Revision::Istanbul, + ] { + let v1 = H256(hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )); + + fn get_storage>(host: &mut MockedHost, key: K) -> &mut StorageValue { + host.accounts + .entry(Address::zero()) + .or_default() + .storage + .entry(key.into()) + .or_default() + } + + let t = EvmTester::new().revision(revision); + + // Added: + t.clone() + .code(Bytecode::new().sstore(1, 1)) + .gas_used(20006) + .status(StatusCode::Success) + .check() + .await; + + // Deleted: + t.clone() + .code(Bytecode::new().sstore(1, 0)) + .apply_host_fn(move |host, _| { + get_storage(host, v1).value = v1; + }) + .gas_used(5006) + .status(StatusCode::Success) + .check() + .await; + + // Modified: + t.clone() + .code(Bytecode::new().sstore(1, 2)) + .apply_host_fn(move |host, _| { + get_storage(host, v1).value = v1; + }) + .gas_used(5006) + .status(StatusCode::Success) + .check() + .await; + + // Unchanged: + t.clone() + .code(Bytecode::new().sstore(1, 1)) + .apply_host_fn(move |host, _| { + get_storage(host, v1).value = v1; + }) + .gas_used(match revision { + Revision::Istanbul => 806, + Revision::Constantinople => 206, + _ => 5006, + }) + .status(StatusCode::Success) + .check() + .await; + + // Added & unchanged: + t.clone() + .code(Bytecode::new().sstore(1, 1).sstore(1, 1)) + .gas_used(match revision { + Revision::Istanbul => 20812, + Revision::Constantinople => 20212, + _ => 25012, + }) + .status(StatusCode::Success) + .check() + .await; + + // Modified again: + t.clone() + .code(Bytecode::new().sstore(1, 2)) + .apply_host_fn(move |host, _| { + let s = get_storage(host, v1); + s.dirty = true; + s.value = v1; + }) + .status(StatusCode::Success) + .gas_used(match revision { + Revision::Istanbul => 806, + Revision::Constantinople => 206, + _ => 5006, + }) + .check() + .await; + + // Added & modified again: + t.clone() + .code(Bytecode::new().sstore(1, 1).sstore(1, 2)) + .status(StatusCode::Success) + .gas_used(match revision { + Revision::Istanbul => 20812, + Revision::Constantinople => 20212, + _ => 25012, + }) + .check() + .await; + + // Modified & modified again: + t.clone() + .code(Bytecode::new().sstore(1, 2).sstore(1, 3)) + .apply_host_fn(move |host, _| { + get_storage(host, v1).value = v1; + }) + .status(StatusCode::Success) + .gas_used(match revision { + Revision::Istanbul => 5812, + Revision::Constantinople => 5212, + _ => 10012, + }) + .check() + .await; + + // Modified & modified again back to original:t.clone() + t.clone() + .code(Bytecode::new().sstore(1, 2).sstore(1, 1)) + .apply_host_fn(move |host, _| { + get_storage(host, v1).value = v1; + }) + .status(StatusCode::Success) + .gas_used(match revision { + Revision::Istanbul => 5812, + Revision::Constantinople => 5212, + _ => 10012, + }) + .check() + .await; + } +} + +#[tokio::test] +async fn sstore_below_stipend() { + let code = Bytecode::new().sstore(0, 0); + + let t = EvmTester::new().code(code); + + for (revision, status) in [ + (Revision::Homestead, StatusCode::OutOfGas), + (Revision::Constantinople, StatusCode::Success), + (Revision::Istanbul, StatusCode::OutOfGas), + ] { + t.clone() + .revision(revision) + .gas(2306) + .status(status) + .check() + .await + } + + t.clone() + .revision(Revision::Constantinople) + .gas(2307) + .status(StatusCode::Success) + .check() + .await +} + +#[tokio::test] +async fn tx_context() { + EvmTester::new() + .code( + Bytecode::new() + .opcode(OpCode::TIMESTAMP) + .opcode(OpCode::COINBASE) + .opcode(OpCode::OR) + .opcode(OpCode::GASPRICE) + .opcode(OpCode::OR) + .opcode(OpCode::NUMBER) + .opcode(OpCode::OR) + .opcode(OpCode::DIFFICULTY) + .opcode(OpCode::OR) + .opcode(OpCode::GASLIMIT) + .opcode(OpCode::OR) + .opcode(OpCode::ORIGIN) + .opcode(OpCode::OR) + .opcode(OpCode::CHAINID) + .opcode(OpCode::OR) + .ret_top(), + ) + .revision(Revision::Istanbul) + .apply_host_fn(|host, _| { + host.tx_context.block_timestamp = 0xdd; + host.tx_context.block_number = 0x1100; + host.tx_context.block_gas_limit = 0x990000; + host.tx_context.chain_id = + hex!("00000000000000000000000000000000000000000000000000000000aa000000").into(); + host.tx_context.block_coinbase.0[1] = 0xcc; + host.tx_context.tx_origin.0[2] = 0x55; + host.tx_context.block_difficulty = + hex!("00dd000000000000000000000000000000000000000000000000000000000000").into(); + host.tx_context.tx_gas_price = + hex!("0000660000000000000000000000000000000000000000000000000000000000").into(); + }) + .status(StatusCode::Success) + .gas_used(52) + .inspect_output(|output_data| { + assert_eq!(output_data.len(), 32); + assert_eq!(output_data[31], 0xdd); + assert_eq!(output_data[30], 0x11); + assert_eq!(output_data[29], 0x99); + assert_eq!(output_data[28], 0xaa); + assert_eq!(output_data[14], 0x55); + assert_eq!(output_data[13], 0xcc); + assert_eq!(output_data[2], 0x66); + assert_eq!(output_data[1], 0xdd); + }) + .check() + .await +} + +#[tokio::test] +async fn balance() { + EvmTester::new() + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 0x0504030201_u64.into() + }) + .code( + Bytecode::new() + .opcode(OpCode::ADDRESS) + .opcode(OpCode::BALANCE) + .mstore(0) + .ret(32 - 6, 6), + ) + .gas_used(417) + .status(StatusCode::Success) + .output_data(hex!("000504030201")) + .check() + .await +} + +#[tokio::test] +async fn account_info_homestead() { + let t = EvmTester::new() + .revision(Revision::Homestead) + .apply_host_fn(|host, msg| { + let acc = host.accounts.entry(msg.destination).or_default(); + acc.balance = 1.into(); + acc.code = [1].to_vec().into(); + }); + + t.clone() + .code( + Bytecode::new() + .opcode(OpCode::ADDRESS) + .opcode(OpCode::BALANCE) + .ret_top(), + ) + .status(StatusCode::Success) + .gas_used(37) + .output_value(1) + .check() + .await; + + t.clone() + .code( + Bytecode::new() + .opcode(OpCode::ADDRESS) + .opcode(OpCode::EXTCODESIZE) + .ret_top(), + ) + .status(StatusCode::Success) + .gas_used(37) + .output_value(1) + .check() + .await; + + t.clone() + .code( + Bytecode::new() + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(OpCode::ADDRESS) + .opcode(OpCode::EXTCODECOPY) + .ret(0, 1), + ) + .status(StatusCode::Success) + .gas_used(43) + .output_data([1]) + .check() + .await +} + +#[tokio::test] +async fn selfbalance() { + let t = EvmTester::new() + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 0x0504030201_u64.into(); + }) + // NOTE: adding push here to balance out the stack pre-Istanbul (needed to get undefined + // instruction as a result) + .code( + Bytecode::new() + .pushv(1) + .opcode(OpCode::SELFBALANCE) + .mstore(0) + .ret(32 - 6, 6), + ); + + t.clone() + .revision(Revision::Constantinople) + .status(StatusCode::UndefinedInstruction) + .check() + .await; + + t.clone() + .revision(Revision::Istanbul) + .status(StatusCode::Success) + .gas_used(23) + .output_data(hex!("000504030201")) + .check() + .await +} + +#[tokio::test] +async fn log() { + for op in [ + OpCode::LOG0, + OpCode::LOG1, + OpCode::LOG2, + OpCode::LOG3, + OpCode::LOG4, + ] { + let n = op.to_usize() - OpCode::LOG0.to_usize(); + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .pushv(2) + .pushv(3) + .pushv(4) + .mstore8_value(2, 0x77) + .pushv(2) + .pushv(2) + .opcode(op), + ) + .status(StatusCode::Success) + .gas_used((421 + n * 375) as i64) + .inspect_host(move |host, _| { + let r = host.recorded.lock(); + + assert_eq!(r.logs.len(), 1); + let last_log = r.logs.last().unwrap(); + assert_eq!(&*last_log.data, &hex!("7700") as &[u8]); + assert_eq!(last_log.topics.len(), n); + for i in 0..n { + assert_eq!(last_log.topics[i].0[31] as usize, 4 - i); + } + }) + .check() + .await + } +} + +#[tokio::test] +async fn log0_empty() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::LOG0), + ) + .inspect_host(|host, _| { + let r = host.recorded.lock(); + assert_eq!(r.logs.len(), 1); + let last_log = r.logs.last().unwrap(); + assert_eq!(last_log.topics.len(), 0); + assert_eq!(last_log.data.len(), 0); + }) + .check() + .await +} + +#[tokio::test] +async fn log_data_cost() { + for op in [ + OpCode::LOG0, + OpCode::LOG1, + OpCode::LOG2, + OpCode::LOG3, + OpCode::LOG4, + ] { + let num_topics = op.to_u8() - OpCode::LOG0.to_u8(); + let mut code = Bytecode::new().pushv(0); + for _ in 0..4 { + code = code.opcode(OpCode::DUP1); + } + code = code.pushv(1).pushv(0).opcode(op); + + let cost = 407 + num_topics as usize * 375; + + EvmTester::new() + .code(code) + .gas_used(cost as i64) + .status(StatusCode::Success) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().logs.len(), 1); + }) + .check() + .await + } +} + +#[tokio::test] +async fn selfdestruct() { + EvmTester::new() + .code(hex!("6009ff")) + .revision(Revision::Spurious) + .status(StatusCode::Success) + .gas_used(5003) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().selfdestructs.len(), 1); + assert_eq!( + host.recorded + .lock() + .selfdestructs + .last() + .unwrap() + .beneficiary[19], + 9 + ); + }) + .check() + .await; + + EvmTester::new() + .code(hex!("6007ff")) + .revision(Revision::Homestead) + .status(StatusCode::Success) + .gas_used(3) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().selfdestructs.len(), 1); + assert_eq!( + host.recorded + .lock() + .selfdestructs + .last() + .unwrap() + .beneficiary[19], + 7 + ); + }) + .check() + .await; + + EvmTester::new() + .code(hex!("6008ff")) + .revision(Revision::Tangerine) + .status(StatusCode::Success) + .gas_used(30003) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().selfdestructs.len(), 1); + assert_eq!( + host.recorded + .lock() + .selfdestructs + .last() + .unwrap() + .beneficiary[19], + 8 + ); + }) + .check() + .await; +} + +#[tokio::test] +async fn selfdestruct_with_balance() { + let beneficiary = Address::zero(); + let code = Bytecode::new() + .pushb(beneficiary.0) + .opcode(OpCode::SELFDESTRUCT); + + let mut t = EvmTester::new() + .code(code) + .destination(hex!("000000000000000000000000000000000000005e")) + .apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 0.into(); + }); + + t.clone() + .revision(Revision::Homestead) + .status(StatusCode::Success) + .gas_used(3) + .inspect_host(|host, msg| { + let r = host.recorded.lock(); + + assert_eq!(r.account_accesses, [msg.destination]); // Selfdestruct. + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .status(StatusCode::Success) + .gas_used(30003) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary, + // Selfdestruct. + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas(30002) + .status(StatusCode::OutOfGas) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .status(StatusCode::Success) + .gas_used(5003) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Balance. + msg.destination, + // Selfdestruct. + msg.destination, + ] + ) + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas(5002) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().account_accesses, []); + }) + .check() + .await; + + t = t.apply_host_fn(move |host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + }); + + t.clone() + .revision(Revision::Homestead) + .gas_used(3) + .status(StatusCode::Success) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Selfdestruct. + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas_used(30003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary, + // Selfdestruct. + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas(30002) + .status(StatusCode::OutOfGas) + .inspect_host(move |host, _| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary, + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas_used(30003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Balance + msg.destination, + // Exists? + beneficiary, + // Selfdestruct. + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas(30002) + .status(StatusCode::OutOfGas) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Balance + msg.destination, + // Exists? + beneficiary, + ] + ); + }) + .check() + .await; + + t = t.apply_host_fn(move |host, msg| { + host.accounts.entry(beneficiary).or_default(); // Beneficiary exists. + host.accounts.get_mut(&msg.destination).unwrap().balance = 0.into(); + }); + + t.clone() + .revision(Revision::Homestead) + .gas_used(3) + .status(StatusCode::Success) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Selfdestruct. + msg.destination, + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas_used(5003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary, + // Selfdestruct. + msg.destination, + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas(5002) + .status(StatusCode::OutOfGas) + .inspect_host(move |host, _| { + assert_eq!(host.recorded.lock().account_accesses, []); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas(5003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Balance. + msg.destination, + // Selfdestruct. + msg.destination, + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas(5002) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().account_accesses, []); + }) + .check() + .await; + + t = t.apply_host_fn(|host, msg| { + host.accounts.entry(msg.destination).or_default().balance = 1.into(); + }); + + t.clone() + .revision(Revision::Homestead) + .gas_used(3) + .status(StatusCode::Success) + .inspect_host(|host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Selfdestruct + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas_used(5003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Exists? + beneficiary, + // Selfdestruct + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Tangerine) + .gas(5002) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().account_accesses, []); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas_used(5003) + .status(StatusCode::Success) + .inspect_host(move |host, msg| { + assert_eq!( + host.recorded.lock().account_accesses, + [ + // Balance + msg.destination, + // Exists? + beneficiary, + // Selfdestruct + msg.destination + ] + ); + }) + .check() + .await; + + t.clone() + .revision(Revision::Spurious) + .gas(5002) + .status(StatusCode::OutOfGas) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().account_accesses, []); + }) + .check() + .await; +} + +#[tokio::test] +async fn blockhash() { + let t = EvmTester::new() + .code(hex!("60004060005260206000f3")) + .status(StatusCode::Success) + .gas_used(38) + .apply_host_fn(|host, _| { + host.block_hash.0[13] = 0x13; + }); + + t.clone() + .apply_host_fn(|host, _| { + host.tx_context.block_number = 0; + }) + .inspect_output(|output| { + assert_eq!(output.len(), 32); + assert_eq!(output[13], 0); + }) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().blockhashes, [] as [u64; 0]); + }) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.tx_context.block_number = 257; + }) + .inspect_output(|output| { + assert_eq!(output.len(), 32); + assert_eq!(output[13], 0); + }) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().blockhashes, [] as [u64; 0]); + }) + .check() + .await; + + t.clone() + .apply_host_fn(|host, _| { + host.tx_context.block_number = 256; + }) + .inspect_output(|output| { + assert_eq!(output.len(), 32); + assert_eq!(output[13], 0x13); + }) + .inspect_host(|host, _| { + assert_eq!(host.recorded.lock().blockhashes, [0]); + }) + .check() + .await; +} + +#[tokio::test] +async fn extcode() { + let addr = hex!("fffffffffffffffffffffffffffffffffffffffe").into(); + + EvmTester::new() + .apply_host_fn(move |host, _| { + host.accounts.entry(addr).or_default().code = (&hex!("0a0b0c0d") as &[u8]).into(); + }) + .code( + Bytecode::new() + .append(hex!("6002600003803b60019003")) // S = EXTCODESIZE(-2) - 1 + .append(hex!("90600080913c")) // EXTCODECOPY(-2, 0, 0, S) + .append(hex!("60046000f3")), // RETURN(0, 4) + ) + .gas_used(1445) + .status(StatusCode::Success) + .inspect(move |host, _, output| { + assert_eq!(output.len(), 4); + assert_eq!(output[..3], host.accounts[&addr].code[..3]); + assert_eq!(output[3], 0); + assert_eq!(host.recorded.lock().account_accesses.len(), 2); + assert_eq!(host.recorded.lock().account_accesses[0].0[19], 0xfe); + assert_eq!(host.recorded.lock().account_accesses[1].0[19], 0xfe); + }) + .check() + .await +} + +#[tokio::test] +async fn extcodesize() { + EvmTester::new() + .apply_host_fn(|host, _| { + host.accounts + .entry(hex!("0000000000000000000000000000000000000002").into()) + .or_default() + .code = (&[0_u8] as &[u8]).into(); + }) + .code( + Bytecode::new() + .pushv(2) + .opcode(OpCode::EXTCODESIZE) + .ret_top(), + ) + .output_value(1) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_big_index() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .opcode(OpCode::DUP1) + .pushv(u64::from(u32::MAX) + 1) + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::EXTCODECOPY) + .pushv(0) + .opcode(OpCode::RETURN), + ) + .output_data(hex!("00")) + .check() + .await +} + +#[tokio::test] +async fn extcodehash() { + let t = EvmTester::new() + .apply_host_fn(|host, _| { + host.accounts.entry(Address::zero()).or_default().code_hash = + hex!("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").into(); + }) + .code(hex!("60003f60005260206000f3")); + + t.clone() + .revision(Revision::Byzantium) + .status(StatusCode::UndefinedInstruction) + .check() + .await; + + t.clone() + .revision(Revision::Constantinople) + .status(StatusCode::Success) + .gas_used(418) + .inspect(|host, _, output| { + assert_eq!(output, host.accounts[&Address::zero()].code_hash.0); + }) + .check() + .await +} + +#[tokio::test] +async fn codecopy_empty() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::CODECOPY) + .opcode(OpCode::MSIZE) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(0) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_empty() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::EXTCODECOPY) + .opcode(OpCode::MSIZE) + .ret_top(), + ) + .status(StatusCode::Success) + .output_value(0) + .check() + .await +} + +#[tokio::test] +async fn codecopy_memory_cost() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .pushv(0) + .pushv(0) + .opcode(OpCode::CODECOPY), + ) + .status(StatusCode::Success) + .gas_used(18) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_memory_cost() { + EvmTester::new() + .code( + Bytecode::new() + .pushv(1) + .pushv(0) + .opcode(OpCode::DUP1) + .opcode(OpCode::DUP1) + .opcode(OpCode::EXTCODECOPY), + ) + .gas_used(718) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_nonzero_index() { + let index = 15; + let code = Bytecode::new() + .pushv(2) + .pushv(index) + .pushv(0) + .pushv(0xa) + .opcode(OpCode::EXTCODECOPY) + .ret(0, 2) + .build(); + assert_eq!(code.len() + 1, index); + + EvmTester::new() + .apply_host_fn(move |host, _| { + let mut code = std::iter::repeat(0).take(16).collect::>(); + code[index] = 0xc0; + host.accounts + .entry(hex!("000000000000000000000000000000000000000a").into()) + .or_default() + .code = code.into(); + }) + .code(code) + .status(StatusCode::Success) + .output_data(hex!("c000")) + .inspect_host(|host, _| { + assert_eq!( + host.recorded.lock().account_accesses, + [hex!("000000000000000000000000000000000000000a").into()] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_fill_tail() { + EvmTester::new() + .apply_host_fn(|host, _| { + let mut addr = Address::zero(); + addr.0[19] = 0xa; + host.accounts.entry(addr).or_default().code = (&hex!("ff") as &[u8]).into(); + }) + .code( + Bytecode::new() + .pushv(2) + .pushv(0) + .pushv(0) + .pushv(0xa) + .opcode(OpCode::EXTCODECOPY) + .ret(0, 2), + ) + .status(StatusCode::Success) + .output_data(hex!("ff00")) + .inspect_host(|host, _| { + assert_eq!( + host.recorded.lock().account_accesses, + [hex!("000000000000000000000000000000000000000a").into()] + ); + }) + .check() + .await +} + +#[tokio::test] +async fn extcodecopy_buffer_overflow() { + let code = Bytecode::new() + .opcode(OpCode::NUMBER) + .opcode(OpCode::TIMESTAMP) + .opcode(OpCode::CALLDATASIZE) + .opcode(OpCode::ADDRESS) + .opcode(OpCode::EXTCODECOPY) + .opcode(OpCode::NUMBER) + .opcode(OpCode::CALLDATASIZE) + .opcode(OpCode::RETURN) + .build(); + + let t = EvmTester::new().apply_host_fn({ + let code = code.clone(); + move |host, msg| { + host.accounts.entry(msg.destination).or_default().code = code.clone().into(); + } + }); + + let s = code.len(); + let values = [0, 1, s - 1, s, s + 1, 5000]; + for offset in values { + for size in values { + t.clone() + .apply_host_fn(move |host, _| { + host.tx_context.block_timestamp = offset as u64; + host.tx_context.block_number = size as u64; + }) + .code(code.clone()) + .status(StatusCode::Success) + .inspect_output(move |output| { + assert_eq!(output.len(), size); + }) + .check() + .await; + } + } +}