Skip to content

Commit

Permalink
[evm] initial implementation of evm execution utils
Browse files Browse the repository at this point in the history
This introduces a few wrapper and util functions that would allow one to
compile solidity code into EVM bytecode using solc and execute the
bytecode using SputnikVM (a Rust EVM implementation).

Closes: diem#56
  • Loading branch information
vgao1996 authored and bors-libra committed Feb 16, 2022
1 parent c706d00 commit 86d9d32
Show file tree
Hide file tree
Showing 12 changed files with 813 additions and 16 deletions.
422 changes: 411 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"devtools/x-core",
"devtools/x-lint",
"language/benchmarks",
"language/evm/exec-utils",
"language/evm/move-to-yul",
"language/move-analyzer",
"language/move-binary-format",
Expand Down
14 changes: 9 additions & 5 deletions crates/workspace-hack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ arrayvec = { version = "0.5.2", features = ["array-sizes-33-128", "std"] }
block-buffer = { version = "0.9.0", default-features = false, features = ["block-padding"] }
bstr = { version = "0.2.15", features = ["lazy_static", "regex-automata", "serde", "serde1", "serde1-nostd", "std", "unicode"] }
byteorder = { version = "1.4.3", features = ["std"] }
bytes = { version = "1.0.1", features = ["std"] }
codespan-reporting = { version = "0.11.1", default-features = false, features = ["serde", "serialization"] }
crossbeam-utils = { version = "0.8.3", features = ["lazy_static", "std"] }
crunchy = { version = "0.2.2", features = ["limit_128", "limit_256", "std"] }
getrandom = { version = "0.2.2", default-features = false, features = ["std"] }
log = { version = "0.4.14", default-features = false, features = ["serde", "std"] }
memchr = { version = "2.4.0", features = ["std", "use_std"] }
Expand All @@ -27,19 +29,21 @@ regex = { version = "1.4.3", features = ["aho-corasick", "memchr", "perf", "perf
regex-automata = { version = "0.1.9", features = ["regex-syntax", "std"] }
regex-syntax = { version = "0.6.23", features = ["unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-gencat", "unicode-perl", "unicode-script", "unicode-segment"] }
serde = { version = "1.0.130", features = ["derive", "rc", "serde_derive", "std"] }
tiny-keccak = { version = "2.0.2", features = ["keccak", "sha3"] }
tracing-core = { version = "0.1.21", features = ["lazy_static", "std"] }

[build-dependencies]
crunchy = { version = "0.2.2", features = ["limit_128", "limit_256", "std"] }
getrandom = { version = "0.2.2", default-features = false, features = ["std"] }
memchr = { version = "2.4.0", features = ["std", "use_std"] }
proc-macro2-9fbad63c4bcf4a8f = { package = "proc-macro2", version = "0.4.30", features = ["proc-macro"] }
proc-macro2-dff4ba8e3ae991db = { package = "proc-macro2", version = "1.0.28", features = ["proc-macro"] }
quote-3b31131e45eafb45 = { package = "quote", version = "0.6.13", features = ["proc-macro"] }
quote-dff4ba8e3ae991db = { package = "quote", version = "1.0.9", features = ["proc-macro"] }
proc-macro2 = { version = "0.4.30", features = ["proc-macro"] }
quote = { version = "0.6.13", features = ["proc-macro"] }
regex = { version = "1.4.3", features = ["aho-corasick", "memchr", "perf", "perf-cache", "perf-dfa", "perf-inline", "perf-literal", "std", "thread_local", "unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-gencat", "unicode-perl", "unicode-script", "unicode-segment"] }
regex-syntax = { version = "0.6.23", features = ["unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-gencat", "unicode-perl", "unicode-script", "unicode-segment"] }
serde = { version = "1.0.130", features = ["derive", "rc", "serde_derive", "std"] }
syn-3575ec1268b04181 = { package = "syn", version = "0.15.44", features = ["clone-impls", "derive", "extra-traits", "full", "parsing", "printing", "proc-macro", "quote", "visit"] }
syn-dff4ba8e3ae991db = { package = "syn", version = "1.0.74", features = ["clone-impls", "derive", "extra-traits", "full", "parsing", "printing", "proc-macro", "quote", "visit", "visit-mut"] }
syn-dff4ba8e3ae991db = { package = "syn", version = "1.0.74", features = ["clone-impls", "derive", "extra-traits", "fold", "full", "parsing", "printing", "proc-macro", "quote", "visit", "visit-mut"] }
tiny-keccak = { version = "2.0.2", features = ["keccak", "sha3"] }

[target.x86_64-unknown-linux-gnu.dependencies]
libc = { version = "0.2.112", features = ["std"] }
Expand Down
23 changes: 23 additions & 0 deletions language/evm/exec-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "evm-exec-utils"
version = "0.1.0"
authors = ["Diem Association <opensource@diem.com>"]
edition = "2021"
license = "Apache-2.0"
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# external dependencies
sha3 = "0.9.1"
evm = "0.33.1"
primitive-types = "0.10.1"
hex = "0.4.3"
tempfile = "3.2.0"
dirs = "4.0.0"
anyhow = "1.0.52"

# move dependencies
workspace-hack = { version = "0.1", path = "../../../crates/workspace-hack" }
move-command-line-common = { path = "../../move-command-line-common" }
10 changes: 10 additions & 0 deletions language/evm/exec-utils/contracts/a_plus_b.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.10;

contract APlusB {
function plus(uint a, uint b) public pure returns(uint) {
return a + b;
}
}
8 changes: 8 additions & 0 deletions language/evm/exec-utils/contracts/hello_world.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.10;

contract HelloWorld {
string public greet = "Hello World!";
}
12 changes: 12 additions & 0 deletions language/evm/exec-utils/contracts/two_functions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.10;

contract TwoFunctions {
function panic() pure public {
assert(false);
}

function do_nothing() public {}
}
72 changes: 72 additions & 0 deletions language/evm/exec-utils/src/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use anyhow::{bail, format_err, Result};
use std::{
collections::BTreeMap,
ffi::OsStr,
fs::{self},
path::{Path, PathBuf},
process::Command,
};

fn solc_path() -> Result<PathBuf> {
let solc_exe = move_command_line_common::env::read_env_var("SOLC_EXE");

if solc_exe.is_empty() {
bail!("failed to find path to solc -- is the environment variable SOLC_EXE set?")
}

Ok(PathBuf::from(&solc_exe))
}

fn solc_impl(
source_paths: impl IntoIterator<Item = impl AsRef<OsStr>>,
output_dir: &Path,
) -> Result<BTreeMap<String, Vec<u8>>> {
Command::new(&solc_path()?)
.args(source_paths)
.arg("--bin")
.arg("-o")
.arg(output_dir)
.output()
.map_err(|err| format_err!("failed to call solc: {:?}", err))?;

let mut compiled_contracts = BTreeMap::new();

for entry in fs::read_dir(output_dir)? {
let entry = entry?;

if entry.file_type()?.is_file() {
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "bin" {
let data = fs::read(&path)?;
let data = hex::decode(&data)?;

compiled_contracts.insert(
path.file_stem()
.ok_or_else(|| format_err!("failed to extract file name"))?
.to_string_lossy()
.to_string(),
data,
);
}
}
}
}

Ok(compiled_contracts)
}

/// Compile the solodity sources using solc.
/// Return a mapping with keys being contract names and values being compiled bytecode.
///
/// `~/bin/solc` must exist in order for this to work.
pub fn solc(
source_paths: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<BTreeMap<String, Vec<u8>>> {
let temp = tempfile::tempdir()?;

solc_impl(source_paths, temp.path())
}
157 changes: 157 additions & 0 deletions language/evm/exec-utils/src/exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use evm::{
backend::{Apply, ApplyBackend, MemoryBackend, MemoryVicinity},
executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata},
Config, ExitReason,
};
use primitive_types::{H160, H256, U256};
use sha3::{Digest, Keccak256};
use std::collections::BTreeMap;

/// Stateful EVM executor backed by an in-memory storage.
pub struct Executor<'v> {
storage_backend: MemoryBackend<'v>,
}

