Repository: vorot93/evmodin Branch: master Commit: 7afaf52855e7 Files: 38 Total size: 292.9 KB Directory structure: gitextract_wugh22sf/ ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src/ │ ├── common.rs │ ├── continuation/ │ │ ├── interrupt.rs │ │ ├── interrupt_data.rs │ │ ├── mod.rs │ │ └── resume_data.rs │ ├── evmc.rs │ ├── host.rs │ ├── instructions/ │ │ ├── arithmetic.rs │ │ ├── bitwise.rs │ │ ├── boolean.rs │ │ ├── call.rs │ │ ├── control.rs │ │ ├── external.rs │ │ ├── instruction_table.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── properties.rs │ │ └── stack_manip.rs │ ├── interpreter.rs │ ├── lib.rs │ ├── opcode.rs │ ├── state.rs │ ├── tracing/ │ │ └── mod.rs │ └── util/ │ ├── bytecode.rs │ ├── mocked_host.rs │ ├── mod.rs │ └── tester.rs └── tests/ ├── basefee.rs ├── call.rs ├── eip2929.rs ├── execute.rs ├── other.rs └── state.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/main.yml ================================================ on: pull_request: push: branches: - master name: CI jobs: ci: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macOS-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 ================================================ FILE: .gitignore ================================================ /target Cargo.lock ================================================ FILE: Cargo.toml ================================================ [package] name = "evmodin" version = "0.1.0" edition = "2021" license = "Apache-2.0" description = "Fast EVM implementation with support for resumability." [dependencies] arrayvec = { version = "0.7", default-features = false, features = ["serde"] } bytes = { version = "1", default-features = false, features = ["serde"] } derive_more = "0.99" educe = { version = "0.4", default-features = false, features = ["Debug"] } enum-as-inner = "0.3" evmc-declare = { git = "https://github.com/ethereum/evmc", tag = "v10.0.0-alpha.1", optional = true } evmc-vm = { git = "https://github.com/ethereum/evmc", tag = "v10.0.0-alpha.1", optional = true } i256 = { git = "https://github.com/vorot93/rust-i256" } ethereum-types = { version = "0.12", default-features = false } genawaiter = { git = "https://github.com/cuviper/genawaiter", branch = "unhacked" } getset = "0.1" 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.10" strum_macros = "0.23" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.3", features = ["js"] } [dev-dependencies] evmodin-test = { path = ".", package = "evmodin", features = ["util"] } hex-literal = "0.3" rand = { version = "0.8", features = ["std"] } [features] evmc = ["evmc-declare", "evmc-vm"] util = ["hex-literal", "parking_lot"] [lib] name = "evmodin" path = "src/lib.rs" crate-type = ["lib", "staticlib", "cdylib"] ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================ # evmodin Fast EVM implementation with support for resumability. 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, recipient: Address::zero(), code_address: Address::zero(), sender: Address::zero(), input_data: vec![].into(), value: U256::zero(), }; assert_eq!( AnalyzedCode::analyze(my_code) .execute(&mut DummyHost, &mut NoopTracer, None, message, Revision::latest()), Output { status_code: StatusCode::Success, gas_left: 146, output_data: b"hello".to_vec().into(), create_address: None, } ) ``` ## Host / interpreter separation `evmodin` is not a standalone execution implementation - it is only an EVM interpreter with gas metering that must be coupled with Host, as defined in EVMC, for state access and inducing sub-calls. `MockedHost` is shipped in `evmodin`, but is only useful in tests. [Akula](https://github.com/akula-bft/akula), a fully-fledged Ethereum implementation, features its own version of Host for execution. Akula+evmodin pairing is considered to be the reference execution implementation which passes all Ethereum consensus tests. ## Resumability `evmodin` is an interpreter loop that runs until host interaction/data is necessary. Then it exits with an interrupt. Each interrupt contains a value to be supplied to the host, and `resume` method which may accept data from Host, depending on interrupt. `AnalyzedCode::execute` simply loops, using data from `Host` to resume interrupts. You can make your own reactor that will handle interrupts instead, please see `ExecutionStartInterrupt::run_to_completion_with_host` for reference implementation. ================================================ FILE: src/common.rs ================================================ use bytes::Bytes; use ethereum_types::*; use serde::Serialize; use strum_macros::Display; /// EVM revision. #[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash, 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, /// The Shanghai revision. Shanghai = 10, } impl Revision { pub fn iter() -> impl IntoIterator { [ Self::Frontier, Self::Homestead, Self::Tangerine, Self::Spurious, Self::Byzantium, Self::Constantinople, Self::Petersburg, Self::Istanbul, Self::Berlin, Self::London, Self::Shanghai, ] } pub const fn latest() -> Self { Self::Shanghai } pub const fn len() -> usize { Self::latest() as usize + 1 } } /// Message status code. #[must_use] #[derive(Clone, Debug, Display, PartialEq)] pub enum StatusCode { /// Execution finished with success. #[strum(serialize = "success")] Success, /// Generic execution failure. #[strum(serialize = "failure")] Failure, /// 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, /// The execution has run out of gas. #[strum(serialize = "out of gas")] OutOfGas, /// 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, /// An undefined instruction has been encountered. #[strum(serialize = "undefined instruction")] UndefinedInstruction, /// The execution has attempted to put more items on the EVM stack /// than the specified limit. #[strum(serialize = "stack overflow")] StackOverflow, /// Execution of an opcode has required more items on the EVM stack. #[strum(serialize = "stack underflow")] StackUnderflow, /// Execution has violated the jump destination restrictions. #[strum(serialize = "bad jump destination")] BadJumpDestination, /// Tried to read outside memory bounds. /// /// An example is RETURNDATACOPY reading past the available buffer. #[strum(serialize = "invalid memory access")] InvalidMemoryAccess, /// Call depth has exceeded the limit (if any) #[strum(serialize = "call depth exceeded")] CallDepthExceeded, /// Tried to execute an operation which is restricted in static mode. #[strum(serialize = "static mode violation")] StaticModeViolation, /// 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, /// Contract validation has failed. #[strum(serialize = "contract validation failure")] ContractValidationFailure, /// An argument to a state accessing method has a value outside of the /// accepted range of values. #[strum(serialize = "argument out of range")] ArgumentOutOfRange, /// The caller does not have enough funds for value transfer. #[strum(serialize = "insufficient balance")] InsufficientBalance, /// EVM implementation generic internal error. #[strum(serialize = "internal error")] InternalError(String), } /// The kind of call-like instruction. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CallKind { Call, DelegateCall, CallCode, Create, Create2 { salt: U256 }, } /// The message describing an EVM call, /// including a zero-depth call from 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 (recipient) of the message. pub recipient: 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, /// The address of the code to be executed. /// /// May be different from the evmc_message::destination (recipient) /// in case of `CallKind::CallCode` or `CallKind::DelegateCall`. pub code_address: Address, } #[derive(Clone, Debug, PartialEq)] pub struct CreateMessage { pub salt: Option, pub gas: i64, pub depth: i32, pub initcode: Bytes, pub sender: Address, pub endowment: U256, } impl From for Message { fn from(msg: CreateMessage) -> Self { Self { kind: if let Some(salt) = msg.salt { CallKind::Create2 { salt } } else { CallKind::Create }, is_static: false, depth: msg.depth, gas: msg.gas, recipient: Address::zero(), code_address: Address::zero(), sender: msg.sender, input_data: msg.initcode, value: msg.endowment, } } } /// 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
, } /// EVM execution output if no error has occurred. #[derive(Clone, Debug, PartialEq)] pub struct SuccessfulOutput { /// Indicates if revert was requested. pub reverted: bool, /// How much gas was left after execution. pub gas_left: i64, /// Output data returned. pub output_data: Bytes, } impl From for Output { fn from( SuccessfulOutput { reverted, gas_left, output_data, }: SuccessfulOutput, ) -> Self { Self { status_code: if reverted { StatusCode::Revert } else { StatusCode::Success }, gas_left, output_data, create_address: None, } } } pub(crate) fn u256_to_address(v: U256) -> Address { H256(v.into()).into() } pub(crate) fn address_to_u256(v: Address) -> U256 { U256::from_big_endian(&v.0) } ================================================ FILE: src/continuation/interrupt.rs ================================================ use super::*; macro_rules! interrupt { ( $(#[$outer:meta])* $name:ident, $data:ty => $resume_with:ty) => { $(#[$outer])* pub struct $name { pub(crate) inner: ::core::pin::Pin< Box< dyn Coroutine< Yield = InterruptDataVariant, Resume = ResumeDataVariant, Return = Result, > + Send + Sync + Unpin, >, >, pub(crate) data: $data, } impl sealed::Sealed for $name {} impl Interrupt for $name { type InterruptData = $data; type ResumeData = $resume_with; fn data(&self) -> &Self::InterruptData { &self.data } fn resume(self, resume_data: $resume_with) -> InterruptVariant { resume_interrupt(self.inner, resume_data.into()) } } }; } interrupt! { /// EVM has just been created. Resume this interrupt to start execution. ExecutionStartInterrupt, () => () } interrupt! { /// New instruction has been encountered. InstructionStartInterrupt, Box => StateModifier } interrupt! { /// Does this account exist? AccountExistsInterrupt, AccountExists => AccountExistsStatus } interrupt! { /// Need this storage key. GetStorageInterrupt, GetStorage => StorageValue } interrupt! { /// Set this storage key. SetStorageInterrupt, SetStorage => StorageStatusInfo } interrupt! { /// Get balance of this account. GetBalanceInterrupt, GetBalance => Balance } interrupt! { /// Get code size of this account. GetCodeSizeInterrupt, GetCodeSize => CodeSize } interrupt! { /// Get code hash of this account. GetCodeHashInterrupt, GetCodeHash => CodeHash } interrupt! { /// Get code of this account. CopyCodeInterrupt, CopyCode => Code } interrupt! { /// Selfdestruct this account. SelfdestructInterrupt, Selfdestruct => () } interrupt! { /// Execute this message as a new call. CallInterrupt, Call => CallOutput } interrupt! { /// Get `TxContext` for this call. GetTxContextInterrupt, () => TxContextData } interrupt! { /// Get block hash for this account. GetBlockHashInterrupt, GetBlockHash => BlockHash } interrupt! { /// Emit log message. EmitLogInterrupt, EmitLog => () } interrupt! { /// Access this account and return its status. AccessAccountInterrupt, AccessAccount => AccessAccountStatus } interrupt! { /// Access this storage key and return its status. AccessStorageInterrupt, AccessStorage => AccessStorageStatus } /// Collection of all possible interrupts. Match on this to get the specific interrupt returned. #[derive(From)] pub enum InterruptVariant { InstructionStart(InstructionStartInterrupt), AccountExists(AccountExistsInterrupt), GetStorage(GetStorageInterrupt), SetStorage(SetStorageInterrupt), GetBalance(GetBalanceInterrupt), GetCodeSize(GetCodeSizeInterrupt), GetCodeHash(GetCodeHashInterrupt), CopyCode(CopyCodeInterrupt), Selfdestruct(SelfdestructInterrupt), Call(CallInterrupt), GetTxContext(GetTxContextInterrupt), GetBlockHash(GetBlockHashInterrupt), EmitLog(EmitLogInterrupt), AccessAccount(AccessAccountInterrupt), AccessStorage(AccessStorageInterrupt), Complete(Result), } ================================================ FILE: src/continuation/interrupt_data.rs ================================================ use super::*; #[derive(Debug)] pub struct InstructionStart { pub pc: usize, pub opcode: OpCode, pub state: ExecutionState, } #[derive(Debug)] pub struct AccountExists { pub address: Address, } #[derive(Debug)] pub struct GetStorage { pub address: Address, pub key: U256, } #[derive(Debug)] pub struct SetStorage { pub address: Address, pub key: U256, pub value: U256, } #[derive(Debug)] pub struct GetBalance { pub address: Address, } #[derive(Debug)] pub struct GetCodeSize { pub address: Address, } #[derive(Debug)] pub struct GetCodeHash { pub address: Address, } #[derive(Debug)] pub struct CopyCode { pub address: Address, pub offset: usize, pub max_size: usize, } #[derive(Debug)] pub struct Selfdestruct { pub address: Address, pub beneficiary: Address, } #[derive(Debug)] pub enum Call { Call(Message), Create(CreateMessage), } #[derive(Debug)] pub struct GetBlockHash { pub block_number: u64, } #[derive(Debug)] pub struct EmitLog { pub address: Address, pub data: Bytes, pub topics: ArrayVec, } #[derive(Debug)] pub struct AccessAccount { pub address: Address, } #[derive(Debug)] pub struct AccessStorage { pub address: Address, pub key: U256, } #[derive(Debug)] pub enum InterruptDataVariant { InstructionStart(Box), AccountExists(AccountExists), GetStorage(GetStorage), SetStorage(SetStorage), GetBalance(GetBalance), GetCodeSize(GetCodeSize), GetCodeHash(GetCodeHash), CopyCode(CopyCode), Selfdestruct(Selfdestruct), Call(Call), GetTxContext, GetBlockHash(GetBlockHash), EmitLog(EmitLog), AccessAccount(AccessAccount), AccessStorage(AccessStorage), } ================================================ FILE: src/continuation/mod.rs ================================================ use self::{interrupt::*, interrupt_data::*, resume_data::*}; use crate::{ common::*, host::{AccessStatus, StorageStatus, TxContext}, state::ExecutionState, *, }; use arrayvec::ArrayVec; use derive_more::From; use enum_as_inner::EnumAsInner; use ethereum_types::*; use genawaiter::{Coroutine, GeneratorState}; use std::{convert::Infallible, pin::Pin}; mod sealed { pub trait Sealed {} } /// Interrupts. pub mod interrupt; /// Data attached to interrupts. pub mod interrupt_data; /// Data required for resume. pub mod resume_data; /// Paused EVM with full state inside. pub trait Interrupt: sealed::Sealed { /// Interrupt data returned. type InterruptData; /// Data required to resume execution. type ResumeData; /// Get interrupt data. fn data(&self) -> &Self::InterruptData; /// Resume execution until the next interrupt. fn resume(self, resume_data: Self::ResumeData) -> InterruptVariant; } type InnerCoroutine = Pin< Box< dyn Coroutine< Yield = InterruptDataVariant, Resume = ResumeDataVariant, Return = Result, > + Send + Sync + Unpin, >, >; fn resume_interrupt(mut inner: InnerCoroutine, resume_data: ResumeDataVariant) -> InterruptVariant { match Pin::new(&mut *inner).resume_with(resume_data) { GeneratorState::Yielded(interrupt) => match interrupt { InterruptDataVariant::InstructionStart(data) => { InstructionStartInterrupt { inner, data }.into() } InterruptDataVariant::AccountExists(data) => { AccountExistsInterrupt { inner, data }.into() } InterruptDataVariant::GetStorage(data) => GetStorageInterrupt { inner, data }.into(), InterruptDataVariant::SetStorage(data) => SetStorageInterrupt { inner, data }.into(), InterruptDataVariant::GetBalance(data) => GetBalanceInterrupt { inner, data }.into(), InterruptDataVariant::GetCodeSize(data) => GetCodeSizeInterrupt { inner, data }.into(), InterruptDataVariant::GetCodeHash(data) => GetCodeHashInterrupt { inner, data }.into(), InterruptDataVariant::CopyCode(data) => CopyCodeInterrupt { inner, data }.into(), InterruptDataVariant::Selfdestruct(data) => { SelfdestructInterrupt { inner, data }.into() } InterruptDataVariant::Call(data) => CallInterrupt { inner, data }.into(), InterruptDataVariant::GetTxContext => GetTxContextInterrupt { inner, data: () }.into(), InterruptDataVariant::GetBlockHash(data) => { GetBlockHashInterrupt { inner, data }.into() } InterruptDataVariant::EmitLog(data) => EmitLogInterrupt { inner, data }.into(), InterruptDataVariant::AccessAccount(data) => { AccessAccountInterrupt { inner, data }.into() } InterruptDataVariant::AccessStorage(data) => { AccessStorageInterrupt { inner, data }.into() } }, GeneratorState::Complete(res) => InterruptVariant::Complete(res), } } ================================================ FILE: src/continuation/resume_data.rs ================================================ use super::*; use educe::Educe; use std::sync::Arc; pub type StateModifier = Option>; #[derive(Debug)] pub struct AccountExistsStatus { pub exists: bool, } #[derive(Debug)] pub struct Balance { pub balance: U256, } #[derive(Debug)] pub struct CodeSize { pub code_size: U256, } #[derive(Debug)] pub struct StorageValue { pub value: U256, } #[derive(Debug)] pub struct StorageStatusInfo { pub status: StorageStatus, } #[derive(Debug)] pub struct CodeHash { pub hash: U256, } #[derive(Debug)] pub struct BlockHash { pub hash: U256, } #[derive(Debug)] pub struct TxContextData { pub context: TxContext, } #[derive(Debug)] pub struct Code { pub code: Bytes, } #[derive(Debug)] pub struct CallOutput { pub output: Output, } #[derive(Debug)] pub struct AccessAccountStatus { pub status: AccessStatus, } #[derive(Debug)] pub struct AccessStorageStatus { pub status: AccessStatus, } /// All resumed data variants. #[derive(Educe, EnumAsInner, From)] #[educe(Debug)] pub(crate) enum ResumeDataVariant { #[from(ignore)] Empty, StateModifier(#[educe(Debug(false))] StateModifier), AccountExistsStatus(AccountExistsStatus), Balance(Balance), CodeSize(CodeSize), StorageValue(StorageValue), StorageStatusInfo(StorageStatusInfo), CodeHash(CodeHash), BlockHash(BlockHash), TxContextData(TxContextData), Code(Code), CallOutput(CallOutput), AccessAccountStatus(AccessAccountStatus), AccessStorageStatus(AccessStorageStatus), Done(Infallible), } impl From<()> for ResumeDataVariant { fn from(_: ()) -> Self { Self::Empty } } ================================================ FILE: src/evmc.rs ================================================ use crate::{common::*, host::*, tracing::*, AnalyzedCode}; use ::evmc_vm; use ::evmc_vm::{ffi::*, EvmcVm, ExecutionContext, ExecutionMessage, MessageFlags, MessageKind}; use arrayvec::ArrayVec; use bytes::Bytes; use ethereum_types::*; use evmc_vm::ExecutionResult; use std::convert::TryInto; pub(crate) trait Convert { type Into; fn convert(self) -> Self::Into; } impl Convert for Address { type Into = evmc_address; fn convert(self) -> Self::Into { evmc_address { bytes: self.to_fixed_bytes(), } } } impl Convert for H256 { type Into = evmc_bytes32; fn convert(self) -> Self::Into { evmc_bytes32 { bytes: self.to_fixed_bytes(), } } } impl Convert for U256 { type Into = evmc_uint256be; fn convert(self) -> Self::Into { evmc_uint256be { bytes: self.into() } } } impl From for AccessStatus { fn from(s: evmc_access_status) -> Self { match s { evmc_access_status::EVMC_ACCESS_COLD => Self::Cold, evmc_access_status::EVMC_ACCESS_WARM => Self::Warm, } } } impl From for ExecutionMessage { fn from(msg: Message) -> Self { let mut create2_salt = evmc_bytes32::default(); let kind = match msg.kind { crate::CallKind::Call => MessageKind::EVMC_CALL, crate::CallKind::DelegateCall => MessageKind::EVMC_DELEGATECALL, crate::CallKind::CallCode => MessageKind::EVMC_CALLCODE, crate::CallKind::Create => MessageKind::EVMC_CREATE, crate::CallKind::Create2 { salt } => { create2_salt = salt.convert(); MessageKind::EVMC_CREATE2 } }; let flags = if msg.is_static { MessageFlags::EVMC_STATIC as u32 } else { 0 }; ExecutionMessage::new( kind, flags, msg.depth, msg.gas, msg.recipient.convert(), msg.sender.convert(), (!msg.input_data.is_empty()).then(|| &*msg.input_data), msg.value.convert(), create2_salt, msg.code_address.convert(), ) } } impl Message { fn from_evmc(msg: &ExecutionMessage) -> Self { let kind = match msg.kind() { evmc_call_kind::EVMC_CALL => CallKind::Call, evmc_call_kind::EVMC_DELEGATECALL => CallKind::DelegateCall, evmc_call_kind::EVMC_CALLCODE => CallKind::CallCode, evmc_call_kind::EVMC_CREATE => CallKind::Create, evmc_call_kind::EVMC_CREATE2 => CallKind::Create2 { salt: msg.create2_salt().bytes.into(), }, }; Self { kind, is_static: msg.flags() == evmc_vm::ffi::evmc_flags::EVMC_STATIC as u32, depth: msg.depth(), gas: msg.gas(), recipient: msg.recipient().bytes.into(), sender: msg.sender().bytes.into(), input_data: msg .input() .map(|v| v.clone().into()) .unwrap_or_else(Bytes::new), value: msg.value().bytes.into(), code_address: msg.code_address().bytes.into(), } } } impl From for StatusCode { fn from(status: evmc_status_code) -> Self { match status { evmc_status_code::EVMC_SUCCESS => StatusCode::Success, evmc_status_code::EVMC_FAILURE => StatusCode::Failure, evmc_status_code::EVMC_REVERT => StatusCode::Revert, evmc_status_code::EVMC_OUT_OF_GAS => StatusCode::OutOfGas, evmc_status_code::EVMC_INVALID_INSTRUCTION => StatusCode::InvalidInstruction, evmc_status_code::EVMC_UNDEFINED_INSTRUCTION => StatusCode::UndefinedInstruction, evmc_status_code::EVMC_STACK_OVERFLOW => StatusCode::StackOverflow, evmc_status_code::EVMC_STACK_UNDERFLOW => StatusCode::StackUnderflow, evmc_status_code::EVMC_BAD_JUMP_DESTINATION => StatusCode::BadJumpDestination, evmc_status_code::EVMC_INVALID_MEMORY_ACCESS => StatusCode::InvalidMemoryAccess, evmc_status_code::EVMC_CALL_DEPTH_EXCEEDED => StatusCode::CallDepthExceeded, evmc_status_code::EVMC_STATIC_MODE_VIOLATION => StatusCode::StaticModeViolation, evmc_status_code::EVMC_PRECOMPILE_FAILURE => StatusCode::PrecompileFailure, evmc_status_code::EVMC_CONTRACT_VALIDATION_FAILURE => { StatusCode::ContractValidationFailure } evmc_status_code::EVMC_ARGUMENT_OUT_OF_RANGE => StatusCode::ArgumentOutOfRange, evmc_status_code::EVMC_WASM_UNREACHABLE_INSTRUCTION => { StatusCode::InternalError("WasmUnreachableInstruction".into()) } evmc_status_code::EVMC_WASM_TRAP => StatusCode::InternalError("WasmTrap".into()), evmc_status_code::EVMC_INSUFFICIENT_BALANCE => StatusCode::InsufficientBalance, evmc_status_code::EVMC_INTERNAL_ERROR => StatusCode::InternalError(String::new()), evmc_status_code::EVMC_REJECTED => StatusCode::InternalError("Rejected".into()), evmc_status_code::EVMC_OUT_OF_MEMORY => StatusCode::InternalError("OutOfMemory".into()), } } } impl From for evmc_status_code { fn from(status: StatusCode) -> Self { match status { StatusCode::Success => evmc_status_code::EVMC_SUCCESS, StatusCode::Failure => evmc_status_code::EVMC_FAILURE, StatusCode::Revert => evmc_status_code::EVMC_REVERT, StatusCode::OutOfGas => evmc_status_code::EVMC_OUT_OF_GAS, StatusCode::InvalidInstruction => evmc_status_code::EVMC_INVALID_INSTRUCTION, StatusCode::UndefinedInstruction => evmc_status_code::EVMC_UNDEFINED_INSTRUCTION, StatusCode::StackOverflow => evmc_status_code::EVMC_STACK_OVERFLOW, StatusCode::StackUnderflow => evmc_status_code::EVMC_STACK_UNDERFLOW, StatusCode::BadJumpDestination => evmc_status_code::EVMC_BAD_JUMP_DESTINATION, StatusCode::InvalidMemoryAccess => evmc_status_code::EVMC_INVALID_MEMORY_ACCESS, StatusCode::CallDepthExceeded => evmc_status_code::EVMC_CALL_DEPTH_EXCEEDED, StatusCode::StaticModeViolation => evmc_status_code::EVMC_STATIC_MODE_VIOLATION, StatusCode::PrecompileFailure => evmc_status_code::EVMC_PRECOMPILE_FAILURE, StatusCode::ContractValidationFailure => { evmc_status_code::EVMC_CONTRACT_VALIDATION_FAILURE } StatusCode::ArgumentOutOfRange => evmc_status_code::EVMC_ARGUMENT_OUT_OF_RANGE, StatusCode::InsufficientBalance => evmc_status_code::EVMC_INSUFFICIENT_BALANCE, StatusCode::InternalError(_) => evmc_status_code::EVMC_INTERNAL_ERROR, } } } impl<'a> Host for ExecutionContext<'a> { fn account_exists(&self, address: Address) -> bool { ExecutionContext::account_exists(self, &address.convert()) } fn get_storage(&self, address: Address, key: U256) -> U256 { ExecutionContext::get_storage(self, &address.convert(), &key.convert()) .bytes .into() } fn set_storage(&mut self, address: Address, key: U256, value: U256) -> StorageStatus { match ExecutionContext::set_storage( self, &address.convert(), &key.convert(), &value.convert(), ) { evmc_storage_status::EVMC_STORAGE_UNCHANGED => StorageStatus::Unchanged, evmc_storage_status::EVMC_STORAGE_MODIFIED => StorageStatus::Modified, evmc_storage_status::EVMC_STORAGE_MODIFIED_AGAIN => StorageStatus::ModifiedAgain, evmc_storage_status::EVMC_STORAGE_ADDED => StorageStatus::Added, evmc_storage_status::EVMC_STORAGE_DELETED => StorageStatus::Deleted, } } fn get_balance(&self, address: Address) -> U256 { ExecutionContext::get_balance(self, &address.convert()) .bytes .into() } fn get_code_size(&self, address: Address) -> U256 { ExecutionContext::get_code_size(self, &address.convert()).into() } fn get_code_hash(&self, address: Address) -> U256 { ExecutionContext::get_code_hash(self, &address.convert()) .bytes .into() } fn copy_code(&self, address: Address, offset: usize, buffer: &mut [u8]) -> usize { ExecutionContext::copy_code(self, &address.convert(), offset, buffer) } fn selfdestruct(&mut self, address: Address, beneficiary: Address) { ExecutionContext::selfdestruct(self, &address.convert(), &beneficiary.convert()) } fn call(&mut self, msg: &Message) -> Output { let execution_result = ExecutionContext::call(self, &msg.clone().into()); Output { status_code: execution_result.status_code().into(), gas_left: execution_result.gas_left(), output_data: execution_result .output() .map(|v| v.to_vec().into()) .unwrap_or_else(Bytes::new), create_address: execution_result.create_address().map(|a| a.bytes.into()), } } fn get_tx_context(&self) -> TxContext { let c = ExecutionContext::get_tx_context(self); TxContext { tx_gas_price: c.tx_gas_price.bytes.into(), tx_origin: c.tx_origin.bytes.into(), block_coinbase: c.block_coinbase.bytes.into(), block_number: c.block_number.try_into().unwrap(), block_timestamp: c.block_timestamp.try_into().unwrap(), block_gas_limit: c.block_gas_limit.try_into().unwrap(), block_difficulty: c.block_difficulty.bytes.into(), chain_id: c.chain_id.bytes.into(), block_base_fee: c.block_base_fee.bytes.into(), } } fn get_block_hash(&self, block_number: u64) -> U256 { ExecutionContext::get_block_hash(self, block_number.try_into().unwrap()) .bytes .into() } fn emit_log(&mut self, address: Address, data: &[u8], topics: &[U256]) { ExecutionContext::emit_log( self, &address.convert(), data, &topics .iter() .map(|topic| topic.convert()) .collect::>(), ) } fn access_account(&mut self, address: Address) -> AccessStatus { ExecutionContext::access_account(self, &address.convert()).into() } fn access_storage(&mut self, address: Address, key: U256) -> AccessStatus { ExecutionContext::access_storage(self, &address.convert(), &key.convert()).into() } } impl From for Revision { fn from(rev: evmc_vm::Revision) -> Self { match rev { evmc_revision::EVMC_FRONTIER => Revision::Frontier, evmc_revision::EVMC_HOMESTEAD => Revision::Homestead, evmc_revision::EVMC_TANGERINE_WHISTLE => Revision::Tangerine, evmc_revision::EVMC_SPURIOUS_DRAGON => Revision::Spurious, evmc_revision::EVMC_BYZANTIUM => Revision::Byzantium, evmc_revision::EVMC_CONSTANTINOPLE => Revision::Constantinople, evmc_revision::EVMC_PETERSBURG => Revision::Petersburg, evmc_revision::EVMC_ISTANBUL => Revision::Istanbul, evmc_revision::EVMC_BERLIN => Revision::Berlin, evmc_revision::EVMC_LONDON => Revision::London, evmc_revision::EVMC_SHANGHAI => Revision::Shanghai, } } } impl From for evmc_vm::Revision { fn from(rev: Revision) -> Self { match rev { Revision::Frontier => evmc_revision::EVMC_FRONTIER, Revision::Homestead => evmc_revision::EVMC_HOMESTEAD, Revision::Tangerine => evmc_revision::EVMC_TANGERINE_WHISTLE, Revision::Spurious => evmc_revision::EVMC_SPURIOUS_DRAGON, Revision::Byzantium => evmc_revision::EVMC_BYZANTIUM, Revision::Constantinople => evmc_revision::EVMC_CONSTANTINOPLE, Revision::Petersburg => evmc_revision::EVMC_PETERSBURG, Revision::Istanbul => evmc_revision::EVMC_ISTANBUL, Revision::Berlin => evmc_revision::EVMC_BERLIN, Revision::London => evmc_revision::EVMC_LONDON, Revision::Shanghai => evmc_revision::EVMC_SHANGHAI, } } } #[evmc_declare::evmc_declare_vm("evmodin", "evm", "0.1.0")] pub struct EvmOdin; impl EvmcVm for EvmOdin { fn init() -> Self { Self } fn execute<'a>( &self, revision: evmc_vm::Revision, code: &'a [u8], message: &'a ExecutionMessage, context: Option<&'a mut ExecutionContext<'a>>, ) -> ExecutionResult { let code = AnalyzedCode::analyze(code); let output = if let Some(context) = context { code.execute( context, &mut NoopTracer, None, Message::from_evmc(message), revision.into(), ) } else { code.execute( &mut DummyHost, &mut NoopTracer, None, Message::from_evmc(message), revision.into(), ) }; ExecutionResult::new( output.status_code.clone().into(), output.gas_left, (!output.output_data.is_empty()).then(|| &*output.output_data), ) } } ================================================ FILE: src/host.rs ================================================ use crate::common::{Message, Output}; use ethereum_types::*; /// 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. pub trait Host { /// Check if an account exists. fn account_exists(&self, address: Address) -> bool; /// Get value of a storage key. /// /// Returns `Ok(U256::zero())` if does not exist. fn get_storage(&self, address: Address, key: U256) -> U256; /// Set value of a storage key. fn set_storage(&mut self, address: Address, key: U256, value: U256) -> StorageStatus; /// Get balance of an account. /// /// Returns `Ok(0)` if account does not exist. fn get_balance(&self, address: Address) -> U256; /// Get code size of an account. /// /// Returns `Ok(0)` if account does not exist. fn get_code_size(&self, address: Address) -> U256; /// Get code hash of an account. /// /// Returns `Ok(0)` if account does not exist. fn get_code_hash(&self, address: Address) -> U256; /// Copy code of an account. /// /// Returns `Ok(0)` if offset is invalid. fn copy_code(&self, address: Address, offset: usize, buffer: &mut [u8]) -> usize; /// Self-destruct account. fn selfdestruct(&mut self, address: Address, beneficiary: Address); /// Call to another account. fn call(&mut self, msg: &Message) -> Output; /// Retrieve transaction context. fn get_tx_context(&self) -> TxContext; /// Get block hash. /// /// Returns `Ok(U256::zero())` if block does not exist. fn get_block_hash(&self, block_number: u64) -> U256; /// Emit a log. fn emit_log(&mut self, address: Address, data: &[u8], topics: &[U256]); /// Mark account as warm, return previous access status. /// /// Returns `Ok(AccessStatus::Cold)` if account does not exist. fn access_account(&mut self, address: Address) -> AccessStatus; /// Mark storage key as warm, return previous access status. /// /// Returns `Ok(AccessStatus::Cold)` if account does not exist. fn access_storage(&mut self, address: Address, key: U256) -> AccessStatus; } /// Host that does not support any ops. pub struct DummyHost; impl Host for DummyHost { fn account_exists(&self, _: Address) -> bool { todo!() } fn get_storage(&self, _: Address, _: U256) -> U256 { todo!() } fn set_storage(&mut self, _: Address, _: U256, _: U256) -> StorageStatus { todo!() } fn get_balance(&self, _: Address) -> U256 { todo!() } fn get_code_size(&self, _: Address) -> U256 { todo!() } fn get_code_hash(&self, _: Address) -> U256 { todo!() } fn copy_code(&self, _: Address, _: usize, _: &mut [u8]) -> usize { todo!() } fn selfdestruct(&mut self, _: Address, _: Address) { todo!() } fn call(&mut self, _: &Message) -> Output { todo!() } fn get_tx_context(&self) -> TxContext { todo!() } fn get_block_hash(&self, _: u64) -> U256 { todo!() } fn emit_log(&mut self, _: Address, _: &[u8], _: &[U256]) { todo!() } fn access_account(&mut self, _: Address) -> AccessStatus { todo!() } fn access_storage(&mut self, _: Address, _: U256) -> AccessStatus { todo!() } } ================================================ FILE: src/instructions/arithmetic.rs ================================================ use crate::{state::*, Revision, StatusCode}; use core::convert::TryInto; use ethereum_types::{U256, U512}; use i256::I256; pub(crate) fn add(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a.overflowing_add(b).0); } pub(crate) fn mul(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a.overflowing_mul(b).0); } pub(crate) fn sub(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a.overflowing_sub(b).0); } 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 }); } 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()); } 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); } 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); } 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); } 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); } 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) -> Result<(), 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 Err(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); Ok(()) } pub(crate) fn signextend(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); let v = if a < U256::from(32) { // `low_u32` works since op1 < 32 let bit_index = (8 * a.low_u32() + 7) as usize; let bit = b.bit(bit_index); let mask = (U256::one() << bit_index) - U256::one(); if bit { b | !mask } else { b & mask } } else { b }; stack.push(v); } ================================================ FILE: src/instructions/bitwise.rs ================================================ use crate::state::Stack; use ethereum_types::U256; use i256::{Sign, I256}; 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) } 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) } 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) } 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) } ================================================ FILE: src/instructions/boolean.rs ================================================ use crate::state::*; use ethereum_types::U256; use i256::I256; 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() }) } 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() }) } 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() }) } 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() }) } 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() }) } pub(crate) fn iszero(stack: &mut Stack) { let a = stack.pop(); stack.push(if a.is_zero() { U256::one() } else { U256::zero() }) } pub(crate) fn and(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a & b); } pub(crate) fn or(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a | b); } pub(crate) fn xor(stack: &mut Stack) { let a = stack.pop(); let b = stack.pop(); stack.push(a ^ b); } pub(crate) fn not(stack: &mut Stack) { let a = stack.get_mut(0); *a = !*a; } ================================================ FILE: src/instructions/call.rs ================================================ #[doc(hidden)] #[macro_export] macro_rules! do_call { ($co:expr, $state:expr, $kind:expr, $is_static:expr) => {{ use std::cmp::min; use $crate::{ common::u256_to_address, continuation::{interrupt_data::*, resume_data::*}, host::AccessStatus, instructions::{memory::MemoryRegion, properties::*}, CallKind, Message, }; 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 && ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address: dst, })) .await, ) .unwrap() .status == AccessStatus::Cold { $state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } let input_region = memory::verify_memory_region($state, input_offset, input_size) .map_err(|_| StatusCode::OutOfGas)?; let output_region = memory::verify_memory_region($state, output_offset, output_size) .map_err(|_| StatusCode::OutOfGas)?; let mut msg = Message { kind: $kind, is_static: $is_static || $state.message.is_static, depth: $state.message.depth + 1, recipient: if matches!($kind, CallKind::Call) { dst } else { $state.message.recipient }, code_address: dst, sender: if matches!($kind, CallKind::DelegateCall) { $state.message.sender } else { $state.message.recipient }, 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 Err(StatusCode::StaticModeViolation); } if (has_value || $state.evm_revision < Revision::Spurious) && !ResumeDataVariant::into_account_exists_status( $co.yield_(InterruptDataVariant::AccountExists(AccountExists { address: dst, })) .await, ) .unwrap() .exists { cost += 25000; } } $state.gas_left -= cost; if $state.gas_left < 0 { return Err(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 Err(StatusCode::OutOfGas); } if has_value { msg.gas += 2300; // Add stipend. $state.gas_left += 2300; } $state.return_data.clear(); if $state.message.depth < 1024 && !(has_value && ResumeDataVariant::into_balance( $co.yield_(InterruptDataVariant::GetBalance(GetBalance { address: $state.message.recipient, })) .await, ) .unwrap() .balance < value) { let msg_gas = msg.gas; let result = ResumeDataVariant::into_call_output( $co.yield_(InterruptDataVariant::Call(Call::Call(msg))) .await, ) .unwrap() .output; $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; } }}; } #[doc(hidden)] #[macro_export] macro_rules! do_create { ($co:expr, $state:expr, $create2:expr) => {{ use ethereum_types::U256; use $crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, CreateMessage, }; if $state.message.is_static { return Err(StatusCode::StaticModeViolation); } let endowment = $state.stack.pop(); let init_code_offset = $state.stack.pop(); let init_code_size = $state.stack.pop(); let region = memory::verify_memory_region($state, init_code_offset, init_code_size) .map_err(|_| StatusCode::OutOfGas)?; let salt = 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 Err(StatusCode::OutOfGas); } } Some(salt) } else { None }; $state.stack.push(U256::zero()); $state.return_data.clear(); if $state.message.depth < 1024 && !(!endowment.is_zero() && ResumeDataVariant::into_balance( $co.yield_(InterruptDataVariant::GetBalance(GetBalance { address: $state.message.recipient, })) .await, ) .unwrap() .balance < endowment) { let msg = CreateMessage { gas: if $state.evm_revision >= Revision::Tangerine { $state.gas_left - $state.gas_left / 64 } else { $state.gas_left }, salt, initcode: 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.recipient, depth: $state.message.depth + 1, endowment, }; let msg_gas = msg.gas; let result = ResumeDataVariant::into_call_output( $co.yield_(InterruptDataVariant::Call(Call::Create(msg))) .await, ) .unwrap() .output; $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.expect("expected create address")); } } }}; } ================================================ FILE: src/instructions/control.rs ================================================ use crate::state::ExecutionState; use crate::{interpreter::JumpdestMap, StatusCode}; use ethereum_types::U256; pub(crate) fn ret(state: &mut ExecutionState) -> Result<(), StatusCode> { let offset = *state.stack.get(0); let size = *state.stack.get(1); if let Some(region) = super::memory::verify_memory_region(state, offset, size) .map_err(|_| StatusCode::OutOfGas)? { state.output_data = state.memory[region.offset..region.offset + region.size.get()] .to_vec() .into(); } Ok(()) } pub(crate) fn op_jump( state: &mut ExecutionState, jumpdest_map: &JumpdestMap, ) -> Result { let dst = state.stack.pop(); if !jumpdest_map.contains(dst) { return Err(StatusCode::BadJumpDestination); } Ok(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()); } ================================================ FILE: src/instructions/external.rs ================================================ use crate::{common::address_to_u256, host::*, state::ExecutionState}; use ethereum_types::U256; pub(crate) fn address(state: &mut ExecutionState) { state.stack.push(address_to_u256(state.message.recipient)); } pub(crate) fn caller(state: &mut ExecutionState) { state.stack.push(address_to_u256(state.message.sender)); } pub(crate) fn callvalue(state: &mut ExecutionState) { state.stack.push(state.message.value); } #[doc(hidden)] #[macro_export] macro_rules! balance { ($co:expr, $state:expr) => { use crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::*, }; let address = u256_to_address($state.stack.pop()); if $state.evm_revision >= Revision::Berlin { let access_status = ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address, })) .await, ) .unwrap() .status; if access_status == AccessStatus::Cold { $state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } } let balance = ResumeDataVariant::into_balance( $co.yield_(InterruptDataVariant::GetBalance(GetBalance { address })) .await, ) .unwrap() .balance; $state.stack.push(balance); }; } #[doc(hidden)] #[macro_export] macro_rules! extcodesize { ($co:expr, $state:expr) => { use crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::*, }; let address = u256_to_address($state.stack.pop()); if $state.evm_revision >= Revision::Berlin { let access_account = ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address, })) .await, ) .unwrap() .status; if access_account == AccessStatus::Cold { $state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } } let code_size = ResumeDataVariant::into_code_size( $co.yield_(InterruptDataVariant::GetCodeSize(GetCodeSize { address })) .await, ) .unwrap() .code_size; $state.stack.push(code_size); }; } #[doc(hidden)] #[macro_export] macro_rules! push_txcontext { ($co:expr, $state:expr, $accessor:expr) => { use $crate::continuation::{interrupt_data::*, resume_data::*}; let tx_context = ResumeDataVariant::into_tx_context_data( $co.yield_(InterruptDataVariant::GetTxContext).await, ) .unwrap() .context; $state.stack.push($accessor(tx_context)); }; } pub(crate) fn origin_accessor(tx_context: TxContext) -> U256 { address_to_u256(tx_context.tx_origin) } pub(crate) fn coinbase_accessor(tx_context: TxContext) -> U256 { address_to_u256(tx_context.block_coinbase) } pub(crate) fn gasprice_accessor(tx_context: TxContext) -> U256 { tx_context.tx_gas_price } pub(crate) fn timestamp_accessor(tx_context: TxContext) -> U256 { tx_context.block_timestamp.into() } pub(crate) fn number_accessor(tx_context: TxContext) -> U256 { tx_context.block_number.into() } pub(crate) fn gaslimit_accessor(tx_context: TxContext) -> U256 { tx_context.block_gas_limit.into() } pub(crate) fn difficulty_accessor(tx_context: TxContext) -> U256 { tx_context.block_difficulty } pub(crate) fn chainid_accessor(tx_context: TxContext) -> U256 { tx_context.chain_id } pub(crate) fn basefee_accessor(tx_context: TxContext) -> U256 { tx_context.block_base_fee } #[doc(hidden)] #[macro_export] macro_rules! selfbalance { ($co:expr, $state:expr) => {{ use $crate::continuation::{interrupt_data::*, resume_data::*}; let balance = ResumeDataVariant::into_balance( $co.yield_(InterruptDataVariant::GetBalance(GetBalance { address: $state.message.recipient, })) .await, ) .unwrap() .balance; $state.stack.push(balance); }}; } #[doc(hidden)] #[macro_export] macro_rules! blockhash { ($co:expr, $state:expr) => { use $crate::continuation::{interrupt_data::*, resume_data::*}; let number = $state.stack.pop(); let upper_bound = ResumeDataVariant::into_tx_context_data( $co.yield_(InterruptDataVariant::GetTxContext).await, ) .unwrap() .context .block_number; let lower_bound = upper_bound.saturating_sub(256); let mut header = U256::zero(); if number <= u64::MAX.into() { let n = number.as_u64(); if (lower_bound..upper_bound).contains(&n) { header = ResumeDataVariant::into_block_hash( $co.yield_(InterruptDataVariant::GetBlockHash(GetBlockHash { block_number: n, })) .await, ) .unwrap() .hash; } } $state.stack.push(header); }; } #[doc(hidden)] #[macro_export] macro_rules! do_log { ($co:expr, $state:expr, $num_topics:expr) => {{ use arrayvec::ArrayVec; use $crate::continuation::{interrupt_data::*, resume_data::*}; if $state.message.is_static { return Err(StatusCode::StaticModeViolation); } let offset = $state.stack.pop(); let size = $state.stack.pop(); let region = memory::verify_memory_region($state, offset, size).map_err(|_| StatusCode::OutOfGas)?; if let Some(region) = ®ion { let cost = region.size.get() as i64 * 8; $state.gas_left -= cost; if cost < 0 { return Err(StatusCode::OutOfGas); } } let mut topics = ArrayVec::new(); for _ in 0..$num_topics { topics.push($state.stack.pop()); } let data = if let Some(region) = region { &$state.memory[region.offset..region.offset + region.size.get()] } else { &[] }; let r = $co .yield_(InterruptDataVariant::EmitLog(EmitLog { address: $state.message.recipient, data: data.to_vec().into(), topics, })) .await; assert!(matches!(r, ResumeDataVariant::Empty)); }}; } #[doc(hidden)] #[macro_export] macro_rules! sload { ($co:expr, $state:expr) => {{ use $crate::{ continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::{COLD_SLOAD_COST, WARM_STORAGE_READ_COST}, }; let key = $state.stack.pop(); if $state.evm_revision >= Revision::Berlin { let access_status = ResumeDataVariant::into_access_storage_status( $co.yield_(InterruptDataVariant::AccessStorage(AccessStorage { address: $state.message.recipient, key, })) .await, ) .unwrap() .status; if access_status == 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 Err(StatusCode::OutOfGas); } } } let storage = ResumeDataVariant::into_storage_value( $co.yield_(InterruptDataVariant::GetStorage(GetStorage { address: $state.message.recipient, key, })) .await, ) .unwrap() .value; $state.stack.push(storage); }}; } #[doc(hidden)] #[macro_export] macro_rules! sstore { ($co:expr, $state:expr) => {{ use $crate::{ continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::{COLD_SLOAD_COST, WARM_STORAGE_READ_COST}, }; if $state.message.is_static { return Err(StatusCode::StaticModeViolation); } if $state.evm_revision >= Revision::Istanbul && $state.gas_left <= 2300 { return Err(StatusCode::OutOfGas); } let key = $state.stack.pop(); let value = $state.stack.pop(); let mut cost = 0; if $state.evm_revision >= Revision::Berlin { let access_status = ResumeDataVariant::into_access_storage_status( $co.yield_(InterruptDataVariant::AccessStorage(AccessStorage { address: $state.message.recipient, key, })) .await, ) .unwrap() .status; if access_status == AccessStatus::Cold { cost = COLD_SLOAD_COST; } } let status = ResumeDataVariant::into_storage_status_info( $co.yield_(InterruptDataVariant::SetStorage(SetStorage { address: $state.message.recipient, key, value, })) .await, ) .unwrap() .status; 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 Err(StatusCode::OutOfGas); } }}; } #[doc(hidden)] #[macro_export] macro_rules! selfdestruct { ($co:expr, $state:expr) => {{ use crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::*, }; if $state.message.is_static { return Err(StatusCode::StaticModeViolation); } let beneficiary = u256_to_address($state.stack.pop()); if $state.evm_revision >= Revision::Berlin { let access_status = ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address: beneficiary, })) .await, ) .unwrap() .status; if access_status == AccessStatus::Cold { $state.gas_left -= i64::from(COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } } if $state.evm_revision >= Revision::Tangerine && ($state.evm_revision == Revision::Tangerine || !{ ResumeDataVariant::into_balance( $co.yield_(InterruptDataVariant::GetBalance(GetBalance { address: $state.message.recipient, })) .await, ) .unwrap() .balance .is_zero() }) { // After TANGERINE_WHISTLE apply additional cost of // sending value to a non-existing account. if !ResumeDataVariant::into_account_exists_status( $co.yield_(InterruptDataVariant::AccountExists(AccountExists { address: beneficiary, })) .await, ) .unwrap() .exists { $state.gas_left -= 25000; if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } } assert!(matches!( $co.yield_(InterruptDataVariant::Selfdestruct(Selfdestruct { address: $state.message.recipient, beneficiary, })) .await, ResumeDataVariant::Empty )); }}; } #[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")) ); } } ================================================ FILE: src/instructions/instruction_table.rs ================================================ 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] } ================================================ FILE: src/instructions/memory.rs ================================================ use crate::{common::*, state::*}; use ethereum_types::U256; use sha3::{Digest, Keccak256}; use std::{cmp::min, num::NonZeroUsize}; pub(crate) 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. pub(crate) fn num_words(size_in_bytes: usize) -> i64 { ((size_in_bytes as i64) + (WORD_SIZE - 1)) / WORD_SIZE } pub(crate) fn mload(state: &mut ExecutionState) -> Result<(), StatusCode> { let index = state.stack.pop(); let region = verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) .map_err(|_| StatusCode::OutOfGas)?; let value = U256::from_big_endian(&state.memory[region.offset..region.offset + region.size.get()]); state.stack.push(value); Ok(()) } pub(crate) fn mstore(state: &mut ExecutionState) -> Result<(), StatusCode> { let index = state.stack.pop(); let value = state.stack.pop(); let region = verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) .map_err(|_| StatusCode::OutOfGas)?; let mut b = [0; 32]; value.to_big_endian(&mut b); state.memory[region.offset..region.offset + 32].copy_from_slice(&b); Ok(()) } pub(crate) fn mstore8(state: &mut ExecutionState) -> Result<(), StatusCode> { let index = state.stack.pop(); let value = state.stack.pop(); let region = verify_memory_region_u64(state, index, NonZeroUsize::new(1).unwrap()) .map_err(|_| StatusCode::OutOfGas)?; let value = (value.low_u32() & 0xff) as u8; state.memory[region.offset] = value; Ok(()) } pub(crate) fn msize(state: &mut ExecutionState) { state.stack.push(state.memory.len().into()); } 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(MemoryRegion { offset: offset.as_usize(), size, }) } pub(crate) struct MemoryRegion { pub offset: usize, pub size: NonZeroUsize, } 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()).map(Some) } pub(crate) fn calldatacopy(state: &mut ExecutionState) -> Result<(), StatusCode> { let mem_index = state.stack.pop(); let input_index = state.stack.pop(); let size = state.stack.pop(); let region = verify_memory_region(state, mem_index, size).map_err(|_| 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 Err(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); } } Ok(()) } pub(crate) fn keccak256(state: &mut ExecutionState) -> Result<(), StatusCode> { let index = state.stack.pop(); let size = state.stack.pop(); let region = verify_memory_region(state, index, size).map_err(|_| 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 Err(StatusCode::OutOfGas); } &state.memory[region.offset..region.offset + region.size.get()] } else { &[] }, ))); Ok(()) } pub(crate) fn codesize(stack: &mut Stack, code: &[u8]) { stack.push(code.len().into()) } pub(crate) fn codecopy(state: &mut ExecutionState, code: &[u8]) -> Result<(), StatusCode> { // TODO: Similar to calldatacopy(). let mem_index = state.stack.pop(); let input_index = state.stack.pop(); let size = state.stack.pop(); let region = verify_memory_region(state, mem_index, size).map_err(|_| 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 Err(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); } } Ok(()) } #[doc(hidden)] #[macro_export] macro_rules! extcodecopy { ($co:expr, $state:expr) => { use crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::{memory::*, properties::*}, }; use core::cmp::min; 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 = verify_memory_region($state, mem_index, size).map_err(|_| 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 Err(StatusCode::OutOfGas); } } if $state.evm_revision >= Revision::Berlin && ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address: addr, })) .await, ) .unwrap() .status == AccessStatus::Cold { $state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } if let Some(region) = region { let src = min(U256::from(MAX_BUFFER_SIZE), input_index).as_usize(); let r = &mut $state.memory[region.offset..region.offset + region.size.get()]; let code = ResumeDataVariant::into_code( $co.yield_(InterruptDataVariant::CopyCode(CopyCode { address: addr, offset: src, max_size: r.len(), })) .await, ) .unwrap() .code; r[..code.len()].copy_from_slice(&code); if region.size.get() - code.len() > 0 { $state.memory[region.offset + code.len()..region.offset + region.size.get()] .fill(0); } } }; } pub(crate) fn returndatasize(state: &mut ExecutionState) { state.stack.push(state.return_data.len().into()); } pub(crate) fn returndatacopy(state: &mut ExecutionState) -> Result<(), StatusCode> { let mem_index = state.stack.pop(); let input_index = state.stack.pop(); let size = state.stack.pop(); let region = verify_memory_region(state, mem_index, size).map_err(|_| StatusCode::OutOfGas)?; if input_index > U256::from(state.return_data.len()) { return Err(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 Err(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 Err(StatusCode::OutOfGas); } state.memory[region.offset..region.offset + region.size.get()] .copy_from_slice(&state.return_data[src..src + region.size.get()]); } Ok(()) } #[doc(hidden)] #[macro_export] macro_rules! extcodehash { ($co:expr, $state:expr) => { use crate::{ common::*, continuation::{interrupt_data::*, resume_data::*}, host::*, instructions::properties::*, }; let addr = u256_to_address($state.stack.pop()); if $state.evm_revision >= Revision::Berlin && ResumeDataVariant::into_access_account_status( $co.yield_(InterruptDataVariant::AccessAccount(AccessAccount { address: addr, })) .await, ) .unwrap() .status == AccessStatus::Cold { $state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); if $state.gas_left < 0 { return Err(StatusCode::OutOfGas); } } $state.stack.push( ResumeDataVariant::into_code_hash( $co.yield_(InterruptDataVariant::GetCodeHash(GetCodeHash { address: addr, })) .await, ) .unwrap() .hash, ); }; } ================================================ FILE: src/instructions/mod.rs ================================================ 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; pub use properties::PROPERTIES; ================================================ FILE: src/instructions/properties.rs ================================================ 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 }); static SHANGHAI_GAS_COSTS: Lazy<[Option; 256]> = Lazy::new(|| *LONDON_GAS_COSTS); 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, Revision::Shanghai => &SHANGHAI_GAS_COSTS, } } ================================================ FILE: src/instructions/stack_manip.rs ================================================ use crate::state::*; use ethereum_types::U256; pub(crate) fn push(stack: &mut Stack, code: &[u8], push_len: usize) { stack.push(U256::from_big_endian(&code[..push_len])); } pub(crate) fn dup(stack: &mut Stack, height: usize) { stack.push(*stack.get(height - 1)); } pub(crate) fn swap(stack: &mut Stack, height: usize) { stack.swap_top(height); } pub(crate) fn pop(stack: &mut Stack) { stack.pop(); } ================================================ FILE: src/interpreter.rs ================================================ use self::instruction_table::*; use crate::{ common::*, continuation::{interrupt::*, interrupt_data::*, resume_data::*, *}, instructions::{control::*, stack_manip::*, *}, state::*, tracing::Tracer, *, }; use ethereum_types::U256; use genawaiter::sync::*; use std::sync::Arc; fn check_requirements( instruction_table: &InstructionTable, state: &mut ExecutionState, op: OpCode, ) -> Result<(), StatusCode> { let metrics = &instruction_table[op.to_usize()].ok_or(StatusCode::UndefinedInstruction)?; state.gas_left -= metrics.gas_cost as i64; if state.gas_left < 0 { return Err(StatusCode::OutOfGas); } let stack_size = state.stack.len(); if stack_size == Stack::limit() { if metrics.can_overflow_stack { return Err(StatusCode::StackOverflow); } } else if stack_size < metrics.stack_height_required.into() { return Err(StatusCode::StackUnderflow); } Ok(()) } #[derive(Clone, Debug)] pub struct JumpdestMap(Arc<[bool]>); impl JumpdestMap { pub fn contains(&self, dst: U256) -> bool { dst < self.0.len().into() && self.0[dst.as_usize()] } } /// Code with analysis. #[derive(Clone, Debug)] pub struct AnalyzedCode { jumpdest_map: JumpdestMap, code: Bytes, padded_code: Bytes, } impl AnalyzedCode { /// Analyze code and prepare it for execution. pub fn analyze(code: impl Into>) -> Self { let code = code.into(); 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() + 2, _ => 1, } } let code_len = code.len(); let mut padded_code = code; padded_code.resize(i + 1, OpCode::STOP.to_u8()); let jumpdest_map = JumpdestMap(jumpdest_map.into()); let padded_code = Bytes::from(padded_code); let mut code = padded_code.clone(); code.truncate(code_len); Self { jumpdest_map, code, padded_code, } } /// Execute analyzed EVM bytecode using provided `Host` context. Optionally modify the state after each instruction using provided closure. pub fn execute( &self, host: &mut H, tracer: &mut T, state_modifier: StateModifier, message: Message, revision: Revision, ) -> Output { if !T::DUMMY { tracer.notify_execution_start(revision, message.clone(), self.code.clone()); } let output = self .execute_resumable(!T::DUMMY || state_modifier.is_some(), message, revision) .run_to_completion_with_host(host, tracer, state_modifier); if !T::DUMMY { tracer.notify_execution_end(&output); } output } /// Execute in resumable EVM. pub fn execute_resumable( &self, trace: bool, message: Message, revision: Revision, ) -> ExecutionStartInterrupt { let code = self.clone(); let inner = Box::pin(Gen::new(move |co| { interpreter_producer(co, code, ExecutionState::new(message, revision), trace) })); ExecutionStartInterrupt { inner, data: () } } } impl ExecutionStartInterrupt { pub fn run_to_completion_with_host( self, host: &mut H, tracer: &mut T, state_modifier: StateModifier, ) -> Output { let mut interrupt = self.resume(()); loop { interrupt = match interrupt { InterruptVariant::InstructionStart(i) => { tracer.notify_instruction_start(i.data().pc, i.data().opcode, &i.data().state); i.resume(state_modifier.clone()) } InterruptVariant::AccountExists(i) => { let exists = host.account_exists(i.data().address); i.resume(AccountExistsStatus { exists }) } InterruptVariant::GetBalance(i) => { let balance = host.get_balance(i.data().address); i.resume(Balance { balance }) } InterruptVariant::GetCodeSize(i) => { let code_size = host.get_code_size(i.data().address); i.resume(CodeSize { code_size }) } InterruptVariant::GetStorage(i) => { let value = host.get_storage(i.data().address, i.data().key); i.resume(StorageValue { value }) } InterruptVariant::SetStorage(i) => { let status = host.set_storage(i.data().address, i.data().key, i.data().value); i.resume(StorageStatusInfo { status }) } InterruptVariant::GetCodeHash(i) => { let hash = host.get_code_hash(i.data().address); i.resume(CodeHash { hash }) } InterruptVariant::CopyCode(i) => { let mut code = vec![0; i.data().max_size]; let copied = host.copy_code(i.data().address, i.data().offset, &mut code[..]); if copied > code.len() { return Output { status_code: StatusCode::InternalError(format!( "copy code: copied {} > max size {}", copied, code.len() )), gas_left: 0, output_data: Bytes::new(), create_address: None, }; } code.truncate(copied); let code = code.into(); i.resume(Code { code }) } InterruptVariant::Selfdestruct(i) => { host.selfdestruct(i.data().address, i.data().beneficiary); i.resume(()) } InterruptVariant::Call(i) => { let message = match i.data() { Call::Call(message) => message.clone(), Call::Create(message) => message.clone().into(), }; let output = host.call(&message); i.resume(CallOutput { output }) } InterruptVariant::GetTxContext(i) => { let context = host.get_tx_context(); i.resume(TxContextData { context }) } InterruptVariant::GetBlockHash(i) => { let hash = host.get_block_hash(i.data().block_number); i.resume(BlockHash { hash }) } InterruptVariant::EmitLog(i) => { host.emit_log( i.data().address, &*i.data().data, i.data().topics.as_slice(), ); i.resume(()) } InterruptVariant::AccessAccount(i) => { let status = host.access_account(i.data().address); i.resume(AccessAccountStatus { status }) } InterruptVariant::AccessStorage(i) => { let status = host.access_storage(i.data().address, i.data().key); i.resume(AccessStorageStatus { status }) } InterruptVariant::Complete(i) => { let output = match i { Ok(output) => output.into(), Err(status_code) => Output { status_code, gas_left: 0, output_data: Bytes::new(), create_address: None, }, }; return output; } }; } } } async fn interpreter_producer( mut co: Co, s: AnalyzedCode, mut state: ExecutionState, trace: bool, ) -> Result { let state = &mut state; let instruction_table = get_baseline_instruction_table(state.evm_revision); let mut reverted = false; let mut pc = 0; loop { let op = OpCode(s.padded_code[pc]); // Do not print stop on the final STOP if trace && pc < s.code.len() { if let Some(modifier) = co .yield_(InterruptDataVariant::InstructionStart(Box::new( InstructionStart { pc, opcode: op, state: state.clone(), }, ))) .await .as_state_modifier() .unwrap() { (modifier)(state) } } check_requirements(instruction_table, state, op)?; match op { OpCode::STOP => { break; } 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 => { arithmetic::exp(state)?; } 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 => { memory::keccak256(state)?; } OpCode::ADDRESS => { external::address(state); } OpCode::BALANCE => { balance!(co, state); } OpCode::CALLER => { external::caller(state); } OpCode::CALLVALUE => { external::callvalue(state); } OpCode::CALLDATALOAD => { calldataload(state); } OpCode::CALLDATASIZE => { calldatasize(state); } OpCode::CALLDATACOPY => { memory::calldatacopy(state)?; } OpCode::CODESIZE => { memory::codesize(&mut state.stack, &s.code[..]); } OpCode::CODECOPY => { memory::codecopy(state, &s.code[..])?; } OpCode::EXTCODESIZE => { extcodesize!(co, state); } OpCode::EXTCODECOPY => { extcodecopy!(co, state); } OpCode::RETURNDATASIZE => { memory::returndatasize(state); } OpCode::RETURNDATACOPY => { memory::returndatacopy(state)?; } OpCode::EXTCODEHASH => { extcodehash!(co, state); } OpCode::BLOCKHASH => { blockhash!(co, state); } OpCode::ORIGIN | OpCode::COINBASE | OpCode::GASPRICE | OpCode::TIMESTAMP | OpCode::NUMBER | OpCode::DIFFICULTY | OpCode::GASLIMIT | OpCode::CHAINID | OpCode::BASEFEE => { push_txcontext!( co, state, match op { OpCode::ORIGIN => external::origin_accessor, OpCode::COINBASE => external::coinbase_accessor, OpCode::GASPRICE => external::gasprice_accessor, OpCode::TIMESTAMP => external::timestamp_accessor, OpCode::NUMBER => external::number_accessor, OpCode::DIFFICULTY => external::difficulty_accessor, OpCode::GASLIMIT => external::gaslimit_accessor, OpCode::CHAINID => external::chainid_accessor, OpCode::BASEFEE => external::basefee_accessor, _ => unreachable!(), } ); } OpCode::SELFBALANCE => { selfbalance!(co, state); } OpCode::POP => { stack_manip::pop(&mut state.stack); } OpCode::MLOAD => { memory::mload(state)?; } OpCode::MSTORE => { memory::mstore(state)?; } OpCode::MSTORE8 => { memory::mstore8(state)?; } OpCode::JUMP => { pc = op_jump(state, &s.jumpdest_map)?; continue; } OpCode::JUMPI => { if !state.stack.get(1).is_zero() { pc = op_jump(state, &s.jumpdest_map)?; state.stack.pop(); continue; } else { state.stack.pop(); state.stack.pop(); } } OpCode::PC => state.stack.push(pc.into()), OpCode::MSIZE => memory::msize(state), OpCode::SLOAD => { sload!(co, state); } OpCode::SSTORE => { sstore!(co, state); } OpCode::GAS => state.stack.push(state.gas_left.into()), OpCode::JUMPDEST => {} 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 => { let push_len = op.push_size().unwrap().into(); push(&mut state.stack, &s.padded_code[pc + 1..], push_len); pc += push_len; } OpCode::DUP1 | OpCode::DUP2 | OpCode::DUP3 | OpCode::DUP4 | OpCode::DUP5 | OpCode::DUP6 | OpCode::DUP7 | OpCode::DUP8 | OpCode::DUP9 | OpCode::DUP10 | OpCode::DUP11 | OpCode::DUP12 | OpCode::DUP13 | OpCode::DUP14 | OpCode::DUP15 | OpCode::DUP16 => { dup( &mut state.stack, op.to_usize() - OpCode::DUP1.to_usize() + 1, ); } OpCode::SWAP1 | OpCode::SWAP2 | OpCode::SWAP3 | OpCode::SWAP4 | OpCode::SWAP5 | OpCode::SWAP6 | OpCode::SWAP7 | OpCode::SWAP8 | OpCode::SWAP9 | OpCode::SWAP10 | OpCode::SWAP11 | OpCode::SWAP12 | OpCode::SWAP13 | OpCode::SWAP14 | OpCode::SWAP15 | OpCode::SWAP16 => swap( &mut state.stack, op.to_usize() - OpCode::SWAP1.to_usize() + 1, ), OpCode::LOG0 | OpCode::LOG1 | OpCode::LOG2 | OpCode::LOG3 | OpCode::LOG4 => { do_log!(co, state, op.to_usize() - OpCode::LOG0.to_usize()); } OpCode::CREATE | OpCode::CREATE2 => { do_create!(co, state, op == OpCode::CREATE2); } OpCode::CALL | OpCode::CALLCODE | OpCode::DELEGATECALL | OpCode::STATICCALL => { do_call!( co, state, match op { OpCode::CALL | OpCode::STATICCALL => CallKind::Call, OpCode::CALLCODE => CallKind::CallCode, OpCode::DELEGATECALL => CallKind::DelegateCall, _ => unreachable!(), }, op == OpCode::STATICCALL ); } OpCode::RETURN | OpCode::REVERT => { ret(state)?; reverted = op == OpCode::REVERT; break; } OpCode::INVALID => { return Err(StatusCode::InvalidInstruction); } OpCode::SELFDESTRUCT => { selfdestruct!(co, state); break; } other => { unreachable!("reached unhandled opcode: {}", other); } } pc += 1; } let output = SuccessfulOutput { reverted, gas_left: state.gas_left, output_data: state.output_data.clone(), }; Ok(output) } ================================================ FILE: src/lib.rs ================================================ #![doc = include_str!("../README.md")] use bytes::Bytes; pub use common::{ CallKind, CreateMessage, Message, Output, Revision, StatusCode, SuccessfulOutput, }; pub use host::Host; pub use interpreter::AnalyzedCode; pub use opcode::OpCode; pub use state::{ExecutionState, Stack}; /// Maximum allowed EVM bytecode size. pub const MAX_CODE_SIZE: usize = 0x6000; mod common; pub mod host; #[doc(hidden)] pub mod instructions; mod interpreter; pub mod opcode; mod state; pub mod tracing; pub mod continuation; #[cfg(feature = "util")] pub mod util; #[cfg(feature = "evmc")] pub mod evmc; ================================================ FILE: src/opcode.rs ================================================ use std::{borrow::Cow, fmt::Display}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct OpCode(pub u8); impl OpCode { #[inline(always)] pub const fn to_u8(self) -> u8 { self.0 } #[inline(always)] 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", } } pub fn push_size(self) -> Option { (self.to_u8() >= OpCode::PUSH1.to_u8() && self.to_u8() <= OpCode::PUSH32.to_u8()) .then(|| self.to_u8() - OpCode::PUSH1.to_u8() + 1) } } 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) } } ================================================ FILE: src/state.rs ================================================ use crate::common::{Message, Revision}; use arrayvec::ArrayVec; use bytes::Bytes; use ethereum_types::U256; use getset::{Getters, MutGetters}; use serde::Serialize; const SIZE: usize = 1024; /// EVM stack. #[derive(Clone, Debug, Default, Serialize)] pub struct Stack(pub ArrayVec); impl Stack { pub const fn limit() -> usize { SIZE } 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 is_empty(&self) -> bool { self.len() == 0 } pub fn push(&mut self, v: U256) { unsafe { self.0.push_unchecked(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; /// EVM execution state. #[derive(Clone, Debug, Getters, MutGetters)] pub struct ExecutionState { #[getset(get = "pub", get_mut = "pub")] pub(crate) gas_left: i64, #[getset(get = "pub", get_mut = "pub")] pub(crate) stack: Stack, #[getset(get = "pub", get_mut = "pub")] pub(crate) memory: Memory, pub(crate) message: Message, pub(crate) evm_revision: Revision, #[getset(get = "pub", get_mut = "pub")] pub(crate) return_data: Bytes, pub(crate) output_data: Bytes, } 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(), } } } #[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()); } } ================================================ FILE: src/tracing/mod.rs ================================================ use super::*; use crate::state::*; use serde::Serialize; /// Passed into execution context to collect metrics. pub trait Tracer { #[doc(hidden)] const DUMMY: bool = false; /// 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 { const DUMMY: bool = true; 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() ) } } ================================================ FILE: src/util/bytecode.rs ================================================ use crate::opcode::*; use core::iter::repeat; use ethereum_types::U256; use std::ops::{Add, Mul}; /// EVM bytecode builder. #[derive(Clone, Debug, PartialEq)] 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 revert(mut self, index: impl Into, size: impl Into) -> Self { self = self.pushv(index); self = self.pushv(size); self = self.opcode(OpCode::REVERT); 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 } pub fn len(&self) -> usize { self.inner.len() } pub fn is_empty(&self) -> bool { self.len() == 0 } } 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 AsRef<[u8]> for Bytecode { fn as_ref(&self) -> &[u8] { &self.inner } } impl IntoIterator for Bytecode { type Item = u8; type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.inner.into_iter() } } impl Mul for usize { type Output = Bytecode; fn mul(self, rhs: Bytecode) -> Self::Output { repeat(rhs) .take(self) .fold(Bytecode::new(), |acc, b| acc.append_bc(b)) } } impl Mul for usize { type Output = Bytecode; fn mul(self, rhs: OpCode) -> Self::Output { self.mul(Bytecode::from(rhs)) } } impl> Add for Bytecode { type Output = Bytecode; fn add(self, rhs: T) -> Self::Output { self.append_bc(rhs) } } 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) } } #[cfg(test)] mod tests { use super::*; #[test] fn multiply_bytecode() { assert_eq!( 3 * Bytecode::new().opcode(OpCode::POP), Bytecode::new() .opcode(OpCode::POP) .opcode(OpCode::POP) .opcode(OpCode::POP) ) } } ================================================ FILE: src/util/mocked_host.rs ================================================ use crate::{host::*, *}; use bytes::Bytes; use ethereum_types::*; 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: U256, 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: U256, /// 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: U256, 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: U256::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) } } } impl crate::Host for MockedHost { fn account_exists(&self, address: ethereum_types::Address) -> bool { self.recorded.lock().record_account_access(address); self.accounts.contains_key(&address) } fn get_storage(&self, address: ethereum_types::Address, key: U256) -> U256 { self.recorded.lock().record_account_access(address); self.accounts .get(&address) .and_then(|account| account.storage.get(&key).map(|value| value.value)) .unwrap_or_else(U256::zero) } fn set_storage( &mut self, address: ethereum_types::Address, key: U256, value: U256, ) -> StorageStatus { 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 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; status } fn get_balance(&self, address: ethereum_types::Address) -> ethereum_types::U256 { self.recorded.lock().record_account_access(address); self.accounts .get(&address) .map(|acc| acc.balance) .unwrap_or_else(U256::zero) } fn get_code_size(&self, address: ethereum_types::Address) -> ethereum_types::U256 { self.recorded.lock().record_account_access(address); self.accounts .get(&address) .map(|acc| acc.code.len().into()) .unwrap_or_else(U256::zero) } fn get_code_hash(&self, address: ethereum_types::Address) -> U256 { self.recorded.lock().record_account_access(address); self.accounts .get(&address) .map(|acc| acc.code_hash) .unwrap_or_else(U256::zero) } fn copy_code(&self, address: Address, code_offset: usize, buffer: &mut [u8]) -> usize { self.recorded.lock().record_account_access(address); 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) } fn selfdestruct( &mut self, address: ethereum_types::Address, beneficiary: ethereum_types::Address, ) { let mut r = self.recorded.lock(); r.record_account_access(address); r.selfdestructs.push(SelfdestructRecord { selfdestructed: address, beneficiary, }); } fn call(&mut self, msg: &Message) -> Output { let mut r = self.recorded.lock(); r.record_account_access(msg.recipient); 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()); } } self.call_result.clone() } fn get_tx_context(&self) -> TxContext { self.tx_context.clone() } fn get_block_hash(&self, block_number: u64) -> U256 { self.recorded.lock().blockhashes.push(block_number); self.block_hash } fn emit_log(&mut self, address: ethereum_types::Address, data: &[u8], topics: &[U256]) { self.recorded.lock().logs.push(LogRecord { creator: address, data: data.to_vec().into(), topics: topics.to_vec(), }); } fn access_account(&mut self, address: ethereum_types::Address) -> AccessStatus { 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 AccessStatus::Warm; } if already_accessed { AccessStatus::Warm } else { AccessStatus::Cold } } fn access_storage(&mut self, address: ethereum_types::Address, key: U256) -> AccessStatus { let value = self .accounts .entry(address) .or_default() .storage .entry(key) .or_default(); let access_status = value.access_status; value.access_status = AccessStatus::Warm; access_status } } ================================================ FILE: src/util/mod.rs ================================================ mod bytecode; pub mod mocked_host; mod tester; pub use bytecode::*; pub use tester::*; ================================================ FILE: src/util/tester.rs ================================================ use crate::{ tracing::*, util::{mocked_host::*, *}, *, }; use bytes::Bytes; use educe::Educe; use ethereum_types::{Address, U256}; use std::sync::Arc; fn exec( host: &mut MockedHost, revision: Revision, message: Message, code: Vec, collect_traces: bool, ) -> Output { // Add EIP-2929 tweak. if revision >= Revision::Berlin { host.access_account(message.sender); host.access_account(message.recipient); } let code = AnalyzedCode::analyze(code); if collect_traces { code.execute(host, &mut StdoutTracer::default(), None, message, revision) } else { code.execute(host, &mut NoopTracer, None, message, revision) } } #[derive(Clone, Copy, Debug)] enum GasCheck { Used(i64), Left(i64), } /// 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: Vec, gas_check: Option, expected_status_codes: Option>, expected_output_data: Option>, collect_traces: bool, } 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, recipient: Address::zero(), code_address: Address::zero(), sender: Address::zero(), input_data: Bytes::new(), value: 0.into(), }, code: Vec::new(), gas_check: None, expected_status_codes: None, expected_output_data: None, collect_traces: false, } } /// Set code to be executed. pub fn code(mut self, code: impl Into) -> Self { self.code = code.into().build(); 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(Arc::new(host_fn)); 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.recipient = 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 } pub fn collect_traces(mut self, doit: bool) -> Self { self.collect_traces = doit; self } /// Execute provided code, run checks and return bytecode returned by EVM. pub fn check_and_get_result(self) -> Output { if self.collect_traces { println!("Executing code: {}", hex::encode(&self.code)); } let mut host = self.host; for f in self.apply_host_fns { (f)(&mut host, &self.message); } let output = exec( &mut host, self.revision, self.message.clone(), self.code, self.collect_traces, ); if let Some(status_codes) = self.expected_status_codes { assert!( status_codes.iter().any(|s| *s == output.status_code), "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 fn check(self) { self.check_and_get_result(); } } ================================================ FILE: tests/basefee.rs ================================================ use evmodin::{opcode::*, util::*, *}; #[test] fn basefee_pre_london() { EvmTester::new() .revision(Revision::Berlin) .code(Bytecode::new().opcode(OpCode::BASEFEE)) .status(StatusCode::UndefinedInstruction) .check() } #[test] 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(); t.code(Bytecode::new().opcode(OpCode::BASEFEE).ret_top()) .status(StatusCode::Success) .gas_used(17) .output_value(7) .check() } ================================================ FILE: tests/call.rs ================================================ use bytes::Bytes; use core::iter::repeat_with; use ethereum_types::*; use evmodin::{opcode::*, util::*, *}; use hex_literal::hex; #[test] 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() } /// Checks if DELEGATECALL forwards the "static" flag. #[test] 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() } #[test] 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(); t.gas(73).status(StatusCode::OutOfGas).check(); } #[test] 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 key = 1.into(); assert_eq!( H256(host.accounts[&address].storage[&key].value.into()).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() } #[test] 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() } } #[test] 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: 0x5a.into() }); assert_eq!(call_msg.gas, 263775); assert_eq!( H256(host.accounts[&address].storage[&1.into()].value.into()).0[22], 0xc2 ); assert_eq!(call_msg.input_data.len(), 0x41); }) .check() } #[test] 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: U256::zero() } ); assert_eq!(r.calls.last().unwrap().depth, 1); }) .check(); t.gas(32021 - 1) .status(StatusCode::OutOfGas) .gas_left(0) .inspect_host(|host, _| { // No another CREATE2. assert_eq!(host.recorded.lock().calls.len(), 0) }) .check() } #[test] 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() } } #[test] 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: U256::zero() } } ); }) .check(); 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: U256::zero() } } ); }) .check(); 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: U256::zero() } } ); }) .check(); } } #[test] 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(); // 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(); // 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(); } } #[test] 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.recipient).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, _| { let r = host.recorded.lock(); assert_eq!(r.calls.len(), 1); let call_msg = &r.calls[0]; assert_eq!(call_msg.kind, CallKind::Call); assert_eq!(call_msg.depth, 1); assert_eq!(call_msg.gas, 32083); assert_eq!(call_msg.recipient, call_dst); assert_eq!(call_msg.sender, call_sender); }) .check() } #[test] 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() } #[test] 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() } } #[test] 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(); 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(); } } #[test] 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() } } #[test] 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, _| { let recorded = host.recorded.lock(); assert_eq!(recorded.calls.len(), 1); let call_msg = &recorded.calls[0]; assert_eq!(call_msg.kind, CallKind::Call); assert_eq!(call_msg.depth, 1); assert_eq!(call_msg.gas, 6000); assert_eq!(call_msg.input_data.len(), 64); assert_eq!( call_msg.recipient, hex!("00000000000000000000000000000000000000aa").into() ); assert_eq!(call_msg.value, 0.into()); }) .check() } #[test] 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.recipient).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(); t.clone() .revision(Revision::Tangerine) .apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).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| { let r = host.recorded.lock(); assert_eq!(r.calls.len(), 1); let call_msg = &r.calls[0]; assert_eq!(call_msg.recipient, call_dst); assert_eq!(call_msg.gas, 2300); assert_eq!(call_msg.sender, destination); assert_eq!(call_msg.value, 1.into()); assert_eq!(call_msg.input_data, Bytes::new()); assert_eq!( r.account_accesses, [ call_dst, // Account exist? msg.recipient, // Balance. call_dst // Call. ] ) }) .check(); t.clone() .revision(Revision::Spurious) .apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).or_default().balance = 0.into(); }) .input(&hex!("00") as &[u8]) .status(StatusCode::Success) .gas_used(739) .output_value(1) .inspect_host(move |host, _| { let r = host.recorded.lock(); assert_eq!(r.calls.len(), 1); let call_msg = &r.calls[0]; assert_eq!(call_msg.recipient, call_dst); assert_eq!(call_msg.gas, 0); assert_eq!(call_msg.sender, destination); assert_eq!(call_msg.value, 0.into()); assert_eq!(call_msg.input_data, Bytes::new()); assert_eq!( r.account_accesses, [ call_dst // Call. ] ) }) .check(); t.revision(Revision::Spurious) .apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).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| { let r = host.recorded.lock(); assert_eq!(r.calls.len(), 1); let call_msg = &r.calls[0]; assert_eq!(call_msg.recipient, call_dst); assert_eq!(call_msg.gas, 2300); assert_eq!(call_msg.sender, destination); assert_eq!(call_msg.value, 1.into()); assert_eq!(call_msg.input_data, Bytes::new()); assert_eq!( r.account_accesses, [ call_dst, // Account exist? msg.recipient, // Balance. call_dst // Call. ] ) }) .check() } #[test] 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.recipient).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, _| { let recorded = host.recorded.lock(); assert_eq!(recorded.calls.len(), 1); let call_msg = &recorded.calls[0]; assert_eq!(call_msg.kind, CallKind::CallCode); assert_eq!(call_msg.depth, 1); assert_eq!(call_msg.gas, 52_300); assert_eq!(call_msg.sender, call_sender); assert_eq!(call_msg.value, 1.into()); }) .check() } /// Performs a CALL then OOG in the same code block. #[test] 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() } /// Performs a CALLCODE then OOG in the same code block. #[test] 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() } /// Performs a CALL then OOG in the same code block. #[test] 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() } /// Performs a STATICCALL then OOG in the same code block. #[test] 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() } #[test] 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() } #[test] 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() } } #[test] 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() } } #[test] 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() } let t = t.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(); t.gas(721 + 5000 - 1).status(StatusCode::OutOfGas).check(); } #[test] 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() } } #[test] fn returndatasize_before_call() { EvmTester::new() .code(hex!("3d60005360016000f3")) .gas_used(17) .output_data([0]) .check() } #[test] 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(); 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(); t.apply_host_fn(|host, _| { host.call_result.output_data = Bytes::new(); host.call_result.status_code = StatusCode::InternalError(String::new()); }) .gas_used(735) .output_data([0]) .check(); } #[test] 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() } #[test] fn returndatacopy_empty() { EvmTester::new() .code(hex!("600080808060aa60fff4600080803e60016000f3")) .gas_used(994) .output_data([0]) .check() } #[test] 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(); t.gas(735).status(StatusCode::OutOfGas).check(); } #[test] 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() } } ================================================ FILE: tests/eip2929.rs ================================================ use evmodin::{host::*, opcode::*, util::*, *}; use hex_literal::hex; #[test] 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.recipient, 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.recipient, msg.recipient, ] ); }) .check() } #[test] 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.recipient, hex!("00000000000000000000000000000000000000ff").into(), hex!("00000000000000000000000000000000000000ff").into(), msg.recipient, ] ); }) .check() } #[test] 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() } #[test] 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() } #[test] 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(); t.clone() .gas(gas - 1) .status(StatusCode::OutOfGas) .gas_used(gas - 1) .check(); } } #[test] 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(); t.gas(2611) .status(StatusCode::OutOfGas) .gas_used(2611) .check(); } #[test] fn eip2929_sload_cold() { let key = 1.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.recipient) .or_default() .storage .entry(key) .or_default(); st.value = 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.recipient].storage[&key].access_status, AccessStatus::Warm ); }) .check(); t.gas(2102) .status(StatusCode::OutOfGas) .gas_used(2102) .check(); } #[test] fn eip2929_sload_two_slots() { let key0 = 0.into(); let key1 = 1.into(); EvmTester::new() .revision(Revision::Berlin) .code( Bytecode::new() .pushv(key0) .opcode(OpCode::SLOAD) .opcode(OpCode::POP) .pushv(key1) .opcode(OpCode::SLOAD) .opcode(OpCode::POP), ) .gas(30000) .status(StatusCode::Success) .gas_used(4210) .inspect_host(move |host, msg| { assert_eq!( host.accounts[&msg.recipient].storage[&key0].access_status, AccessStatus::Warm ); assert_eq!( host.accounts[&msg.recipient].storage[&key1].access_status, AccessStatus::Warm ); }) .check() } #[test] fn eip2929_sload_warm() { let key = 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.recipient) .or_default() .storage .entry(key) .or_default(); st.value = 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.recipient].storage[&key].access_status, AccessStatus::Warm ); }) .check(); t.gas(102) .status(StatusCode::OutOfGas) .gas_used(102) .check(); } #[test] fn eip2929_sstore_modify_cold() { let key = 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.recipient) .or_default() .storage .entry(key) .or_default() .value = 2.into(); }); t.clone() .gas(5006) .status(StatusCode::Success) .gas_used(5006) .inspect_host(move |host, msg| { assert_eq!(host.accounts[&msg.recipient].storage[&key].value, 3.into()); assert_eq!( host.accounts[&msg.recipient].storage[&key].access_status, AccessStatus::Warm ); }) .check(); t.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.recipient].storage[&key].value, 3.into()); assert_eq!( host.accounts[&msg.recipient].storage[&key].access_status, AccessStatus::Warm ); }) .check(); } #[test] 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(); t.gas(7602) .status(StatusCode::OutOfGas) .gas_used(7602) .check(); } #[test] fn eip2929_selfdestruct_warm_beneficiary() { let t = EvmTester::new() .revision(Revision::Berlin) .code(Bytecode::new().pushv(0xbe).opcode(OpCode::SELFDESTRUCT)) .apply_host_fn(|host, _| { host.access_account(hex!("00000000000000000000000000000000000000be").into()); }); t.clone() .gas(5003) .status(StatusCode::Success) .gas_used(5003) .check(); t.gas(5002) .status(StatusCode::OutOfGas) .gas_used(5002) .check(); } #[test] 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.recipient, hex!("00000000000000000000000000000000000000de").into(), msg.sender, ] ); }) .check(); t.gas(2617) .status(StatusCode::OutOfGas) .gas_used(2617) .inspect_host(|host, msg| { assert_eq!( host.recorded.lock().account_accesses, [ msg.sender, msg.recipient, hex!("00000000000000000000000000000000000000de").into(), ] ); }) .check(); } ================================================ FILE: tests/execute.rs ================================================ #![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; #[test] fn empty_code() { for gas in [0, 1] { EvmTester::new() .code(hex!("")) .gas(gas) .gas_used(0) .status(StatusCode::Success) .check() } } #[test] fn invalid_push() { EvmTester::new() .code(Bytecode::new().opcode(OpCode::PUSH1)) .status(StatusCode::Success) .check(); } #[test] 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() } #[test] 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(); EvmTester::new() .code(Bytecode::new().opcode(OpCode::NOT)) .status(StatusCode::StackUnderflow) .check(); } #[test] fn add() { EvmTester::new() .code(hex!("6007600d0160005260206000f3")) .gas(25) .gas_used(24) .status(StatusCode::Success) .output_value(20) .check() } #[test] 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() } #[test] 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() } #[test] 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(); EvmTester::new() .code(b.append([0x8f])) .status(StatusCode::StackOverflow) .check(); } #[test] 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() } } #[test] fn sub_and_swap() { EvmTester::new() .code(hex!("600180810380829052602090f3")) .gas(33) .status(StatusCode::Success) .gas_left(0) .output_value(1) .check() } #[test] fn memory_and_not() { EvmTester::new() .code(hex!("600060018019815381518252800190f3")) .gas(42) .status(StatusCode::Success) .gas_left(0) .output_data(hex!("00fe")) .check() } #[test] fn msize() { EvmTester::new() .code(hex!("60aa6022535960005360016000f3")) .gas(29) .status(StatusCode::Success) .gas_left(0) .output_data(hex!("40")) .check() } #[test] fn gas() { EvmTester::new() .code(hex!("5a5a5a010160005360016000f3")) .gas(40) .status(StatusCode::Success) .gas_left(13) .output_data([38 + 36 + 34]) .check() } #[test] 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() } #[test] 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() } #[allow(clippy::identity_op)] #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] fn jumpi_at_the_end() { EvmTester::new() .code(hex!("5b6001600057")) .gas(1000) .status(StatusCode::OutOfGas) .gas_used(1000) .check() } #[test] 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(); } } } #[test] fn jump_to_block_beginning() { EvmTester::new() .code(Bytecode::new().jumpi(U256::zero(), OpCode::MSIZE).jump(4)) .status(StatusCode::BadJumpDestination) .check() } #[test] 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() } } #[test] 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() } #[test] fn jump_to_missing_push_data() { EvmTester::new() .code( Bytecode::new() .pushv(5) .opcode(OpCode::JUMP) .opcode(OpCode::PUSH1), ) .status(StatusCode::BadJumpDestination) .check() } #[test] fn jump_to_missing_push_data2() { EvmTester::new() .code( Bytecode::new() .pushv(6) .opcode(OpCode::JUMP) .opcode(OpCode::PUSH2) .append(hex!("ef")), ) .status(StatusCode::BadJumpDestination) .check() } #[test] 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() } #[test] 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() } #[test] 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() } } #[test] 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() } #[test] fn byte_overflow() { EvmTester::new() .code( Bytecode::new() .pushv(0) .opcode(OpCode::NOT) .pushv(32) .opcode(OpCode::BYTE) .ret_top(), ) .output_value(0) .check(); EvmTester::new() .code( Bytecode::new() .pushv(0) .opcode(OpCode::NOT) .pushb(hex!("ffffffffffffffffffffffffffffffffffff")) .opcode(OpCode::BYTE) .ret_top(), ) .output_value(0) .check(); } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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(); } #[test] 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(); } #[test] 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(); } } #[test] 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() } #[test] 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() } #[test] 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() } #[test] fn exp_oog() { let code = hex!("6001600003800a"); EvmTester::new() .code(code) .gas(1622) .status(StatusCode::Success) .gas_left(0) .check(); EvmTester::new() .code(code) .gas(1621) .status(StatusCode::OutOfGas) .gas_left(0) .check(); } #[test] 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(); } #[test] 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() } #[test] fn calldataload_outofrange() { EvmTester::new() .code( Bytecode::new() .pushv(1) .opcode(OpCode::CALLDATALOAD) .ret_top(), ) .status(StatusCode::Success) .output_value(U256::zero()) .check() } #[test] 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(); EvmTester::new() .code(code) .status(StatusCode::Success) .gas_used(20) .check(); EvmTester::new() .code(hex!("60ff66fffffffffffffa60003760ff6000f3")) .status(StatusCode::Success) .gas_used(66) .output_data([0; 0xff]) .check(); } #[test] 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() } #[test] 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() } #[test] fn undefined() { EvmTester::new() .code(hex!("2a")) .gas(1) .status(StatusCode::UndefinedInstruction) .gas_left(0) .check() } #[test] fn invalid() { EvmTester::new() .code(hex!("fe")) .gas(1) .status(StatusCode::InvalidInstruction) .gas_left(0) .check() } #[test] fn inner_stop() { EvmTester::new() .code( Bytecode::new() .pushv(0) .opcode(OpCode::STOP) .opcode(OpCode::POP), ) .gas(3) .status(StatusCode::Success) .gas_used(3) .check() } #[test] fn inner_return() { EvmTester::new() .code(Bytecode::new().ret(0, 0).pushv(0)) .gas(6) .status(StatusCode::Success) .gas_used(6) .check() } #[test] fn inner_revert() { EvmTester::new() .code(Bytecode::new().revert(0, 0).pushv(0)) .gas(6) .status(StatusCode::Revert) .gas_used(6) .check() } #[test] fn inner_invalid() { EvmTester::new() .revision(Revision::Frontier) .code( Bytecode::new() .pushv(0) .append(hex!("fe")) .opcode(OpCode::POP), ) .gas(5) .status(StatusCode::InvalidInstruction) .gas_left(0) .check() } #[test] fn inner_selfdestruct() { EvmTester::new() .revision(Revision::Frontier) .code( Bytecode::new() .pushv(0) .opcode(OpCode::SELFDESTRUCT) .pushv(0), ) .gas(3) .status(StatusCode::Success) .gas_used(3) .check() } #[test] fn keccak256() { EvmTester::new() .code(hex!("6108006103ff2060005260206000f3")) .status(StatusCode::Success) .gas_used(738) .output_data(hex!( "aeffb38c06e111d84216396baefeb7fed397f303d5cb84a33f1e8b485c4a22da" )) .check() } #[test] fn keccak256_empty() { EvmTester::new() .code( Bytecode::new() .pushv(0) .opcode(OpCode::DUP1) .opcode(OpCode::KECCAK256) .ret_top(), ) .output_data(hex!( "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" )) .check() } #[test] 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() } #[test] 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() } #[test] 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(); } } #[test] fn shl() { EvmTester::new() .code(hex!("600560011b6000526001601ff3")) .revision(Revision::Constantinople) .gas_used(24) .status(StatusCode::Success) .output_data([5 << 1]) .check() } #[test] fn shr() { EvmTester::new() .code(hex!("600560011c6000526001601ff3")) .revision(Revision::Constantinople) .gas_used(24) .status(StatusCode::Success) .output_data([5 >> 1]) .check() } #[test] fn sar() { EvmTester::new() .code(hex!("600160000360021d60005260016000f3")) .revision(Revision::Constantinople) .gas_used(30) .status(StatusCode::Success) .output_data([0xff]) .check() // MSB of (-1 >> 2) == -1 } #[test] fn sar_01() { EvmTester::new() .code(hex!("600060011d60005260016000f3")) .revision(Revision::Constantinople) .gas_used(24) .status(StatusCode::Success) .output_data([0]) .check() } #[test] 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() } } #[test] fn undefined_instruction_analysis_overflow() { let undefined_opcode = OpCode(0x0c); EvmTester::new() .code(Bytecode::new().opcode(undefined_opcode)) .status(StatusCode::UndefinedInstruction) .check() } #[test] fn abort() { for r in Revision::iter() { EvmTester::new() .code(hex!("fe")) .revision(r) .status(StatusCode::InvalidInstruction) .check() } } #[test] 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() } } #[test] 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() } #[test] 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() } #[test] 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() } } #[test] 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() } } #[test] 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() } } const MAX_CODE_SIZE: usize = 0x6000; #[test] 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(); EvmTester::new() .code(code[..code.len() - 1].to_vec()) .status(StatusCode::StackOverflow) .check(); } #[test] 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() } #[test] 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() .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(); } } } } ================================================ FILE: tests/other.rs ================================================ use evmodin::{opcode::*, util::*, *}; #[test] fn loop_full_of_jumpdests() { // The code is a simple loop with a counter taken from the input or a constant (325) if the // input is zero. The loop body contains of only JUMPDESTs, as much as the code size limit // allows. // The `mul(325, iszero(dup1(calldataload(0)))) + OP_OR` is equivalent of // `((x == 0) * 325) | x` // what is // `x == 0 ? 325 : x`. // The `not_(0)` is -1 so we can do `loop_counter + (-1)` to decrease the loop counter. let code = Bytecode::new() .pushv(15) .pushv(0) .opcode(OpCode::NOT) .pushv(0) .opcode(OpCode::CALLDATALOAD) .opcode(OpCode::DUP1) .opcode(OpCode::ISZERO) .pushv(325) .opcode(OpCode::MUL) + OpCode::OR + (MAX_CODE_SIZE - 20) * OpCode::JUMPDEST + OpCode::DUP2 + OpCode::ADD + OpCode::DUP1 + OpCode::DUP4 + OpCode::JUMPI; assert_eq!(code.clone().build().len(), MAX_CODE_SIZE); EvmTester::new() .code(code) .status(StatusCode::Success) .gas_used(7987882) .check() } #[test] fn jumpdest_with_high_offset() { for offset in [3, 16383, 16384, 32767, 32768, 65535, 65536] { let mut code = Bytecode::new().pushv(offset).opcode(OpCode::JUMP).build(); code.resize(offset, OpCode::INVALID.to_u8()); code.push(OpCode::JUMPDEST.to_u8()); EvmTester::new() .code(code) .status(StatusCode::Success) .check() } } ================================================ FILE: tests/state.rs ================================================ use ethereum_types::*; use evmodin::{ opcode::*, util::{mocked_host::*, *}, *, }; use hex_literal::hex; #[test] 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() } #[test] 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() } } #[test] 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() } #[test] fn sstore_pop_stack() { EvmTester::new() .code(hex!("60008060015560005360016000f3")) .gas(100000) .status(StatusCode::Success) .output_data(hex!("00")) .check() } #[test] fn sload_cost_pre_tangerine_whistle() { EvmTester::new() .code(hex!("60008054")) .revision(Revision::Homestead) .apply_host_fn(|host, message| { host.accounts.entry(message.recipient).or_default(); }) .gas(56) .status(StatusCode::Success) .gas_left(0) .inspect_host(|host, message| { assert_eq!(host.accounts[&message.recipient].storage.len(), 0); }) .check() } #[test] 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() } } #[test] fn sstore_cost() { for revision in [ Revision::Byzantium, Revision::Constantinople, Revision::Petersburg, Revision::Istanbul, ] { let v1 = 1.into(); 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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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(); } } #[test] 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() } t.revision(Revision::Constantinople) .gas(2307) .status(StatusCode::Success) .check() } #[test] 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() } #[test] fn balance() { EvmTester::new() .apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).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() } #[test] fn account_info_homestead() { let t = EvmTester::new() .revision(Revision::Homestead) .apply_host_fn(|host, msg| { let acc = host.accounts.entry(msg.recipient).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(); t.clone() .code( Bytecode::new() .opcode(OpCode::ADDRESS) .opcode(OpCode::EXTCODESIZE) .ret_top(), ) .status(StatusCode::Success) .gas_used(37) .output_value(1) .check(); t.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() } #[test] fn selfbalance() { let t = EvmTester::new() .apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).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(); t.revision(Revision::Istanbul) .status(StatusCode::Success) .gas_used(23) .output_data(hex!("000504030201")) .check() } #[test] 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], (4 - i).into()); } }) .check() } } #[test] 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() } #[test] 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() } } #[test] 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(); 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(); 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(); } #[test] 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.recipient).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.recipient]); // Selfdestruct. }) .check(); 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.recipient ] ); }) .check(); t.clone() .revision(Revision::Tangerine) .gas(30002) .status(StatusCode::OutOfGas) .inspect_host(move |host, _| { assert_eq!( host.recorded.lock().account_accesses, [ // Exists? beneficiary ] ); }) .check(); 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.recipient, // Selfdestruct. msg.recipient, ] ) }) .check(); t.clone() .revision(Revision::Spurious) .gas(5002) .status(StatusCode::OutOfGas) .inspect_host(|host, _| { assert_eq!(host.recorded.lock().account_accesses, []); }) .check(); t = t.apply_host_fn(move |host, msg| { host.accounts.entry(msg.recipient).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.recipient ] ); }) .check(); 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.recipient ] ); }) .check(); t.clone() .revision(Revision::Tangerine) .gas(30002) .status(StatusCode::OutOfGas) .inspect_host(move |host, _| { assert_eq!( host.recorded.lock().account_accesses, [ // Exists? beneficiary, ] ); }) .check(); 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.recipient, // Exists? beneficiary, // Selfdestruct. msg.recipient ] ); }) .check(); t.clone() .revision(Revision::Spurious) .gas(30002) .status(StatusCode::OutOfGas) .inspect_host(move |host, msg| { assert_eq!( host.recorded.lock().account_accesses, [ // Balance msg.recipient, // Exists? beneficiary, ] ); }) .check(); t = t.apply_host_fn(move |host, msg| { host.accounts.entry(beneficiary).or_default(); // Beneficiary exists. host.accounts.get_mut(&msg.recipient).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.recipient, ] ); }) .check(); 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.recipient, ] ); }) .check(); t.clone() .revision(Revision::Tangerine) .gas(5002) .status(StatusCode::OutOfGas) .inspect_host(move |host, _| { assert_eq!(host.recorded.lock().account_accesses, []); }) .check(); t.clone() .revision(Revision::Spurious) .gas(5003) .status(StatusCode::Success) .inspect_host(move |host, msg| { assert_eq!( host.recorded.lock().account_accesses, [ // Balance. msg.recipient, // Selfdestruct. msg.recipient, ] ); }) .check(); t.clone() .revision(Revision::Spurious) .gas(5002) .status(StatusCode::OutOfGas) .inspect_host(|host, _| { assert_eq!(host.recorded.lock().account_accesses, []); }) .check(); t = t.apply_host_fn(|host, msg| { host.accounts.entry(msg.recipient).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.recipient ] ); }) .check(); 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.recipient ] ); }) .check(); t.clone() .revision(Revision::Tangerine) .gas(5002) .status(StatusCode::OutOfGas) .inspect_host(|host, _| { assert_eq!(host.recorded.lock().account_accesses, []); }) .check(); 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.recipient, // Exists? beneficiary, // Selfdestruct msg.recipient ] ); }) .check(); t.revision(Revision::Spurious) .gas(5002) .status(StatusCode::OutOfGas) .inspect_host(|host, _| { assert_eq!(host.recorded.lock().account_accesses, []); }) .check(); } #[test] fn blockhash() { let t = EvmTester::new() .code(hex!("60004060005260206000f3")) .status(StatusCode::Success) .gas_used(38) .apply_host_fn(|host, _| { let mut v = H256(host.block_hash.into()); v.0[13] = 0x13; host.block_hash = <[u8; 32]>::from(v).into(); }); 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(); 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(); t.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(); } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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(); t.revision(Revision::Constantinople) .status(StatusCode::Success) .gas_used(418) .inspect(|host, _, output| { assert_eq!( output, <[u8; 32]>::from(host.accounts[&Address::zero()].code_hash) ); }) .check() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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() } #[test] 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.recipient).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(); } } }