-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
0 parents
commit 8e95a97
Showing
32 changed files
with
8,915 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- master | ||
|
||
name: CI | ||
|
||
jobs: | ||
ci: | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, macOS-latest, windows-latest] | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
submodules: recursive | ||
|
||
- uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: nightly | ||
override: true | ||
components: rustfmt, clippy | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: fmt | ||
args: --all -- --check | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: install | ||
args: cargo-hack | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: hack | ||
args: check --all --ignore-private --each-feature --no-dev-deps | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: check | ||
args: --all --all-targets --all-features | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: test | ||
|
||
- uses: actions-rs/cargo@v1 | ||
with: | ||
command: clippy | ||
args: -- -D warnings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
name = "evmodin" | ||
version = "0.1.0" | ||
edition = "2018" | ||
license = "Apache-2.0" | ||
|
||
[dependencies] | ||
anyhow = { version = "1", default-features = false } | ||
arrayvec = { version = "0.7", default-features = false, features = ["serde"] } | ||
async-trait = { version = "0.1", default-features = false } | ||
bytes = { version = "1", default-features = false, features = ["serde"] } | ||
educe = { version = "0.4", default-features = false, optional = true } | ||
i256 = { git = "https://github.com/vorot93/rust-i256" } | ||
ethereum-types = { version = "0.12", default-features = false } | ||
hex = "0.4" | ||
hex-literal = { version = "0.3", optional = true } | ||
num-traits = { version = "0.2", default-features = false } | ||
once_cell = "1" | ||
parking_lot = { version = "0.11", optional = true } | ||
primitive-types = { version = "0.10", default-features = false, features = [ | ||
"serde", | ||
] } | ||
serde = { version = "1", features = ["derive"] } | ||
serde_json = "1" | ||
sha3 = "0.9" | ||
strum_macros = "0.21" | ||
|
||
[dev-dependencies] | ||
evmodin-test = { path = ".", package = "evmodin", features = ["util"] } | ||
hex-literal = "0.3" | ||
rand = { version = "0.8", features = ["std"] } | ||
tokio = { version = "1", features = ["full"] } | ||
|
||
[features] | ||
util = ["educe/Debug", "hex-literal", "parking_lot"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
Apache License | ||
Version 2.0, January 2004 | ||
http://www.apache.org/licenses/ | ||
|
||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
|
||
1. Definitions. | ||
|
||
"License" shall mean the terms and conditions for use, reproduction, | ||
and distribution as defined by Sections 1 through 9 of this document. | ||
|
||
"Licensor" shall mean the copyright owner or entity authorized by | ||
the copyright owner that is granting the License. | ||
|
||
"Legal Entity" shall mean the union of the acting entity and all | ||
other entities that control, are controlled by, or are under common | ||
control with that entity. For the purposes of this definition, | ||
"control" means (i) the power, direct or indirect, to cause the | ||
direction or management of such entity, whether by contract or | ||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
outstanding shares, or (iii) beneficial ownership of such entity. | ||
|
||
"You" (or "Your") shall mean an individual or Legal Entity | ||
exercising permissions granted by this License. | ||
|
||
"Source" form shall mean the preferred form for making modifications, | ||
including but not limited to software source code, documentation | ||
source, and configuration files. | ||
|
||
"Object" form shall mean any form resulting from mechanical | ||
transformation or translation of a Source form, including but | ||
not limited to compiled object code, generated documentation, | ||
and conversions to other media types. | ||
|
||
"Work" shall mean the work of authorship, whether in Source or | ||
Object form, made available under the License, as indicated by a | ||
copyright notice that is included in or attached to the work | ||
(an example is provided in the Appendix below). | ||
|
||
"Derivative Works" shall mean any work, whether in Source or Object | ||
form, that is based on (or derived from) the Work and for which the | ||
editorial revisions, annotations, elaborations, or other modifications | ||
represent, as a whole, an original work of authorship. For the purposes | ||
of this License, Derivative Works shall not include works that remain | ||
separable from, or merely link (or bind by name) to the interfaces of, | ||
the Work and Derivative Works thereof. | ||
|
||
"Contribution" shall mean any work of authorship, including | ||
the original version of the Work and any modifications or additions | ||
to that Work or Derivative Works thereof, that is intentionally | ||
submitted to Licensor for inclusion in the Work by the copyright owner | ||
or by an individual or Legal Entity authorized to submit on behalf of | ||
the copyright owner. For the purposes of this definition, "submitted" | ||
means any form of electronic, verbal, or written communication sent | ||
to the Licensor or its representatives, including but not limited to | ||
communication on electronic mailing lists, source code control systems, | ||
and issue tracking systems that are managed by, or on behalf of, the | ||
Licensor for the purpose of discussing and improving the Work, but | ||
excluding communication that is conspicuously marked or otherwise | ||
designated in writing by the copyright owner as "Not a Contribution." | ||
|
||
"Contributor" shall mean Licensor and any individual or Legal Entity | ||
on behalf of whom a Contribution has been received by Licensor and | ||
subsequently incorporated within the Work. | ||
|
||
2. Grant of Copyright License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
copyright license to reproduce, prepare Derivative Works of, | ||
publicly display, publicly perform, sublicense, and distribute the | ||
Work and such Derivative Works in Source or Object form. | ||
|
||
3. Grant of Patent License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
(except as stated in this section) patent license to make, have made, | ||
use, offer to sell, sell, import, and otherwise transfer the Work, | ||
where such license applies only to those patent claims licensable | ||
by such Contributor that are necessarily infringed by their | ||
Contribution(s) alone or by combination of their Contribution(s) | ||
with the Work to which such Contribution(s) was submitted. If You | ||
institute patent litigation against any entity (including a | ||
cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
or a Contribution incorporated within the Work constitutes direct | ||
or contributory patent infringement, then any patent licenses | ||
granted to You under this License for that Work shall terminate | ||
as of the date such litigation is filed. | ||
|
||
4. Redistribution. You may reproduce and distribute copies of the | ||
Work or Derivative Works thereof in any medium, with or without | ||
modifications, and in Source or Object form, provided that You | ||
meet the following conditions: | ||
|
||
(a) You must give any other recipients of the Work or | ||
Derivative Works a copy of this License; and | ||
|
||
(b) You must cause any modified files to carry prominent notices | ||
stating that You changed the files; and | ||
|
||
(c) You must retain, in the Source form of any Derivative Works | ||
that You distribute, all copyright, patent, trademark, and | ||
attribution notices from the Source form of the Work, | ||
excluding those notices that do not pertain to any part of | ||
the Derivative Works; and | ||
|
||
(d) If the Work includes a "NOTICE" text file as part of its | ||
distribution, then any Derivative Works that You distribute must | ||
include a readable copy of the attribution notices contained | ||
within such NOTICE file, excluding those notices that do not | ||
pertain to any part of the Derivative Works, in at least one | ||
of the following places: within a NOTICE text file distributed | ||
as part of the Derivative Works; within the Source form or | ||
documentation, if provided along with the Derivative Works; or, | ||
within a display generated by the Derivative Works, if and | ||
wherever such third-party notices normally appear. The contents | ||
of the NOTICE file are for informational purposes only and | ||
do not modify the License. You may add Your own attribution | ||
notices within Derivative Works that You distribute, alongside | ||
or as an addendum to the NOTICE text from the Work, provided | ||
that such additional attribution notices cannot be construed | ||
as modifying the License. | ||
|
||
You may add Your own copyright statement to Your modifications and | ||
may provide additional or different license terms and conditions | ||
for use, reproduction, or distribution of Your modifications, or | ||
for any such Derivative Works as a whole, provided Your use, | ||
reproduction, and distribution of the Work otherwise complies with | ||
the conditions stated in this License. | ||
|
||
5. Submission of Contributions. Unless You explicitly state otherwise, | ||
any Contribution intentionally submitted for inclusion in the Work | ||
by You to the Licensor shall be under the terms and conditions of | ||
this License, without any additional terms or conditions. | ||
Notwithstanding the above, nothing herein shall supersede or modify | ||
the terms of any separate license agreement you may have executed | ||
with Licensor regarding such Contributions. | ||
|
||
6. Trademarks. This License does not grant permission to use the trade | ||
names, trademarks, service marks, or product names of the Licensor, | ||
except as required for reasonable and customary use in describing the | ||
origin of the Work and reproducing the content of the NOTICE file. | ||
|
||
7. Disclaimer of Warranty. Unless required by applicable law or | ||
agreed to in writing, Licensor provides the Work (and each | ||
Contributor provides its Contributions) on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
implied, including, without limitation, any warranties or conditions | ||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
PARTICULAR PURPOSE. You are solely responsible for determining the | ||
appropriateness of using or redistributing the Work and assume any | ||
risks associated with Your exercise of permissions under this License. | ||
|
||
8. Limitation of Liability. In no event and under no legal theory, | ||
whether in tort (including negligence), contract, or otherwise, | ||
unless required by applicable law (such as deliberate and grossly | ||
negligent acts) or agreed to in writing, shall any Contributor be | ||
liable to You for damages, including any direct, indirect, special, | ||
incidental, or consequential damages of any character arising as a | ||
result of this License or out of the use or inability to use the | ||
Work (including but not limited to damages for loss of goodwill, | ||
work stoppage, computer failure or malfunction, or any and all | ||
other commercial damages or losses), even if such Contributor | ||
has been advised of the possibility of such damages. | ||
|
||
9. Accepting Warranty or Additional Liability. While redistributing | ||
the Work or Derivative Works thereof, You may choose to offer, | ||
and charge a fee for, acceptance of support, warranty, indemnity, | ||
or other liability obligations and/or rights consistent with this | ||
License. However, in accepting such obligations, You may act only | ||
on Your own behalf and on Your sole responsibility, not on behalf | ||
of any other Contributor, and only if You agree to indemnify, | ||
defend, and hold each Contributor harmless for any liability | ||
incurred by, or claims asserted against, such Contributor by reason | ||
of your accepting any such warranty or additional liability. | ||
|
||
END OF TERMS AND CONDITIONS | ||
|
||
APPENDIX: How to apply the Apache License to your work. | ||
|
||
To apply the Apache License to your work, attach the following | ||
boilerplate notice, with the fields enclosed by brackets "[]" | ||
replaced with your own identifying information. (Don't include | ||
the brackets!) The text should be enclosed in the appropriate | ||
comment syntax for the file format. We also recommend that a | ||
file or class name and description of purpose be included on the | ||
same "printed page" as the copyright notice for easier | ||
identification within third-party archives. | ||
|
||
Copyright [yyyy] [name of copyright owner] | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# evmodin | ||
|
||
Fast EVM implementation with full async support. Port of [evmone](https://github.com/ethereum/evmone) to Rust. | ||
|
||
## Usage | ||
```rust | ||
use evmodin::{*, host::*, util::*, tracing::*}; | ||
use ethereum_types::*; | ||
use hex_literal::hex; | ||
|
||
let my_code = Bytecode::new() | ||
.mstore8_value(0, b'h') | ||
.mstore8_value(1, b'e') | ||
.mstore8_value(2, b'l') | ||
.mstore8_value(3, b'l') | ||
.mstore8_value(4, b'o') | ||
.ret(0, 5) | ||
.build(); | ||
let message = Message { | ||
kind: CallKind::Call, | ||
is_static: true, | ||
depth: 0, | ||
gas: 200, | ||
destination: Address::zero(), | ||
sender: Address::zero(), | ||
input_data: vec![].into(), | ||
value: U256::zero(), | ||
}; | ||
|
||
assert_eq!( | ||
AnalyzedCode::analyze(my_code) | ||
.execute(&mut DummyHost, &mut NoopTracer, message, Revision::London) | ||
.await | ||
.unwrap(), | ||
Output { | ||
status_code: StatusCode::Success, | ||
gas_left: 146, | ||
output_data: b"hello".to_vec().into(), | ||
create_address: None, | ||
} | ||
) | ||
``` | ||
|
||
License: Apache-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
use bytes::Bytes; | ||
use ethereum_types::{Address, H256, U256}; | ||
use serde::Serialize; | ||
use strum_macros::Display; | ||
|
||
/// EVM revision. | ||
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, Serialize)] | ||
pub enum Revision { | ||
/// The Frontier revision. | ||
/// The one Ethereum launched with. | ||
Frontier = 0, | ||
|
||
/// [The Homestead revision.](https://eips.ethereum.org/EIPS/eip-606) | ||
Homestead = 1, | ||
|
||
/// [The Tangerine Whistle revision.](https://eips.ethereum.org/EIPS/eip-608) | ||
Tangerine = 2, | ||
|
||
/// [The Spurious Dragon revision.](https://eips.ethereum.org/EIPS/eip-607) | ||
Spurious = 3, | ||
|
||
/// [The Byzantium revision.](https://eips.ethereum.org/EIPS/eip-609) | ||
Byzantium = 4, | ||
|
||
/// [The Constantinople revision.](https://eips.ethereum.org/EIPS/eip-1013) | ||
Constantinople = 5, | ||
|
||
/// [The Petersburg revision.](https://eips.ethereum.org/EIPS/eip-1716) | ||
Petersburg = 6, | ||
|
||
/// [The Istanbul revision.](https://eips.ethereum.org/EIPS/eip-1679) | ||
Istanbul = 7, | ||
|
||
/// [The Berlin revision.](https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md) | ||
Berlin = 8, | ||
|
||
/// [The London revision.](https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md) | ||
London = 9, | ||
} | ||
|
||
impl Revision { | ||
pub fn iter() -> impl Iterator<Item = Self> { | ||
(&[ | ||
Self::Frontier, | ||
Self::Homestead, | ||
Self::Tangerine, | ||
Self::Spurious, | ||
Self::Byzantium, | ||
Self::Constantinople, | ||
Self::Petersburg, | ||
Self::Istanbul, | ||
Self::Berlin, | ||
Self::London, | ||
]) | ||
.iter() | ||
.copied() | ||
} | ||
|
||
pub const fn len() -> usize { | ||
Self::London as usize + 1 | ||
} | ||
|
||
pub const fn latest() -> Self { | ||
Self::London | ||
} | ||
} | ||
|
||
/// Message status code. | ||
#[derive(Clone, Copy, Debug, Display, PartialEq)] | ||
pub enum StatusCode { | ||
/// Execution finished with success. | ||
#[strum(serialize = "success")] | ||
Success = 0, | ||
|
||
/// Generic execution failure. | ||
#[strum(serialize = "failure")] | ||
Failure = 1, | ||
|
||
/// Execution terminated with REVERT opcode. | ||
/// | ||
/// In this case the amount of gas left MAY be non-zero and additional output | ||
/// data MAY be provided in ::evmc_result. | ||
#[strum(serialize = "revert")] | ||
Revert = 2, | ||
|
||
/// The execution has run out of gas. | ||
#[strum(serialize = "out of gas")] | ||
OutOfGas = 3, | ||
|
||
/// The designated INVALID instruction has been hit during execution. | ||
/// | ||
/// [EIP-141](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-141.md) | ||
/// defines the instruction 0xfe as INVALID instruction to indicate execution | ||
/// abortion coming from high-level languages. This status code is reported | ||
/// in case this INVALID instruction has been encountered. | ||
#[strum(serialize = "invalid instruction")] | ||
InvalidInstruction = 4, | ||
|
||
/// An undefined instruction has been encountered. | ||
#[strum(serialize = "undefined instruction")] | ||
UndefinedInstruction = 5, | ||
|
||
/// The execution has attempted to put more items on the EVM stack | ||
/// than the specified limit. | ||
#[strum(serialize = "stack overflow")] | ||
StackOverflow = 6, | ||
|
||
/// Execution of an opcode has required more items on the EVM stack. | ||
#[strum(serialize = "stack underflow")] | ||
StackUnderflow = 7, | ||
|
||
/// Execution has violated the jump destination restrictions. | ||
#[strum(serialize = "bad jump destination")] | ||
BadJumpDestination = 8, | ||
|
||
/// Tried to read outside memory bounds. | ||
/// | ||
/// An example is RETURNDATACOPY reading past the available buffer. | ||
#[strum(serialize = "invalid memory access")] | ||
InvalidMemoryAccess = 9, | ||
|
||
/// Call depth has exceeded the limit (if any) | ||
#[strum(serialize = "call depth exceeded")] | ||
CallDepthExceeded = 10, | ||
|
||
/// Tried to execute an operation which is restricted in static mode. | ||
#[strum(serialize = "static mode violation")] | ||
StaticModeViolation = 11, | ||
|
||
/// A call to a precompiled or system contract has ended with a failure. | ||
/// | ||
/// An example: elliptic curve functions handed invalid EC points. | ||
#[strum(serialize = "precompile failure")] | ||
PrecompileFailure = 12, | ||
|
||
/// Contract validation has failed (e.g. due to EVM 1.5 jump validity, | ||
/// Casper's purity checker or ewasm contract rules). | ||
#[strum(serialize = "contract validation failure")] | ||
ContractValidationFailure = 13, | ||
|
||
/// An argument to a state accessing method has a value outside of the | ||
/// accepted range of values. | ||
#[strum(serialize = "argument out of range")] | ||
ArgumentOutOfRange = 14, | ||
|
||
/// The caller does not have enough funds for value transfer. | ||
#[strum(serialize = "insufficient balance")] | ||
InsufficientBalance = 17, | ||
|
||
/// EVM implementation generic internal error. | ||
#[strum(serialize = "internal error")] | ||
InternalError = -1, | ||
|
||
/// The execution of the given code and/or message has been rejected | ||
/// by the EVM implementation. | ||
/// | ||
/// This error SHOULD be used to signal that the EVM is not able to or | ||
/// willing to execute the given code type or message. | ||
/// If an EVM returns the ::EVMC_REJECTED status code, | ||
/// the Client MAY try to execute it in other EVM implementation. | ||
/// For example, the Client tries running a code in the EVM 1.5. If the | ||
/// code is not supported there, the execution falls back to the EVM 1.0. | ||
#[strum(serialize = "rejected")] | ||
Rejected = -2, | ||
|
||
/// The VM failed to allocate the amount of memory needed for execution. | ||
#[strum(serialize = "out of memory")] | ||
OutOfMemory = -3, | ||
} | ||
|
||
/// The kind of call-like instruction. | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum CallKind { | ||
Call, | ||
DelegateCall, | ||
CallCode, | ||
Create, | ||
Create2 { salt: H256 }, | ||
} | ||
|
||
/// The message describing an EVM call, | ||
/// including a zero-depth calls from a transaction origin. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct Message { | ||
/// The kind of the call. For zero-depth calls `CallKind::Call` SHOULD be used. | ||
pub kind: CallKind, | ||
|
||
/// Static call mode. | ||
pub is_static: bool, | ||
|
||
/// The call depth. | ||
pub depth: i32, | ||
|
||
/// The amount of gas for message execution. | ||
pub gas: i64, | ||
|
||
/// The destination of the message. | ||
pub destination: Address, | ||
|
||
/// The sender of the message. | ||
pub sender: Address, | ||
|
||
/// Message input data. | ||
pub input_data: Bytes, | ||
|
||
/// The amount of Ether transferred with the message. | ||
pub value: U256, | ||
} | ||
|
||
/// Output of EVM execution. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct Output { | ||
/// EVM exited with this status code. | ||
pub status_code: StatusCode, | ||
/// How much gas was left after execution | ||
pub gas_left: i64, | ||
/// Output data returned. | ||
pub output_data: Bytes, | ||
/// Contract creation address. | ||
pub create_address: Option<Address>, | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn u256_to_address(v: U256) -> Address { | ||
H256(v.into()).into() | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn address_to_u256(v: Address) -> U256 { | ||
U256::from_big_endian(&v.0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
use crate::common::{Message, Output}; | ||
use anyhow::bail; | ||
use async_trait::async_trait; | ||
use ethereum_types::{Address, H256, U256}; | ||
|
||
/// State access status (EIP-2929). | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum AccessStatus { | ||
Cold, | ||
Warm, | ||
} | ||
|
||
impl Default for AccessStatus { | ||
fn default() -> Self { | ||
Self::Cold | ||
} | ||
} | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub enum StorageStatus { | ||
/// The value of a storage item has been left unchanged: 0 -> 0 and X -> X. | ||
Unchanged, | ||
/// The value of a storage item has been modified: X -> Y. | ||
Modified, | ||
/// A storage item has been modified after being modified before: X -> Y -> Z. | ||
ModifiedAgain, | ||
/// A new storage item has been added: 0 -> X. | ||
Added, | ||
/// A storage item has been deleted: X -> 0. | ||
Deleted, | ||
} | ||
|
||
/// The transaction and block data for execution. | ||
#[derive(Clone, Debug)] | ||
pub struct TxContext { | ||
/// The transaction gas price. | ||
pub tx_gas_price: U256, | ||
/// The transaction origin account. | ||
pub tx_origin: Address, | ||
/// The miner of the block. | ||
pub block_coinbase: Address, | ||
/// The block number. | ||
pub block_number: u64, | ||
/// The block timestamp. | ||
pub block_timestamp: u64, | ||
/// The block gas limit. | ||
pub block_gas_limit: u64, | ||
/// The block difficulty. | ||
pub block_difficulty: U256, | ||
/// The blockchain's ChainID. | ||
pub chain_id: U256, | ||
/// The block base fee per gas (EIP-1559, EIP-3198). | ||
pub block_base_fee: U256, | ||
} | ||
|
||
/// Abstraction that exposes host context to EVM. | ||
/// | ||
/// It is asynchronous, allowing for remote access. Errors represent network or host errors. | ||
#[async_trait] | ||
pub trait Host { | ||
/// Check if an account exists. | ||
async fn account_exists(&self, address: Address) -> anyhow::Result<bool>; | ||
/// Get value of a storage key. | ||
/// | ||
/// Returns `Ok(H256::zero())` if does not exist. | ||
async fn get_storage(&self, address: Address, key: H256) -> anyhow::Result<H256>; | ||
/// Set value of a storage key. | ||
async fn set_storage( | ||
&mut self, | ||
address: Address, | ||
key: H256, | ||
value: H256, | ||
) -> anyhow::Result<StorageStatus>; | ||
/// Get balance of an account. | ||
/// | ||
/// Returns `Ok(0)` if account does not exist. | ||
async fn get_balance(&self, address: Address) -> anyhow::Result<U256>; | ||
/// Get code size of an account. | ||
/// | ||
/// Returns `Ok(0)` if account does not exist. | ||
async fn get_code_size(&self, address: Address) -> anyhow::Result<U256>; | ||
/// Get code hash of an account. | ||
/// | ||
/// Returns `Ok(0)` if account does not exist. | ||
async fn get_code_hash(&self, address: Address) -> anyhow::Result<H256>; | ||
/// Copy code of an account. | ||
/// | ||
/// Returns `Ok(0)` if offset is invalid. | ||
async fn copy_code( | ||
&self, | ||
address: Address, | ||
offset: usize, | ||
buffer: &mut [u8], | ||
) -> anyhow::Result<usize>; | ||
/// Self-destruct account. | ||
async fn selfdestruct(&mut self, address: Address, beneficiary: Address) -> anyhow::Result<()>; | ||
/// Call to another account. | ||
async fn call(&mut self, msg: &Message) -> anyhow::Result<Output>; | ||
/// Retrieve transaction context. | ||
async fn get_tx_context(&self) -> anyhow::Result<TxContext>; | ||
/// Get block hash. | ||
/// | ||
/// Returns `Ok(H256::zero())` if block does not exist. | ||
async fn get_block_hash(&self, block_number: u64) -> anyhow::Result<H256>; | ||
/// Emit a log. | ||
async fn emit_log( | ||
&mut self, | ||
address: Address, | ||
data: &[u8], | ||
topics: &[H256], | ||
) -> anyhow::Result<()>; | ||
/// Mark account as warm, return previous access status. | ||
/// | ||
/// Returns `Ok(AccessStatus::Cold)` if account does not exist. | ||
async fn access_account(&mut self, address: Address) -> anyhow::Result<AccessStatus>; | ||
/// Mark storage key as warm, return previous access status. | ||
/// | ||
/// Returns `Ok(AccessStatus::Cold)` if account does not exist. | ||
async fn access_storage(&mut self, address: Address, key: H256) | ||
-> anyhow::Result<AccessStatus>; | ||
} | ||
|
||
/// Host that does not support any ops. | ||
pub struct DummyHost; | ||
|
||
#[async_trait] | ||
impl Host for DummyHost { | ||
async fn account_exists(&self, _: Address) -> anyhow::Result<bool> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_storage(&self, _: Address, _: H256) -> anyhow::Result<H256> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn set_storage(&mut self, _: Address, _: H256, _: H256) -> anyhow::Result<StorageStatus> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_balance(&self, _: Address) -> anyhow::Result<U256> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_code_size(&self, _: Address) -> anyhow::Result<U256> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_code_hash(&self, _: Address) -> anyhow::Result<H256> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn copy_code(&self, _: Address, _: usize, _: &mut [u8]) -> anyhow::Result<usize> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn selfdestruct(&mut self, _: Address, _: Address) -> anyhow::Result<()> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn call(&mut self, _: &Message) -> anyhow::Result<Output> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_tx_context(&self) -> anyhow::Result<TxContext> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn get_block_hash(&self, _: u64) -> anyhow::Result<H256> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn emit_log(&mut self, _: Address, _: &[u8], _: &[H256]) -> anyhow::Result<()> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn access_account(&mut self, _: Address) -> anyhow::Result<AccessStatus> { | ||
bail!("unsupported") | ||
} | ||
|
||
async fn access_storage(&mut self, _: Address, _: H256) -> anyhow::Result<AccessStatus> { | ||
bail!("unsupported") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
use crate::{state::*, Revision, StatusCode}; | ||
use core::convert::TryInto; | ||
use ethereum_types::{U256, U512}; | ||
use i256::I256; | ||
|
||
#[inline] | ||
pub(crate) fn add(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a.overflowing_add(b).0); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn mul(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a.overflowing_mul(b).0); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn sub(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a.overflowing_sub(b).0); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn div(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(if b.is_zero() { U256::zero() } else { a / b }); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn sdiv(stack: &mut Stack) { | ||
let a = I256::from(stack.pop()); | ||
let b = I256::from(stack.pop()); | ||
let v = a / b; | ||
stack.push(v.into()); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn modulo(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
let v = if b.is_zero() { U256::zero() } else { a % b }; | ||
stack.push(v); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn smod(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
let v = if b.is_zero() { | ||
U256::zero() | ||
} else { | ||
let v = I256::from(a) % I256::from(b); | ||
v.into() | ||
}; | ||
|
||
stack.push(v); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn addmod(stack: &mut Stack) { | ||
let a = U512::from(stack.pop()); | ||
let b = U512::from(stack.pop()); | ||
let c = U512::from(stack.pop()); | ||
|
||
let v = if c.is_zero() { | ||
U256::zero() | ||
} else { | ||
let v = (a + b) % c; | ||
v.try_into().unwrap() | ||
}; | ||
|
||
stack.push(v); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn mulmod(stack: &mut Stack) { | ||
let a = U512::from(stack.pop()); | ||
let b = U512::from(stack.pop()); | ||
let c = U512::from(stack.pop()); | ||
|
||
let v = if c.is_zero() { | ||
U256::zero() | ||
} else { | ||
let v = (a * b) % c; | ||
v.try_into().unwrap() | ||
}; | ||
|
||
stack.push(v); | ||
} | ||
|
||
#[inline] | ||
fn log2floor(value: U256) -> u64 { | ||
assert!(value != U256::zero()); | ||
let mut l: u64 = 256; | ||
for i in 0..4 { | ||
let i = 3 - i; | ||
if value.0[i] == 0u64 { | ||
l -= 64; | ||
} else { | ||
l -= value.0[i].leading_zeros() as u64; | ||
if l == 0 { | ||
return l; | ||
} else { | ||
return l - 1; | ||
} | ||
} | ||
} | ||
l | ||
} | ||
|
||
pub(crate) fn exp(state: &mut ExecutionState) -> StatusCode { | ||
let mut base = state.stack.pop(); | ||
let mut power = state.stack.pop(); | ||
|
||
if !power.is_zero() { | ||
let additional_gas = if state.evm_revision >= Revision::Spurious { | ||
50 | ||
} else { | ||
10 | ||
} * (log2floor(power) / 8 + 1); | ||
|
||
state.gas_left -= additional_gas as i64; | ||
|
||
if state.gas_left < 0 { | ||
return StatusCode::OutOfGas; | ||
} | ||
} | ||
|
||
let mut v = U256::one(); | ||
|
||
while !power.is_zero() { | ||
if !(power & U256::one()).is_zero() { | ||
v = v.overflowing_mul(base).0; | ||
} | ||
power >>= 1; | ||
base = base.overflowing_mul(base).0; | ||
} | ||
|
||
state.stack.push(v); | ||
|
||
StatusCode::Success | ||
} | ||
|
||
pub(crate) fn signextend(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
let v = if a > U256::from(32) { | ||
b | ||
} else { | ||
let mut v = U256::zero(); | ||
let len: usize = a.as_usize(); | ||
let t: usize = 8 * (len + 1) - 1; | ||
let t_bit_mask = U256::one() << t; | ||
let t_value = (b & t_bit_mask) >> t; | ||
for i in 0..256 { | ||
let bit_mask = U256::one() << i; | ||
let i_value = (b & bit_mask) >> i; | ||
if i <= t { | ||
v = v.overflowing_add(i_value << i).0; | ||
} else { | ||
v = v.overflowing_add(t_value << i).0; | ||
} | ||
} | ||
v | ||
}; | ||
|
||
stack.push(v); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
use crate::state::Stack; | ||
use ethereum_types::U256; | ||
use i256::{Sign, I256}; | ||
|
||
#[inline] | ||
pub(crate) fn byte(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
let mut ret = U256::zero(); | ||
|
||
for i in 0..256 { | ||
if i < 8 && a < 32.into() { | ||
let o: usize = a.as_usize(); | ||
let t = 255 - (7 - i + 8 * o); | ||
let bit_mask = U256::one() << t; | ||
let value = (b & bit_mask) >> t; | ||
ret = ret.overflowing_add(value << i).0; | ||
} | ||
} | ||
|
||
stack.push(ret) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn shl(stack: &mut Stack) { | ||
let shift = stack.pop(); | ||
let value = stack.pop(); | ||
|
||
let ret = if value.is_zero() || shift >= U256::from(256) { | ||
U256::zero() | ||
} else { | ||
value << shift.as_usize() | ||
}; | ||
|
||
stack.push(ret) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn shr(stack: &mut Stack) { | ||
let shift = stack.pop(); | ||
let value = stack.pop(); | ||
|
||
let ret = if value.is_zero() || shift >= U256::from(256) { | ||
U256::zero() | ||
} else { | ||
value >> shift.as_usize() | ||
}; | ||
|
||
stack.push(ret) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn sar(stack: &mut Stack) { | ||
let shift = stack.pop(); | ||
let value = I256::from(stack.pop()); | ||
|
||
let ret = if value == I256::zero() || shift >= U256::from(256) { | ||
match value.0 { | ||
// value is 0 or >=1, pushing 0 | ||
Sign::Plus | Sign::NoSign => U256::zero(), | ||
// value is <0, pushing -1 | ||
Sign::Minus => I256(Sign::Minus, U256::one()).into(), | ||
} | ||
} else { | ||
let shift = shift.as_usize(); | ||
|
||
match value.0 { | ||
Sign::Plus | Sign::NoSign => value.1 >> shift, | ||
Sign::Minus => { | ||
let shifted = ((value.1.overflowing_sub(U256::one()).0) >> shift) | ||
.overflowing_add(U256::one()) | ||
.0; | ||
I256(Sign::Minus, shifted).into() | ||
} | ||
} | ||
}; | ||
|
||
stack.push(ret) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
use crate::state::*; | ||
use ethereum_types::U256; | ||
use i256::I256; | ||
|
||
#[inline] | ||
pub(crate) fn lt(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
stack.push(if a.lt(&b) { U256::one() } else { U256::zero() }) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn gt(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
stack.push(if a.gt(&b) { U256::one() } else { U256::zero() }) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn slt(stack: &mut Stack) { | ||
let a: I256 = stack.pop().into(); | ||
let b: I256 = stack.pop().into(); | ||
|
||
stack.push(if a.lt(&b) { U256::one() } else { U256::zero() }) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn sgt(stack: &mut Stack) { | ||
let a: I256 = stack.pop().into(); | ||
let b: I256 = stack.pop().into(); | ||
|
||
stack.push(if a.gt(&b) { U256::one() } else { U256::zero() }) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn eq(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
|
||
stack.push(if a.eq(&b) { U256::one() } else { U256::zero() }) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn iszero(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
stack.push(if a.is_zero() { | ||
U256::one() | ||
} else { | ||
U256::zero() | ||
}) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn and(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a & b); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn or(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a | b); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn xor(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
let b = stack.pop(); | ||
stack.push(a ^ b); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn not(stack: &mut Stack) { | ||
let a = stack.pop(); | ||
stack.push(!a); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
use std::cmp::min; | ||
|
||
use bytes::Bytes; | ||
use ethereum_types::{Address, H256}; | ||
|
||
use super::*; | ||
use crate::{ | ||
common::{address_to_u256, u256_to_address}, | ||
host::AccessStatus, | ||
instructions::{memory::MemoryRegion, properties::*}, | ||
CallKind, Host, Message, | ||
}; | ||
|
||
pub(crate) async fn call<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
kind: CallKind, | ||
is_static: bool, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let gas = state.stack.pop(); | ||
let dst = u256_to_address(state.stack.pop()); | ||
let value = if is_static || matches!(kind, CallKind::DelegateCall) { | ||
U256::zero() | ||
} else { | ||
state.stack.pop() | ||
}; | ||
let has_value = !value.is_zero(); | ||
let input_offset = state.stack.pop(); | ||
let input_size = state.stack.pop(); | ||
let output_offset = state.stack.pop(); | ||
let output_size = state.stack.pop(); | ||
|
||
state.stack.push(U256::zero()); // Assume failure. | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(dst).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
let input_region = if let Ok(r) = memory::verify_memory_region(state, input_offset, input_size) | ||
{ | ||
r | ||
} else { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
}; | ||
|
||
let output_region = | ||
if let Ok(r) = memory::verify_memory_region(state, output_offset, output_size) { | ||
r | ||
} else { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
}; | ||
|
||
let mut msg = Message { | ||
kind, | ||
is_static: is_static || state.message.is_static, | ||
depth: state.message.depth + 1, | ||
destination: dst, | ||
sender: if matches!(kind, CallKind::DelegateCall) { | ||
state.message.sender | ||
} else { | ||
state.message.destination | ||
}, | ||
gas: i64::MAX, | ||
value: if matches!(kind, CallKind::DelegateCall) { | ||
state.message.value | ||
} else { | ||
value | ||
}, | ||
input_data: input_region | ||
.map(|MemoryRegion { offset, size }| { | ||
state.memory[offset..offset + size.get()].to_vec().into() | ||
}) | ||
.unwrap_or_default(), | ||
}; | ||
|
||
let mut cost = if has_value { 9000 } else { 0 }; | ||
|
||
if matches!(kind, CallKind::Call) { | ||
if has_value && state.message.is_static { | ||
return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); | ||
} | ||
|
||
if (has_value || state.evm_revision < Revision::Spurious) | ||
&& !host.account_exists(dst).await? | ||
{ | ||
cost += 25000; | ||
} | ||
} | ||
state.gas_left -= cost; | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
|
||
if gas < msg.gas.into() { | ||
msg.gas = gas.as_usize() as i64; | ||
} | ||
|
||
if state.evm_revision >= Revision::Tangerine { | ||
// TODO: Always true for STATICCALL. | ||
msg.gas = min(msg.gas, state.gas_left - state.gas_left / 64); | ||
} else if msg.gas > state.gas_left { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
|
||
if has_value { | ||
msg.gas += 2300; // Add stipend. | ||
state.gas_left += 2300; | ||
} | ||
|
||
state.return_data.clear(); | ||
|
||
if state.message.depth >= 1024 { | ||
return Ok(InstructionResolution::Continue); | ||
} | ||
|
||
if has_value && host.get_balance(state.message.destination).await? < value { | ||
return Ok(InstructionResolution::Continue); | ||
} | ||
|
||
let result = host.call(&msg).await?; | ||
state.return_data = result.output_data.clone(); | ||
*state.stack.get_mut(0) = if matches!(result.status_code, StatusCode::Success) { | ||
U256::one() | ||
} else { | ||
U256::zero() | ||
}; | ||
|
||
if let Some(MemoryRegion { offset, size }) = output_region { | ||
let copy_size = min(size.get(), result.output_data.len()); | ||
if copy_size > 0 { | ||
state.memory[offset..offset + copy_size] | ||
.copy_from_slice(&result.output_data[..copy_size]); | ||
} | ||
} | ||
|
||
let gas_used = msg.gas - result.gas_left; | ||
state.gas_left -= gas_used; | ||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
pub(crate) async fn create<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
create2: bool, | ||
) -> anyhow::Result<InstructionResolution> { | ||
if state.message.is_static { | ||
return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); | ||
} | ||
|
||
let endowment = state.stack.pop(); | ||
let init_code_offset = state.stack.pop(); | ||
let init_code_size = state.stack.pop(); | ||
|
||
let region = | ||
if let Ok(r) = memory::verify_memory_region(state, init_code_offset, init_code_size) { | ||
r | ||
} else { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
}; | ||
|
||
let call_kind = if create2 { | ||
let salt = state.stack.pop(); | ||
|
||
if let Some(region) = ®ion { | ||
let salt_cost = memory::num_words(region.size.get()) * 6; | ||
state.gas_left -= salt_cost; | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
CallKind::Create2 { | ||
salt: H256(salt.into()), | ||
} | ||
} else { | ||
CallKind::Create | ||
}; | ||
|
||
state.stack.push(U256::zero()); | ||
state.return_data.clear(); | ||
|
||
if state.message.depth >= 1024 { | ||
return Ok(InstructionResolution::Continue); | ||
} | ||
|
||
if !endowment.is_zero() && host.get_balance(state.message.destination).await? < endowment { | ||
return Ok(InstructionResolution::Continue); | ||
} | ||
|
||
let msg = Message { | ||
gas: if state.evm_revision >= Revision::Tangerine { | ||
state.gas_left - state.gas_left / 64 | ||
} else { | ||
state.gas_left | ||
}, | ||
|
||
is_static: false, | ||
destination: Address::zero(), | ||
|
||
kind: call_kind, | ||
input_data: if !init_code_size.is_zero() { | ||
state.memory[init_code_offset.as_usize() | ||
..init_code_offset.as_usize() + init_code_size.as_usize()] | ||
.to_vec() | ||
.into() | ||
} else { | ||
Bytes::new() | ||
}, | ||
sender: state.message.destination, | ||
depth: state.message.depth + 1, | ||
value: endowment, | ||
}; | ||
let result = host.call(&msg).await?; | ||
state.gas_left -= msg.gas - result.gas_left; | ||
|
||
state.return_data = result.output_data; | ||
if result.status_code == StatusCode::Success { | ||
*state.stack.get_mut(0) = address_to_u256( | ||
result | ||
.create_address | ||
.ok_or_else(|| anyhow::anyhow!("expected create address"))?, | ||
); | ||
} | ||
|
||
Ok(InstructionResolution::Continue) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
use super::*; | ||
use crate::{interpreter::JumpdestMap, StatusCode}; | ||
|
||
#[inline] | ||
pub(crate) fn ret(state: &mut ExecutionState, status_code: StatusCode) -> InstructionResolution { | ||
let offset = *state.stack.get(0); | ||
let size = *state.stack.get(1); | ||
|
||
if let Some(region) = if let Ok(r) = super::memory::verify_memory_region(state, offset, size) { | ||
r | ||
} else { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
} { | ||
state.output_data = state.memory[region.offset..region.offset + region.size.get()] | ||
.to_vec() | ||
.into(); | ||
} | ||
|
||
InstructionResolution::Exit(status_code) | ||
} | ||
|
||
pub(crate) fn op_jump( | ||
state: &mut ExecutionState, | ||
jumpdest_map: &JumpdestMap, | ||
) -> InstructionResolution { | ||
let dst = state.stack.pop(); | ||
if !jumpdest_map.contains(dst) { | ||
return InstructionResolution::Exit(StatusCode::BadJumpDestination); | ||
} | ||
|
||
InstructionResolution::Jump(dst.as_usize()) | ||
} | ||
|
||
pub(crate) fn calldataload(state: &mut ExecutionState) { | ||
let index = state.stack.pop(); | ||
|
||
let input_len = state.message.input_data.len(); | ||
|
||
state.stack.push({ | ||
if index > U256::from(input_len) { | ||
U256::zero() | ||
} else { | ||
let index_usize = index.as_usize(); | ||
let end = core::cmp::min(index_usize + 32, input_len); | ||
|
||
let mut data = [0; 32]; | ||
data[..end - index_usize].copy_from_slice(&state.message.input_data[index_usize..end]); | ||
|
||
data.into() | ||
} | ||
}); | ||
} | ||
|
||
pub(crate) fn calldatasize(state: &mut ExecutionState) { | ||
state.stack.push(state.message.input_data.len().into()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,369 @@ | ||
use ethereum_types::H256; | ||
|
||
use super::{ | ||
properties::{COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST}, | ||
*, | ||
}; | ||
use crate::{ | ||
common::{address_to_u256, u256_to_address}, | ||
host::*, | ||
instructions::properties::{ADDITIONAL_COLD_ACCOUNT_ACCESS_COST, COLD_SLOAD_COST}, | ||
state::ExecutionState, | ||
}; | ||
|
||
#[inline] | ||
pub(crate) fn address(state: &mut ExecutionState) { | ||
state.stack.push(address_to_u256(state.message.destination)); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn caller(state: &mut ExecutionState) { | ||
state.stack.push(address_to_u256(state.message.sender)); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn callvalue(state: &mut ExecutionState) { | ||
state.stack.push(state.message.value); | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn balance<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let address = u256_to_address(state.stack.pop()); | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(address).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
state.stack.push(host.get_balance(address).await?); | ||
|
||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn extcodesize<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let address = u256_to_address(state.stack.pop()); | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(address).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
state.stack.push(host.get_code_size(address).await?); | ||
|
||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn gasprice<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state.stack.push(host.get_tx_context().await?.tx_gas_price); | ||
Ok(()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn origin<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(address_to_u256(host.get_tx_context().await?.tx_origin)); | ||
Ok(()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn coinbase<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(address_to_u256(host.get_tx_context().await?.block_coinbase)); | ||
Ok(()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn number<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_tx_context().await?.block_number.into()); | ||
Ok(()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn timestamp<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_tx_context().await?.block_timestamp.into()); | ||
Ok(()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn gaslimit<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_tx_context().await?.block_gas_limit.into()); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn difficulty<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_tx_context().await?.block_difficulty); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn chainid<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state.stack.push(host.get_tx_context().await?.chain_id); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn basefee<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_tx_context().await?.block_base_fee); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn selfbalance<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
state | ||
.stack | ||
.push(host.get_balance(state.message.destination).await?); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn blockhash<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<()> { | ||
let number = state.stack.pop(); | ||
|
||
let upper_bound = host.get_tx_context().await?.block_number; | ||
let lower_bound = upper_bound.saturating_sub(256); | ||
|
||
let mut header = H256::zero(); | ||
if number <= u64::MAX.into() { | ||
let n = number.as_u64(); | ||
if (lower_bound..upper_bound).contains(&n) { | ||
header = host.get_block_hash(n).await?; | ||
} | ||
} | ||
|
||
state.stack.push(U256::from_big_endian(&header.0)); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) async fn log<H: Host, const N: usize>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
if state.message.is_static { | ||
return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); | ||
} | ||
|
||
let offset = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = memory::verify_memory_region(state, offset, size) { | ||
r | ||
} else { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
}; | ||
|
||
if let Some(region) = ®ion { | ||
let cost = region.size.get() as i64 * 8; | ||
state.gas_left -= cost; | ||
if cost < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
let mut topics = [H256::zero(); N]; | ||
for topic in &mut topics { | ||
*topic = H256(state.stack.pop().into()); | ||
} | ||
|
||
let data = if let Some(region) = region { | ||
&state.memory[region.offset..region.offset + region.size.get()] | ||
} else { | ||
&[] | ||
}; | ||
host.emit_log(state.message.destination, data, &topics) | ||
.await?; | ||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn sload<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let key = H256(state.stack.pop().into()); | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_storage(state.message.destination, key).await? == AccessStatus::Cold | ||
{ | ||
// The warm storage access cost is already applied (from the cost table). | ||
// Here we need to apply additional cold storage access cost. | ||
const ADDITIONAL_COLD_SLOAD_COST: u16 = COLD_SLOAD_COST - WARM_STORAGE_READ_COST; | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_SLOAD_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
state.stack.push(U256::from_big_endian( | ||
host.get_storage(state.message.destination, key) | ||
.await? | ||
.as_bytes(), | ||
)); | ||
|
||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn sstore<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
if state.message.is_static { | ||
return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); | ||
} | ||
|
||
if state.evm_revision >= Revision::Istanbul && state.gas_left <= 2300 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
|
||
let key = H256(state.stack.pop().into()); | ||
let value = H256(state.stack.pop().into()); | ||
|
||
let mut cost = 0; | ||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_storage(state.message.destination, key).await? == AccessStatus::Cold | ||
{ | ||
cost = COLD_SLOAD_COST; | ||
} | ||
|
||
let status = host | ||
.set_storage(state.message.destination, key, value) | ||
.await?; | ||
|
||
cost = match status { | ||
StorageStatus::Unchanged | StorageStatus::ModifiedAgain => { | ||
if state.evm_revision >= Revision::Berlin { | ||
cost + WARM_STORAGE_READ_COST | ||
} else if state.evm_revision == Revision::Istanbul { | ||
800 | ||
} else if state.evm_revision == Revision::Constantinople { | ||
200 | ||
} else { | ||
5000 | ||
} | ||
} | ||
StorageStatus::Modified | StorageStatus::Deleted => { | ||
if state.evm_revision >= Revision::Berlin { | ||
cost + 5000 - COLD_SLOAD_COST | ||
} else { | ||
5000 | ||
} | ||
} | ||
StorageStatus::Added => cost + 20000, | ||
}; | ||
state.gas_left -= i64::from(cost); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
pub(crate) async fn selfdestruct<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
if state.message.is_static { | ||
return Ok(InstructionResolution::Exit(StatusCode::StaticModeViolation)); | ||
} | ||
|
||
let beneficiary = u256_to_address(state.stack.pop()); | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(beneficiary).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
if state.evm_revision >= Revision::Tangerine | ||
&& (state.evm_revision == Revision::Tangerine | ||
|| !host.get_balance(state.message.destination).await?.is_zero()) | ||
{ | ||
// After TANGERINE_WHISTLE apply additional cost of | ||
// sending value to a non-existing account. | ||
if !host.account_exists(beneficiary).await? { | ||
state.gas_left -= 25000; | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
} | ||
|
||
host.selfdestruct(state.message.destination, beneficiary) | ||
.await?; | ||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::common::u256_to_address; | ||
use ethereum_types::Address; | ||
use hex_literal::hex; | ||
|
||
#[test] | ||
fn u256_to_address_conversion() { | ||
assert_eq!( | ||
u256_to_address(0x42.into()), | ||
Address::from(hex!("0000000000000000000000000000000000000042")) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use crate::{instructions::properties, Revision}; | ||
use once_cell::sync::Lazy; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub struct InstructionTableEntry { | ||
pub gas_cost: u16, | ||
pub stack_height_required: u8, | ||
pub can_overflow_stack: bool, | ||
} | ||
|
||
pub type InstructionTable = [Option<InstructionTableEntry>; 256]; | ||
pub type InstructionTables = [InstructionTable; Revision::len()]; | ||
|
||
pub static INSTRUCTION_TABLES: Lazy<InstructionTables> = 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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,363 @@ | ||
use super::{properties::*, *}; | ||
use crate::{common::u256_to_address, host::AccessStatus, state::Stack, Host}; | ||
use sha3::{Digest, Keccak256}; | ||
use std::{cmp::min, num::NonZeroUsize}; | ||
|
||
const MAX_BUFFER_SIZE: u32 = u32::MAX; | ||
|
||
/// The size of the EVM 256-bit word. | ||
const WORD_SIZE: i64 = 32; | ||
|
||
/// Returns number of words what would fit to provided number of bytes, | ||
/// i.e. it rounds up the number bytes to number of words. | ||
#[inline] | ||
pub(crate) fn num_words(size_in_bytes: usize) -> i64 { | ||
((size_in_bytes as i64) + (WORD_SIZE - 1)) / WORD_SIZE | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn mload(state: &mut ExecutionState) -> StatusCode { | ||
let index = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = | ||
verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) | ||
.map(|region| region.unwrap()) | ||
{ | ||
r | ||
} else { | ||
return StatusCode::OutOfGas; | ||
}; | ||
|
||
let value = | ||
U256::from_big_endian(&state.memory[region.offset..region.offset + region.size.get()]); | ||
|
||
state.stack.push(value); | ||
|
||
StatusCode::Success | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn mstore(state: &mut ExecutionState) -> StatusCode { | ||
let index = state.stack.pop(); | ||
let value = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = | ||
verify_memory_region_u64(state, index, NonZeroUsize::new(32).unwrap()) | ||
.map(|region| region.unwrap()) | ||
{ | ||
r | ||
} else { | ||
return StatusCode::OutOfGas; | ||
}; | ||
|
||
let mut b = [0; 32]; | ||
value.to_big_endian(&mut b); | ||
state.memory[region.offset..region.offset + 32].copy_from_slice(&b); | ||
StatusCode::Success | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn mstore8(state: &mut ExecutionState) -> StatusCode { | ||
let index = state.stack.pop(); | ||
let value = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = | ||
verify_memory_region_u64(state, index, NonZeroUsize::new(1).unwrap()) | ||
.map(|region| region.unwrap()) | ||
{ | ||
r | ||
} else { | ||
return StatusCode::OutOfGas; | ||
}; | ||
|
||
let value = (value.low_u32() & 0xff) as u8; | ||
|
||
state.memory[region.offset] = value; | ||
StatusCode::Success | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn msize(state: &mut ExecutionState) { | ||
state.stack.push(state.memory.len().into()); | ||
} | ||
|
||
#[inline] | ||
|
||
pub(crate) fn verify_memory_region_u64( | ||
state: &mut ExecutionState, | ||
offset: U256, | ||
size: NonZeroUsize, | ||
) -> Result<Option<MemoryRegion>, ()> { | ||
if offset > U256::from(MAX_BUFFER_SIZE) { | ||
return Err(()); | ||
} | ||
|
||
let new_size = offset.as_usize() + size.get(); | ||
let current_size = state.memory.len(); | ||
if new_size > current_size { | ||
let new_words = num_words(new_size); | ||
let current_words = (current_size / 32) as i64; | ||
let new_cost = 3 * new_words + new_words * new_words / 512; | ||
let current_cost = 3 * current_words + current_words * current_words / 512; | ||
let cost = new_cost - current_cost; | ||
|
||
state.gas_left -= cost; | ||
|
||
if state.gas_left < 0 { | ||
return Err(()); | ||
} | ||
|
||
state | ||
.memory | ||
.resize((new_words * WORD_SIZE) as usize, Default::default()); | ||
} | ||
|
||
Ok(Some(MemoryRegion { | ||
offset: offset.as_usize(), | ||
size, | ||
})) | ||
} | ||
|
||
pub(crate) struct MemoryRegion { | ||
pub offset: usize, | ||
pub size: NonZeroUsize, | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn verify_memory_region( | ||
state: &mut ExecutionState, | ||
offset: U256, | ||
size: U256, | ||
) -> Result<Option<MemoryRegion>, ()> { | ||
if size.is_zero() { | ||
return Ok(None); | ||
} | ||
|
||
if size > U256::from(MAX_BUFFER_SIZE) { | ||
return Err(()); | ||
} | ||
|
||
verify_memory_region_u64(state, offset, NonZeroUsize::new(size.as_usize()).unwrap()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn calldatacopy(state: &mut ExecutionState) -> InstructionResolution { | ||
let mem_index = state.stack.pop(); | ||
let input_index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(region) = verify_memory_region(state, mem_index, size) { | ||
region | ||
} else { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
}; | ||
|
||
if let Some(region) = ®ion { | ||
let copy_cost = num_words(region.size.get()) * 3; | ||
state.gas_left -= copy_cost; | ||
if state.gas_left < 0 { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
} | ||
|
||
let input_len = state.message.input_data.len().into(); | ||
|
||
let src = core::cmp::min(input_len, input_index).as_usize(); | ||
let copy_size = core::cmp::min(size, input_len - src).as_usize(); | ||
|
||
if copy_size > 0 { | ||
state.memory[region.offset..region.offset + copy_size] | ||
.copy_from_slice(&state.message.input_data[src..src + copy_size]); | ||
} | ||
|
||
if region.size.get() - copy_size > 0 { | ||
state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); | ||
} | ||
} | ||
|
||
InstructionResolution::Continue | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn keccak256(state: &mut ExecutionState) -> InstructionResolution { | ||
let index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(region) = verify_memory_region(state, index, size) { | ||
region | ||
} else { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
}; | ||
|
||
state.stack.push(U256::from_big_endian(&*Keccak256::digest( | ||
if let Some(region) = region { | ||
let w = num_words(region.size.get()); | ||
let cost = w * 6; | ||
state.gas_left -= cost; | ||
if state.gas_left < 0 { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
} | ||
|
||
&state.memory[region.offset..region.offset + region.size.get()] | ||
} else { | ||
&[] | ||
}, | ||
))); | ||
|
||
InstructionResolution::Continue | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn codesize(stack: &mut Stack, code: &[u8]) { | ||
stack.push(code.len().into()) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn codecopy(state: &mut ExecutionState, code: &[u8]) -> InstructionResolution { | ||
// TODO: Similar to calldatacopy(). | ||
|
||
let mem_index = state.stack.pop(); | ||
let input_index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { | ||
r | ||
} else { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
}; | ||
|
||
if let Some(region) = region { | ||
let src = min(U256::from(code.len()), input_index).as_usize(); | ||
let copy_size = min(region.size.get(), code.len() - src); | ||
|
||
let copy_cost = num_words(region.size.get()) * 3; | ||
state.gas_left -= copy_cost; | ||
if state.gas_left < 0 { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
} | ||
|
||
// TODO: Add unit tests for each combination of conditions. | ||
if copy_size > 0 { | ||
state.memory[region.offset..region.offset + copy_size] | ||
.copy_from_slice(&code[src..src + copy_size]); | ||
} | ||
|
||
if region.size.get() - copy_size > 0 { | ||
state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); | ||
} | ||
} | ||
|
||
InstructionResolution::Continue | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn extcodecopy<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let addr = u256_to_address(state.stack.pop()); | ||
let mem_index = state.stack.pop(); | ||
let input_index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { | ||
r | ||
} else { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
}; | ||
|
||
if let Some(region) = ®ion { | ||
let copy_cost = num_words(region.size.get()) * 3; | ||
state.gas_left -= copy_cost; | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(addr).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
if let Some(region) = region { | ||
let src = min(U256::from(MAX_BUFFER_SIZE), input_index).as_usize(); | ||
|
||
let num_bytes_copied = host | ||
.copy_code( | ||
addr, | ||
src, | ||
&mut state.memory[region.offset..region.offset + region.size.get()], | ||
) | ||
.await?; | ||
if region.size.get() - num_bytes_copied > 0 { | ||
state.memory[region.offset + num_bytes_copied..region.offset + region.size.get()] | ||
.fill(0); | ||
} | ||
} | ||
Ok(InstructionResolution::Continue) | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn returndatasize(state: &mut ExecutionState) { | ||
state.stack.push(state.return_data.len().into()); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn returndatacopy(state: &mut ExecutionState) -> InstructionResolution { | ||
let mem_index = state.stack.pop(); | ||
let input_index = state.stack.pop(); | ||
let size = state.stack.pop(); | ||
|
||
let region = if let Ok(r) = verify_memory_region(state, mem_index, size) { | ||
r | ||
} else { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
}; | ||
|
||
if input_index > U256::from(state.return_data.len()) { | ||
return InstructionResolution::Exit(StatusCode::InvalidMemoryAccess); | ||
} | ||
let src = input_index.as_usize(); | ||
|
||
if src + region.as_ref().map(|r| r.size.get()).unwrap_or(0) > state.return_data.len() { | ||
return InstructionResolution::Exit(StatusCode::InvalidMemoryAccess); | ||
} | ||
|
||
if let Some(region) = region { | ||
let copy_cost = num_words(region.size.get()) * 3; | ||
state.gas_left -= copy_cost; | ||
if state.gas_left < 0 { | ||
return InstructionResolution::Exit(StatusCode::OutOfGas); | ||
} | ||
|
||
state.memory[region.offset..region.offset + region.size.get()] | ||
.copy_from_slice(&state.return_data[src..src + region.size.get()]); | ||
} | ||
|
||
InstructionResolution::Continue | ||
} | ||
|
||
#[inline] | ||
pub(crate) async fn extcodehash<H: Host>( | ||
host: &mut H, | ||
state: &mut ExecutionState, | ||
) -> anyhow::Result<InstructionResolution> { | ||
let addr = u256_to_address(state.stack.pop()); | ||
|
||
if state.evm_revision >= Revision::Berlin | ||
&& host.access_account(addr).await? == AccessStatus::Cold | ||
{ | ||
state.gas_left -= i64::from(ADDITIONAL_COLD_ACCOUNT_ACCESS_COST); | ||
if state.gas_left < 0 { | ||
return Ok(InstructionResolution::Exit(StatusCode::OutOfGas)); | ||
} | ||
} | ||
|
||
state | ||
.stack | ||
.push(U256::from_big_endian(&host.get_code_hash(addr).await?.0)); | ||
Ok(InstructionResolution::Continue) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use crate::{ | ||
common::{Revision, StatusCode}, | ||
state::ExecutionState, | ||
}; | ||
use ethereum_types::U256; | ||
|
||
pub(crate) mod arithmetic; | ||
pub(crate) mod bitwise; | ||
pub(crate) mod boolean; | ||
pub(crate) mod call; | ||
pub(crate) mod control; | ||
pub(crate) mod external; | ||
pub(crate) mod instruction_table; | ||
pub(crate) mod memory; | ||
pub(crate) mod properties; | ||
pub(crate) mod stack_manip; | ||
|
||
#[must_use] | ||
pub(crate) enum InstructionResolution { | ||
Continue, | ||
Exit(StatusCode), | ||
Jump(usize), | ||
} | ||
|
||
pub use properties::PROPERTIES; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use super::*; | ||
use crate::state::*; | ||
|
||
pub(crate) fn load_push<const N: usize>(stack: &mut Stack, code: &[u8]) -> usize { | ||
stack.push(U256::from_big_endian(&code[..N])); | ||
N | ||
} | ||
|
||
pub(crate) fn dup<const N: usize>(stack: &mut Stack) { | ||
stack.push(*stack.get(N - 1)); | ||
} | ||
|
||
pub(crate) fn swap<const N: usize>(stack: &mut Stack) { | ||
stack.swap_top(N); | ||
} | ||
|
||
#[inline] | ||
pub(crate) fn pop(stack: &mut Stack) { | ||
stack.pop(); | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
//! Fast EVM implementation with full async support. Port of [evmone](https://github.com/ethereum/evmone) to Rust. | ||
//! | ||
//! # Usage | ||
//! ``` | ||
//! # tokio::runtime::Runtime::new().unwrap().block_on(async { | ||
//! use evmodin::{*, host::*, util::*, tracing::*}; | ||
//! use ethereum_types::*; | ||
//! use hex_literal::hex; | ||
//! | ||
//! let my_code = Bytecode::new() | ||
//! .mstore8_value(0, b'h') | ||
//! .mstore8_value(1, b'e') | ||
//! .mstore8_value(2, b'l') | ||
//! .mstore8_value(3, b'l') | ||
//! .mstore8_value(4, b'o') | ||
//! .ret(0, 5) | ||
//! .build(); | ||
//! let message = Message { | ||
//! kind: CallKind::Call, | ||
//! is_static: true, | ||
//! depth: 0, | ||
//! gas: 200, | ||
//! destination: Address::zero(), | ||
//! sender: Address::zero(), | ||
//! input_data: vec![].into(), | ||
//! value: U256::zero(), | ||
//! }; | ||
//! | ||
//! assert_eq!( | ||
//! AnalyzedCode::analyze(my_code) | ||
//! .execute(&mut DummyHost, &mut NoopTracer, message, Revision::London) | ||
//! .await | ||
//! .unwrap(), | ||
//! Output { | ||
//! status_code: StatusCode::Success, | ||
//! gas_left: 146, | ||
//! output_data: b"hello".to_vec().into(), | ||
//! create_address: None, | ||
//! } | ||
//! ) | ||
//! # }) | ||
//! ``` | ||
use bytes::Bytes; | ||
pub use common::{CallKind, Message, Output, Revision, StatusCode}; | ||
pub use host::Host; | ||
pub use interpreter::AnalyzedCode; | ||
pub use opcode::OpCode; | ||
|
||
mod common; | ||
pub mod host; | ||
#[doc(hidden)] | ||
pub mod instructions; | ||
mod interpreter; | ||
pub mod opcode; | ||
mod state; | ||
pub mod tracing; | ||
|
||
#[cfg(feature = "util")] | ||
pub mod util; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
use std::{borrow::Cow, fmt::Display}; | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub struct OpCode(pub u8); | ||
|
||
impl OpCode { | ||
#[inline] | ||
pub const fn to_u8(self) -> u8 { | ||
self.0 | ||
} | ||
|
||
#[inline] | ||
pub const fn to_usize(self) -> usize { | ||
self.to_u8() as usize | ||
} | ||
} | ||
|
||
impl OpCode { | ||
pub const STOP: OpCode = OpCode(0x00); | ||
pub const ADD: OpCode = OpCode(0x01); | ||
pub const MUL: OpCode = OpCode(0x02); | ||
pub const SUB: OpCode = OpCode(0x03); | ||
pub const DIV: OpCode = OpCode(0x04); | ||
pub const SDIV: OpCode = OpCode(0x05); | ||
pub const MOD: OpCode = OpCode(0x06); | ||
pub const SMOD: OpCode = OpCode(0x07); | ||
pub const ADDMOD: OpCode = OpCode(0x08); | ||
pub const MULMOD: OpCode = OpCode(0x09); | ||
pub const EXP: OpCode = OpCode(0x0a); | ||
pub const SIGNEXTEND: OpCode = OpCode(0x0b); | ||
|
||
pub const LT: OpCode = OpCode(0x10); | ||
pub const GT: OpCode = OpCode(0x11); | ||
pub const SLT: OpCode = OpCode(0x12); | ||
pub const SGT: OpCode = OpCode(0x13); | ||
pub const EQ: OpCode = OpCode(0x14); | ||
pub const ISZERO: OpCode = OpCode(0x15); | ||
pub const AND: OpCode = OpCode(0x16); | ||
pub const OR: OpCode = OpCode(0x17); | ||
pub const XOR: OpCode = OpCode(0x18); | ||
pub const NOT: OpCode = OpCode(0x19); | ||
pub const BYTE: OpCode = OpCode(0x1a); | ||
pub const SHL: OpCode = OpCode(0x1b); | ||
pub const SHR: OpCode = OpCode(0x1c); | ||
pub const SAR: OpCode = OpCode(0x1d); | ||
|
||
pub const KECCAK256: OpCode = OpCode(0x20); | ||
|
||
pub const ADDRESS: OpCode = OpCode(0x30); | ||
pub const BALANCE: OpCode = OpCode(0x31); | ||
pub const ORIGIN: OpCode = OpCode(0x32); | ||
pub const CALLER: OpCode = OpCode(0x33); | ||
pub const CALLVALUE: OpCode = OpCode(0x34); | ||
pub const CALLDATALOAD: OpCode = OpCode(0x35); | ||
pub const CALLDATASIZE: OpCode = OpCode(0x36); | ||
pub const CALLDATACOPY: OpCode = OpCode(0x37); | ||
pub const CODESIZE: OpCode = OpCode(0x38); | ||
pub const CODECOPY: OpCode = OpCode(0x39); | ||
pub const GASPRICE: OpCode = OpCode(0x3a); | ||
pub const EXTCODESIZE: OpCode = OpCode(0x3b); | ||
pub const EXTCODECOPY: OpCode = OpCode(0x3c); | ||
pub const RETURNDATASIZE: OpCode = OpCode(0x3d); | ||
pub const RETURNDATACOPY: OpCode = OpCode(0x3e); | ||
pub const EXTCODEHASH: OpCode = OpCode(0x3f); | ||
|
||
pub const BLOCKHASH: OpCode = OpCode(0x40); | ||
pub const COINBASE: OpCode = OpCode(0x41); | ||
pub const TIMESTAMP: OpCode = OpCode(0x42); | ||
pub const NUMBER: OpCode = OpCode(0x43); | ||
pub const DIFFICULTY: OpCode = OpCode(0x44); | ||
pub const GASLIMIT: OpCode = OpCode(0x45); | ||
pub const CHAINID: OpCode = OpCode(0x46); | ||
pub const SELFBALANCE: OpCode = OpCode(0x47); | ||
pub const BASEFEE: OpCode = OpCode(0x48); | ||
|
||
pub const POP: OpCode = OpCode(0x50); | ||
pub const MLOAD: OpCode = OpCode(0x51); | ||
pub const MSTORE: OpCode = OpCode(0x52); | ||
pub const MSTORE8: OpCode = OpCode(0x53); | ||
pub const SLOAD: OpCode = OpCode(0x54); | ||
pub const SSTORE: OpCode = OpCode(0x55); | ||
pub const JUMP: OpCode = OpCode(0x56); | ||
pub const JUMPI: OpCode = OpCode(0x57); | ||
pub const PC: OpCode = OpCode(0x58); | ||
pub const MSIZE: OpCode = OpCode(0x59); | ||
pub const GAS: OpCode = OpCode(0x5a); | ||
pub const JUMPDEST: OpCode = OpCode(0x5b); | ||
|
||
pub const PUSH1: OpCode = OpCode(0x60); | ||
pub const PUSH2: OpCode = OpCode(0x61); | ||
pub const PUSH3: OpCode = OpCode(0x62); | ||
pub const PUSH4: OpCode = OpCode(0x63); | ||
pub const PUSH5: OpCode = OpCode(0x64); | ||
pub const PUSH6: OpCode = OpCode(0x65); | ||
pub const PUSH7: OpCode = OpCode(0x66); | ||
pub const PUSH8: OpCode = OpCode(0x67); | ||
pub const PUSH9: OpCode = OpCode(0x68); | ||
pub const PUSH10: OpCode = OpCode(0x69); | ||
pub const PUSH11: OpCode = OpCode(0x6a); | ||
pub const PUSH12: OpCode = OpCode(0x6b); | ||
pub const PUSH13: OpCode = OpCode(0x6c); | ||
pub const PUSH14: OpCode = OpCode(0x6d); | ||
pub const PUSH15: OpCode = OpCode(0x6e); | ||
pub const PUSH16: OpCode = OpCode(0x6f); | ||
pub const PUSH17: OpCode = OpCode(0x70); | ||
pub const PUSH18: OpCode = OpCode(0x71); | ||
pub const PUSH19: OpCode = OpCode(0x72); | ||
pub const PUSH20: OpCode = OpCode(0x73); | ||
pub const PUSH21: OpCode = OpCode(0x74); | ||
pub const PUSH22: OpCode = OpCode(0x75); | ||
pub const PUSH23: OpCode = OpCode(0x76); | ||
pub const PUSH24: OpCode = OpCode(0x77); | ||
pub const PUSH25: OpCode = OpCode(0x78); | ||
pub const PUSH26: OpCode = OpCode(0x79); | ||
pub const PUSH27: OpCode = OpCode(0x7a); | ||
pub const PUSH28: OpCode = OpCode(0x7b); | ||
pub const PUSH29: OpCode = OpCode(0x7c); | ||
pub const PUSH30: OpCode = OpCode(0x7d); | ||
pub const PUSH31: OpCode = OpCode(0x7e); | ||
pub const PUSH32: OpCode = OpCode(0x7f); | ||
pub const DUP1: OpCode = OpCode(0x80); | ||
pub const DUP2: OpCode = OpCode(0x81); | ||
pub const DUP3: OpCode = OpCode(0x82); | ||
pub const DUP4: OpCode = OpCode(0x83); | ||
pub const DUP5: OpCode = OpCode(0x84); | ||
pub const DUP6: OpCode = OpCode(0x85); | ||
pub const DUP7: OpCode = OpCode(0x86); | ||
pub const DUP8: OpCode = OpCode(0x87); | ||
pub const DUP9: OpCode = OpCode(0x88); | ||
pub const DUP10: OpCode = OpCode(0x89); | ||
pub const DUP11: OpCode = OpCode(0x8a); | ||
pub const DUP12: OpCode = OpCode(0x8b); | ||
pub const DUP13: OpCode = OpCode(0x8c); | ||
pub const DUP14: OpCode = OpCode(0x8d); | ||
pub const DUP15: OpCode = OpCode(0x8e); | ||
pub const DUP16: OpCode = OpCode(0x8f); | ||
pub const SWAP1: OpCode = OpCode(0x90); | ||
pub const SWAP2: OpCode = OpCode(0x91); | ||
pub const SWAP3: OpCode = OpCode(0x92); | ||
pub const SWAP4: OpCode = OpCode(0x93); | ||
pub const SWAP5: OpCode = OpCode(0x94); | ||
pub const SWAP6: OpCode = OpCode(0x95); | ||
pub const SWAP7: OpCode = OpCode(0x96); | ||
pub const SWAP8: OpCode = OpCode(0x97); | ||
pub const SWAP9: OpCode = OpCode(0x98); | ||
pub const SWAP10: OpCode = OpCode(0x99); | ||
pub const SWAP11: OpCode = OpCode(0x9a); | ||
pub const SWAP12: OpCode = OpCode(0x9b); | ||
pub const SWAP13: OpCode = OpCode(0x9c); | ||
pub const SWAP14: OpCode = OpCode(0x9d); | ||
pub const SWAP15: OpCode = OpCode(0x9e); | ||
pub const SWAP16: OpCode = OpCode(0x9f); | ||
pub const LOG0: OpCode = OpCode(0xa0); | ||
pub const LOG1: OpCode = OpCode(0xa1); | ||
pub const LOG2: OpCode = OpCode(0xa2); | ||
pub const LOG3: OpCode = OpCode(0xa3); | ||
pub const LOG4: OpCode = OpCode(0xa4); | ||
|
||
pub const CREATE: OpCode = OpCode(0xf0); | ||
pub const CALL: OpCode = OpCode(0xf1); | ||
pub const CALLCODE: OpCode = OpCode(0xf2); | ||
pub const RETURN: OpCode = OpCode(0xf3); | ||
pub const DELEGATECALL: OpCode = OpCode(0xf4); | ||
pub const CREATE2: OpCode = OpCode(0xf5); | ||
|
||
pub const STATICCALL: OpCode = OpCode(0xfa); | ||
|
||
pub const REVERT: OpCode = OpCode(0xfd); | ||
pub const INVALID: OpCode = OpCode(0xfe); | ||
pub const SELFDESTRUCT: OpCode = OpCode(0xff); | ||
} | ||
|
||
impl OpCode { | ||
pub const fn name(&self) -> &'static str { | ||
match *self { | ||
OpCode::STOP => "STOP", | ||
OpCode::ADD => "ADD", | ||
OpCode::MUL => "MUL", | ||
OpCode::SUB => "SUB", | ||
OpCode::DIV => "DIV", | ||
OpCode::SDIV => "SDIV", | ||
OpCode::MOD => "MOD", | ||
OpCode::SMOD => "SMOD", | ||
OpCode::ADDMOD => "ADDMOD", | ||
OpCode::MULMOD => "MULMOD", | ||
OpCode::EXP => "EXP", | ||
OpCode::SIGNEXTEND => "SIGNEXTEND", | ||
OpCode::LT => "LT", | ||
OpCode::GT => "GT", | ||
OpCode::SLT => "SLT", | ||
OpCode::SGT => "SGT", | ||
OpCode::EQ => "EQ", | ||
OpCode::ISZERO => "ISZERO", | ||
OpCode::AND => "AND", | ||
OpCode::OR => "OR", | ||
OpCode::XOR => "XOR", | ||
OpCode::NOT => "NOT", | ||
OpCode::BYTE => "BYTE", | ||
OpCode::SHL => "SHL", | ||
OpCode::SHR => "SHR", | ||
OpCode::SAR => "SAR", | ||
OpCode::KECCAK256 => "KECCAK256", | ||
OpCode::ADDRESS => "ADDRESS", | ||
OpCode::BALANCE => "BALANCE", | ||
OpCode::ORIGIN => "ORIGIN", | ||
OpCode::CALLER => "CALLER", | ||
OpCode::CALLVALUE => "CALLVALUE", | ||
OpCode::CALLDATALOAD => "CALLDATALOAD", | ||
OpCode::CALLDATASIZE => "CALLDATASIZE", | ||
OpCode::CALLDATACOPY => "CALLDATACOPY", | ||
OpCode::CODESIZE => "CODESIZE", | ||
OpCode::CODECOPY => "CODECOPY", | ||
OpCode::GASPRICE => "GASPRICE", | ||
OpCode::EXTCODESIZE => "EXTCODESIZE", | ||
OpCode::EXTCODECOPY => "EXTCODECOPY", | ||
OpCode::RETURNDATASIZE => "RETURNDATASIZE", | ||
OpCode::RETURNDATACOPY => "RETURNDATACOPY", | ||
OpCode::EXTCODEHASH => "EXTCODEHASH", | ||
OpCode::BLOCKHASH => "BLOCKHASH", | ||
OpCode::COINBASE => "COINBASE", | ||
OpCode::TIMESTAMP => "TIMESTAMP", | ||
OpCode::NUMBER => "NUMBER", | ||
OpCode::DIFFICULTY => "DIFFICULTY", | ||
OpCode::GASLIMIT => "GASLIMIT", | ||
OpCode::CHAINID => "CHAINID", | ||
OpCode::SELFBALANCE => "SELFBALANCE", | ||
OpCode::BASEFEE => "BASEFEE", | ||
OpCode::POP => "POP", | ||
OpCode::MLOAD => "MLOAD", | ||
OpCode::MSTORE => "MSTORE", | ||
OpCode::MSTORE8 => "MSTORE8", | ||
OpCode::SLOAD => "SLOAD", | ||
OpCode::SSTORE => "SSTORE", | ||
OpCode::JUMP => "JUMP", | ||
OpCode::JUMPI => "JUMPI", | ||
OpCode::PC => "PC", | ||
OpCode::MSIZE => "MSIZE", | ||
OpCode::GAS => "GAS", | ||
OpCode::JUMPDEST => "JUMPDEST", | ||
OpCode::PUSH1 => "PUSH1", | ||
OpCode::PUSH2 => "PUSH2", | ||
OpCode::PUSH3 => "PUSH3", | ||
OpCode::PUSH4 => "PUSH4", | ||
OpCode::PUSH5 => "PUSH5", | ||
OpCode::PUSH6 => "PUSH6", | ||
OpCode::PUSH7 => "PUSH7", | ||
OpCode::PUSH8 => "PUSH8", | ||
OpCode::PUSH9 => "PUSH9", | ||
OpCode::PUSH10 => "PUSH10", | ||
OpCode::PUSH11 => "PUSH11", | ||
OpCode::PUSH12 => "PUSH12", | ||
OpCode::PUSH13 => "PUSH13", | ||
OpCode::PUSH14 => "PUSH14", | ||
OpCode::PUSH15 => "PUSH15", | ||
OpCode::PUSH16 => "PUSH16", | ||
OpCode::PUSH17 => "PUSH17", | ||
OpCode::PUSH18 => "PUSH18", | ||
OpCode::PUSH19 => "PUSH19", | ||
OpCode::PUSH20 => "PUSH20", | ||
OpCode::PUSH21 => "PUSH21", | ||
OpCode::PUSH22 => "PUSH22", | ||
OpCode::PUSH23 => "PUSH23", | ||
OpCode::PUSH24 => "PUSH24", | ||
OpCode::PUSH25 => "PUSH25", | ||
OpCode::PUSH26 => "PUSH26", | ||
OpCode::PUSH27 => "PUSH27", | ||
OpCode::PUSH28 => "PUSH28", | ||
OpCode::PUSH29 => "PUSH29", | ||
OpCode::PUSH30 => "PUSH30", | ||
OpCode::PUSH31 => "PUSH31", | ||
OpCode::PUSH32 => "PUSH32", | ||
OpCode::DUP1 => "DUP1", | ||
OpCode::DUP2 => "DUP2", | ||
OpCode::DUP3 => "DUP3", | ||
OpCode::DUP4 => "DUP4", | ||
OpCode::DUP5 => "DUP5", | ||
OpCode::DUP6 => "DUP6", | ||
OpCode::DUP7 => "DUP7", | ||
OpCode::DUP8 => "DUP8", | ||
OpCode::DUP9 => "DUP9", | ||
OpCode::DUP10 => "DUP10", | ||
OpCode::DUP11 => "DUP11", | ||
OpCode::DUP12 => "DUP12", | ||
OpCode::DUP13 => "DUP13", | ||
OpCode::DUP14 => "DUP14", | ||
OpCode::DUP15 => "DUP15", | ||
OpCode::DUP16 => "DUP16", | ||
OpCode::SWAP1 => "SWAP1", | ||
OpCode::SWAP2 => "SWAP2", | ||
OpCode::SWAP3 => "SWAP3", | ||
OpCode::SWAP4 => "SWAP4", | ||
OpCode::SWAP5 => "SWAP5", | ||
OpCode::SWAP6 => "SWAP6", | ||
OpCode::SWAP7 => "SWAP7", | ||
OpCode::SWAP8 => "SWAP8", | ||
OpCode::SWAP9 => "SWAP9", | ||
OpCode::SWAP10 => "SWAP10", | ||
OpCode::SWAP11 => "SWAP11", | ||
OpCode::SWAP12 => "SWAP12", | ||
OpCode::SWAP13 => "SWAP13", | ||
OpCode::SWAP14 => "SWAP14", | ||
OpCode::SWAP15 => "SWAP15", | ||
OpCode::SWAP16 => "SWAP16", | ||
OpCode::LOG0 => "LOG0", | ||
OpCode::LOG1 => "LOG1", | ||
OpCode::LOG2 => "LOG2", | ||
OpCode::LOG3 => "LOG3", | ||
OpCode::LOG4 => "LOG4", | ||
OpCode::CREATE => "CREATE", | ||
OpCode::CALL => "CALL", | ||
OpCode::CALLCODE => "CALLCODE", | ||
OpCode::RETURN => "RETURN", | ||
OpCode::DELEGATECALL => "DELEGATECALL", | ||
OpCode::CREATE2 => "CREATE2", | ||
OpCode::STATICCALL => "STATICCALL", | ||
OpCode::REVERT => "REVERT", | ||
OpCode::INVALID => "INVALID", | ||
OpCode::SELFDESTRUCT => "SELFDESTRUCT", | ||
_ => "UNDEFINED", | ||
} | ||
} | ||
} | ||
|
||
impl Display for OpCode { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
let name = self.name(); | ||
|
||
let n = if name == "UNDEFINED" { | ||
Cow::Owned(format!("UNDEFINED(0x{:02x})", self.0)) | ||
} else { | ||
Cow::Borrowed(name) | ||
}; | ||
write!(f, "{}", n) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use crate::common::{Message, Revision}; | ||
use arrayvec::ArrayVec; | ||
use bytes::Bytes; | ||
use ethereum_types::U256; | ||
use serde::Serialize; | ||
|
||
const SIZE: usize = 1024; | ||
|
||
#[derive(Clone, Debug, Default, Serialize)] | ||
pub struct Stack(pub ArrayVec<U256, SIZE>); | ||
|
||
impl Stack { | ||
pub const fn limit() -> usize { | ||
SIZE | ||
} | ||
|
||
#[inline] | ||
fn get_pos(&self, pos: usize) -> usize { | ||
self.len() - 1 - pos | ||
} | ||
|
||
pub fn get(&self, pos: usize) -> &U256 { | ||
&self.0[self.get_pos(pos)] | ||
} | ||
|
||
pub fn get_mut(&mut self, pos: usize) -> &mut U256 { | ||
let pos = self.get_pos(pos); | ||
&mut self.0[pos] | ||
} | ||
|
||
pub fn len(&self) -> usize { | ||
self.0.len() | ||
} | ||
|
||
pub fn push(&mut self, v: U256) { | ||
self.0.push(v) | ||
} | ||
|
||
pub fn pop(&mut self) -> U256 { | ||
self.0.pop().expect("underflow") | ||
} | ||
|
||
pub fn swap_top(&mut self, pos: usize) { | ||
let top = self.0.len() - 1; | ||
let pos = self.get_pos(pos); | ||
self.0.swap(top, pos); | ||
} | ||
} | ||
|
||
pub type Memory = Vec<u8>; | ||
|
||
/// Execution state | ||
#[derive(Debug)] | ||
pub struct ExecutionState { | ||
pub(crate) gas_left: i64, | ||
pub(crate) stack: Stack, | ||
pub(crate) memory: Memory, | ||
pub(crate) message: Message, | ||
pub(crate) evm_revision: Revision, | ||
pub(crate) return_data: Bytes, | ||
pub(crate) output_data: Bytes, | ||
pub(crate) current_block_cost: u32, | ||
} | ||
|
||
impl ExecutionState { | ||
pub fn new(message: Message, evm_revision: Revision) -> Self { | ||
Self { | ||
gas_left: message.gas, | ||
stack: Default::default(), | ||
memory: Memory::with_capacity(4 * 1024), | ||
message, | ||
evm_revision, | ||
return_data: Default::default(), | ||
output_data: Bytes::new(), | ||
current_block_cost: 0, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn stack() { | ||
let mut stack = Stack::default(); | ||
|
||
let items = [0xde, 0xad, 0xbe, 0xef]; | ||
|
||
for (i, item) in items.iter().copied().enumerate() { | ||
stack.push(item.into()); | ||
assert_eq!(stack.len(), i + 1); | ||
} | ||
|
||
assert_eq!(*stack.get(2), 0xad.into()); | ||
|
||
assert_eq!(stack.pop(), 0xef.into()); | ||
|
||
assert_eq!(*stack.get(2), 0xde.into()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use super::*; | ||
use crate::state::*; | ||
use serde::Serialize; | ||
|
||
/// Passed into execution context to collect metrics. | ||
pub trait Tracer { | ||
/// Called when execution starts. | ||
fn notify_execution_start(&mut self, revision: Revision, message: Message, code: Bytes); | ||
/// Called on each instruction. | ||
fn notify_instruction_start(&mut self, pc: usize, opcode: OpCode, state: &ExecutionState); | ||
/// Called when execution ends. | ||
fn notify_execution_end(&mut self, output: &Output); | ||
} | ||
|
||
/// Tracer which does nothing. | ||
pub struct NoopTracer; | ||
|
||
impl Tracer for NoopTracer { | ||
fn notify_execution_start(&mut self, _: Revision, _: Message, _: Bytes) {} | ||
|
||
fn notify_instruction_start(&mut self, _: usize, _: OpCode, _: &ExecutionState) {} | ||
|
||
fn notify_execution_end(&mut self, _: &Output) {} | ||
} | ||
|
||
#[derive(Serialize)] | ||
struct ExecutionStart { | ||
pub depth: i32, | ||
pub rev: Revision, | ||
#[serde(rename = "static")] | ||
pub is_static: bool, | ||
} | ||
|
||
#[derive(Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub(crate) struct InstructionStart { | ||
pub pc: usize, | ||
pub op: u8, | ||
pub op_name: &'static str, | ||
pub gas: i64, | ||
pub stack: Stack, | ||
pub memory_size: usize, | ||
} | ||
|
||
#[derive(Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub(crate) struct ExecutionEnd { | ||
pub error: Option<String>, | ||
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<TracerContext>, | ||
} | ||
|
||
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() | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
use crate::opcode::*; | ||
use core::iter::repeat; | ||
use ethereum_types::U256; | ||
|
||
/// EVM bytecode builder. | ||
#[derive(Clone, Debug)] | ||
pub struct Bytecode { | ||
inner: Vec<u8>, | ||
} | ||
|
||
impl Bytecode { | ||
pub const fn new() -> Self { | ||
Self { inner: Vec::new() } | ||
} | ||
|
||
pub fn append(mut self, b: impl IntoIterator<Item = u8>) -> Self { | ||
self.inner.append(&mut b.into_iter().collect::<Vec<_>>()); | ||
self | ||
} | ||
|
||
pub fn append_bc(mut self, b: impl Into<Self>) -> 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<U256>) -> Self { | ||
let value = value.into(); | ||
let b = <[u8; 32]>::from(value) | ||
.iter() | ||
.skip_while(|&&v| v == 0) | ||
.copied() | ||
.collect::<Vec<_>>(); | ||
|
||
self.pushb(b) | ||
} | ||
|
||
pub fn pushb(mut self, b: impl IntoIterator<Item = u8>) -> Self { | ||
let mut b = b.into_iter().collect::<Vec<_>>(); | ||
|
||
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<U256>, size: impl Into<U256>) -> Self { | ||
self = self.pushv(size); | ||
self = self.pushv(index); | ||
self = self.opcode(OpCode::RETURN); | ||
self | ||
} | ||
|
||
pub fn mstore(mut self, index: impl Into<U256>) -> Self { | ||
self = self.pushv(index); | ||
self = self.opcode(OpCode::MSTORE); | ||
self | ||
} | ||
|
||
pub fn mstore_value(mut self, index: impl Into<U256>, value: impl Into<U256>) -> Self { | ||
self = self.pushv(value); | ||
self = self.pushv(index); | ||
self = self.opcode(OpCode::MSTORE); | ||
self | ||
} | ||
|
||
pub fn mstore8(mut self, index: impl Into<U256>) -> Self { | ||
self = self.pushv(index); | ||
self = self.opcode(OpCode::MSTORE8); | ||
self | ||
} | ||
|
||
pub fn mstore8_value(mut self, index: impl Into<U256>, value: impl Into<U256>) -> 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<U256>) -> Self { | ||
self.pushv(target).opcode(OpCode::JUMP) | ||
} | ||
|
||
pub fn jumpi(self, target: impl Into<Bytecode>, condition: impl Into<Bytecode>) -> Self { | ||
self.append(condition.into().build()) | ||
.append(target.into().build()) | ||
.opcode(OpCode::JUMPI) | ||
} | ||
|
||
pub fn sstore(self, index: impl Into<U256>, value: impl Into<U256>) -> Self { | ||
self.pushv(value).pushv(index).opcode(OpCode::SSTORE) | ||
} | ||
|
||
pub fn sload(self, index: impl Into<U256>) -> Self { | ||
self.pushv(index).opcode(OpCode::SLOAD) | ||
} | ||
|
||
pub fn build(self) -> Vec<u8> { | ||
self.inner | ||
} | ||
} | ||
|
||
impl From<U256> for Bytecode { | ||
fn from(value: U256) -> Self { | ||
Self::new().pushv(value) | ||
} | ||
} | ||
|
||
impl From<OpCode> for Bytecode { | ||
fn from(opcode: OpCode) -> Self { | ||
Self::new().opcode(opcode) | ||
} | ||
} | ||
|
||
impl<const N: usize> From<[u8; N]> for Bytecode { | ||
fn from(inner: [u8; N]) -> Self { | ||
Self { | ||
inner: Vec::from(&inner as &[u8]), | ||
} | ||
} | ||
} | ||
|
||
impl From<Vec<u8>> for Bytecode { | ||
fn from(inner: Vec<u8>) -> Self { | ||
Self { inner } | ||
} | ||
} | ||
|
||
impl IntoIterator for Bytecode { | ||
type Item = u8; | ||
type IntoIter = <Vec<u8> as IntoIterator>::IntoIter; | ||
|
||
fn into_iter(self) -> Self::IntoIter { | ||
self.inner.into_iter() | ||
} | ||
} | ||
|
||
pub struct CallInstruction { | ||
op: OpCode, | ||
address: U256, | ||
gas: U256, | ||
value: U256, | ||
input: U256, | ||
input_size: U256, | ||
output: U256, | ||
output_size: U256, | ||
} | ||
|
||
impl CallInstruction { | ||
fn new(op: OpCode, address: impl Into<U256>) -> 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<U256>) -> Self { | ||
Self::new(OpCode::DELEGATECALL, address) | ||
} | ||
|
||
pub fn staticcall(address: impl Into<U256>) -> Self { | ||
Self::new(OpCode::STATICCALL, address) | ||
} | ||
|
||
pub fn call(address: impl Into<U256>) -> Self { | ||
Self::new(OpCode::CALL, address) | ||
} | ||
|
||
pub fn callcode(address: impl Into<U256>) -> Self { | ||
Self::new(OpCode::CALLCODE, address) | ||
} | ||
|
||
pub fn opcode(&self) -> OpCode { | ||
self.op | ||
} | ||
|
||
pub fn gas(mut self, gas: impl Into<U256>) -> Self { | ||
self.gas = gas.into(); | ||
self | ||
} | ||
|
||
pub fn value(mut self, value: impl Into<U256>) -> Self { | ||
self.value = value.into(); | ||
self | ||
} | ||
|
||
pub fn input(mut self, index: impl Into<U256>, size: impl Into<U256>) -> Self { | ||
self.input = index.into(); | ||
self.input_size = size.into(); | ||
self | ||
} | ||
|
||
pub fn output(mut self, index: impl Into<U256>, size: impl Into<U256>) -> Self { | ||
self.output = index.into(); | ||
self.output_size = size.into(); | ||
self | ||
} | ||
} | ||
|
||
impl From<CallInstruction> 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) | ||
} | ||
} |
Oops, something went wrong.