fn map_apply<F, T, U>(apply: Apply<T>, mut f: F) -> Apply<U>
where
F: FnMut(T) -> U,
{
match apply {
Apply::Modify {
address,
basic,
code,
storage,
reset_storage,
} => Apply::Modify {
address,
basic,
code,
storage: f(storage),
reset_storage,
},
Apply::Delete { address } => Apply::Delete { address },
}
}

// Try to find the address at which the contract has been deployed to.
//
// TODO: right now this feels more like a hack.
// We should study it more and determine if it's acceptable and see if we need to use CREATE2 instead.
fn find_contract_address<'a, I, T>(caller_address: H160, applies: I) -> H160
where
I: IntoIterator<Item = &'a Apply<T>>,
T: 'a,
{
for apply in applies {
if let Apply::Modify {
address,
code: Some(_),
..
} = apply
{
if *address != caller_address {
return *address;
}
}
}

panic!("failed to find contract address -- something is wrong")
}

/// Return the 4-byte method selector derived from the signature, which is encoded as a string (e.g. `"foo(uint256,uint256)"`).
//
// TODO: Rust type to represent the signature.
pub fn derive_method_selector(sig: &str) -> [u8; 4] {
let mut keccak = Keccak256::new();
keccak.update(sig.as_bytes());
let digest = keccak.finalize();
[digest[0], digest[1], digest[2], digest[3]]
}

