-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[evm] initial implementation of evm execution utils
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
1 parent
c706d00
commit 86d9d32
Showing
12 changed files
with
813 additions
and
16 deletions.
There are no files selected for viewing
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
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
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,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" } |
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,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; | ||
} | ||
} |
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,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!"; | ||
} |
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,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 {} | ||
} |
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,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()) | ||
} |
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,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!() | ||
// } | ||
} |
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,7 @@ | ||
// Copyright (c) The Diem Core Contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
pub mod compile; | ||
pub mod exec; | ||
#[cfg(test)] | ||
pub mod tests; |
Oops, something went wrong.