impl<'v> Executor<'v> {
/// Create a new `Executor` with an empty in-memory storage backend.
//
// TODO: review the lifetime of vicinity.
pub fn new(vicinity: &'v MemoryVicinity) -> Self {
Self {
storage_backend: MemoryBackend::new(vicinity, BTreeMap::new()),
}
}

/// Return a reference to the in-memory storage backend.
pub fn storage(&self) -> &MemoryBackend<'v> {
&self.storage_backend
}

/// Create a contract and return the contract address if successful.
pub fn create_contract(
&mut self,
caller_address: H160,
contract_code: Vec<u8>,
) -> Result<H160, ExitReason> {
let config = Config::london();
let metadata = StackSubstateMetadata::new(u64::MAX, &config);
let state = MemoryStackState::new(metadata, &self.storage_backend);
let mut exec = StackExecutor::new_with_precompiles(state, &config, &());

let exit_reason =
exec.transact_create(caller_address, 0.into(), contract_code, u64::MAX, vec![]);
let state = exec.into_state();

let (changes, logs) = state.deconstruct();

match &exit_reason {
ExitReason::Succeed(_) => {
let changes: Vec<Apply<Vec<(H256, H256)>>> = changes
.into_iter()
.map(|app| map_apply(app, |entries| entries.into_iter().collect()))
.collect();

let contract_addr = find_contract_address(caller_address, &changes);

self.storage_backend.apply(changes, logs, false);
Ok(contract_addr)
}
_ => {
self.storage_backend.apply(changes, logs, false);
Err(exit_reason)
}
}
}

/// Call a contract method with the given signature.
pub fn call_function(
&mut self,
caller_address: H160,
contract_address: H160,
method_sig: &str,
method_args: &[u8],
) -> (ExitReason, Vec<u8>) {
let config = Config::london();
let metadata = StackSubstateMetadata::new(u64::MAX, &config);
let state = MemoryStackState::new(metadata, &self.storage_backend);
let mut exec = StackExecutor::new_with_precompiles(state, &config, &());

let mut data = vec![];
data.extend(derive_method_selector(method_sig));
data.extend(method_args);

exec.transact_call(
caller_address,
contract_address,
U256::zero(),
data,
u64::MAX,
vec![],
)
}

// TODO: implement this.
// pub fn run_custom_code(&mut self, _code: Vec<u8>, _data: Vec<u8>) {
// unimplemented!()
// }
}
7 changes: 7 additions & 0 deletions language/evm/exec-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

pub mod compile;
pub mod exec;
#[cfg(test)]
pub mod tests;
Loading

0 comments on commit 86d9d32

Please sign in to comment.