diff --git a/Cargo.toml b/Cargo.toml index d265bcdbd..7703562e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "bindings/rust/evmc-sys", "bindings/rust/evmc-vm", + "bindings/rust/evmc-client", "bindings/rust/evmc-declare", "bindings/rust/evmc-declare-tests", "examples/example-rust-vm" diff --git a/bindings/rust/evmc-client/.gitignore b/bindings/rust/evmc-client/.gitignore new file mode 100644 index 000000000..84c47ed70 --- /dev/null +++ b/bindings/rust/evmc-client/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +/Cargo.lock diff --git a/bindings/rust/evmc-client/Cargo.toml b/bindings/rust/evmc-client/Cargo.toml new file mode 100644 index 000000000..a4fe0b5fa --- /dev/null +++ b/bindings/rust/evmc-client/Cargo.toml @@ -0,0 +1,22 @@ +# EVMC: Ethereum Client-VM Connector API. +# Copyright 2019 The EVMC Authors. +# Licensed under the Apache License, Version 2.0. + +[package] +name = "evmc-client" +version = "7.4.0" +authors = ["Zigang Lin "] +license = "Apache-2.0" +repository = "https://github.com/ethereum/evmc" +description = "Bindings to EVMC (Client/Host specific)" +edition = "2018" +build = "build.rs" + +[dependencies] +evmc-sys = { path = "../evmc-sys" } +evmc-vm = { path = "../evmc-vm" } +enum_primitive = "0.1.1" +num = "0.3" + +[build-dependencies] +cmake = "0.1.44" diff --git a/bindings/rust/evmc-client/build.rs b/bindings/rust/evmc-client/build.rs new file mode 100644 index 000000000..02f7be8fb --- /dev/null +++ b/bindings/rust/evmc-client/build.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2020 Second State. +// This file is part of EVMC-Client. + +// EVMC-Client is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. + +// EVMC-Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; +extern crate cmake; +use cmake::Config; + +macro_rules! get(($name:expr) => (ok!(env::var($name)))); +macro_rules! ok(($expression:expr) => ($expression.unwrap())); + +const REPOSITORY: &'static str = "https://github.com/second-state/evmc.git"; +const TAG: &'static str = "v7.4.0-rust-evmc-client-rc.2"; + +fn run(name: &str, mut configure: F) +where + F: FnMut(&mut Command) -> &mut Command, +{ + let mut command = Command::new(name); + let configured = configure(&mut command); + if !ok!(configured.status()).success() { + panic!("failed to execute {:?}", configured); + } +} + +fn build_from_src() { + let source = PathBuf::from(&get!("CARGO_MANIFEST_DIR")).join(format!("target/evmc-{}", TAG)); + if !Path::new(&source.join(".git")).exists() { + run("git", |command| { + command + .arg("clone") + .arg(format!("--branch={}", TAG)) + .arg("--recursive") + .arg(REPOSITORY) + .arg(&source) + }); + } + + let dst = Config::new(source).build(); + let evmc_path = Path::new(&dst).join("build/lib/loader"); + println!("cargo:rustc-link-search=native={}", evmc_path.display()); + println!("cargo:rustc-link-lib=static=evmc-loader"); +} + +fn main() { + build_from_src(); +} diff --git a/bindings/rust/evmc-client/src/host.rs b/bindings/rust/evmc-client/src/host.rs new file mode 100644 index 000000000..7e62e4544 --- /dev/null +++ b/bindings/rust/evmc-client/src/host.rs @@ -0,0 +1,237 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2019 The EVMC Authors. + * Licensed under the Apache License, Version 2.0. + */ + +use crate::types::*; +use evmc_sys as ffi; +use std::mem; + +#[repr(C)] +pub(crate) struct ExtendedContext<'a> { + pub hctx: &'a mut dyn HostContext, +} + +pub trait HostContext { + fn account_exists(&mut self, addr: &Address) -> bool; + fn get_storage(&mut self, addr: &Address, key: &Bytes32) -> Bytes32; + fn set_storage(&mut self, addr: &Address, key: &Bytes32, value: &Bytes32) -> StorageStatus; + fn get_balance(&mut self, addr: &Address) -> Bytes32; + fn get_code_size(&mut self, addr: &Address) -> usize; + fn get_code_hash(&mut self, addr: &Address) -> Bytes32; + fn copy_code( + &mut self, + addr: &Address, + offset: &usize, + buffer_data: &*mut u8, + buffer_size: &usize, + ) -> usize; + fn selfdestruct(&mut self, addr: &Address, beneficiary: &Address); + fn get_tx_context(&mut self) -> (Bytes32, Address, Address, i64, i64, i64, Bytes32, Bytes32); + fn get_block_hash(&mut self, number: i64) -> Bytes32; + fn emit_log(&mut self, addr: &Address, topics: &Vec, data: &Bytes); + fn call( + &mut self, + kind: MessageKind, + destination: &Address, + sender: &Address, + value: &Bytes32, + input: &Bytes, + gas: i64, + depth: i32, + is_static: bool, + salt: &Bytes32, + ) -> (Vec, i64, Address, StatusCode); +} + +pub(crate) fn get_evmc_host_interface() -> ffi::evmc_host_interface { + ffi::evmc_host_interface { + account_exists: Some(account_exists), + get_storage: Some(get_storage), + set_storage: Some(set_storage), + get_balance: Some(get_balance), + get_code_size: Some(get_code_size), + get_code_hash: Some(get_code_hash), + copy_code: Some(copy_code), + selfdestruct: Some(selfdestruct), + call: Some(call), + get_tx_context: Some(get_tx_context), + get_block_hash: Some(get_block_hash), + emit_log: Some(emit_log), + } +} + +unsafe extern "C" fn account_exists( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, +) -> bool { + return (*(context as *mut ExtendedContext)) + .hctx + .account_exists(&(*address).bytes); +} + +unsafe extern "C" fn get_storage( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, + key: *const ffi::evmc_bytes32, +) -> ffi::evmc_bytes32 { + return ffi::evmc_bytes32 { + bytes: (*(context as *mut ExtendedContext)) + .hctx + .get_storage(&(*address).bytes, &(*key).bytes), + }; +} + +unsafe extern "C" fn set_storage( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, + key: *const ffi::evmc_bytes32, + value: *const ffi::evmc_bytes32, +) -> ffi::evmc_storage_status { + return (*(context as *mut ExtendedContext)).hctx.set_storage( + &(*address).bytes, + &(*key).bytes, + &(*value).bytes, + ); +} + +unsafe extern "C" fn get_balance( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, +) -> ffi::evmc_uint256be { + return ffi::evmc_uint256be { + bytes: (*(context as *mut ExtendedContext)) + .hctx + .get_balance(&(*address).bytes), + }; +} + +unsafe extern "C" fn get_code_size( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, +) -> usize { + return (*(context as *mut ExtendedContext)) + .hctx + .get_code_size(&(*address).bytes); +} + +unsafe extern "C" fn get_code_hash( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, +) -> ffi::evmc_bytes32 { + return ffi::evmc_bytes32 { + bytes: (*(context as *mut ExtendedContext)) + .hctx + .get_code_hash(&(*address).bytes), + }; +} + +unsafe extern "C" fn copy_code( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, + code_offset: usize, + buffer_data: *mut u8, + buffer_size: usize, +) -> usize { + return (*(context as *mut ExtendedContext)).hctx.copy_code( + &(*address).bytes, + &code_offset, + &buffer_data, + &buffer_size, + ); +} + +unsafe extern "C" fn selfdestruct( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, + beneficiary: *const ffi::evmc_address, +) { + (*(context as *mut ExtendedContext)) + .hctx + .selfdestruct(&(*address).bytes, &(*beneficiary).bytes) +} + +unsafe extern "C" fn get_tx_context(context: *mut ffi::evmc_host_context) -> ffi::evmc_tx_context { + let (gas_price, origin, coinbase, number, timestamp, gas_limit, difficulty, chain_id) = + (*(context as *mut ExtendedContext)).hctx.get_tx_context(); + return ffi::evmc_tx_context { + tx_gas_price: evmc_sys::evmc_bytes32 { bytes: gas_price }, + tx_origin: evmc_sys::evmc_address { bytes: origin }, + block_coinbase: evmc_sys::evmc_address { bytes: coinbase }, + block_number: number, + block_timestamp: timestamp, + block_gas_limit: gas_limit, + block_difficulty: evmc_sys::evmc_bytes32 { bytes: difficulty }, + chain_id: evmc_sys::evmc_bytes32 { bytes: chain_id }, + }; +} + +unsafe extern "C" fn get_block_hash( + context: *mut ffi::evmc_host_context, + number: i64, +) -> ffi::evmc_bytes32 { + return ffi::evmc_bytes32 { + bytes: (*(context as *mut ExtendedContext)) + .hctx + .get_block_hash(number), + }; +} + +unsafe extern "C" fn emit_log( + context: *mut ffi::evmc_host_context, + address: *const ffi::evmc_address, + data: *const u8, + data_size: usize, + topics: *const ffi::evmc_bytes32, + topics_count: usize, +) { + let ts = &std::slice::from_raw_parts(topics, topics_count) + .iter() + .map(|topic| topic.bytes) + .collect::>(); + (*(context as *mut ExtendedContext)).hctx.emit_log( + &(*address).bytes, + &ts, + &std::slice::from_raw_parts(data, data_size), + ); +} + +unsafe extern "C" fn release(result: *const ffi::evmc_result) { + drop(std::slice::from_raw_parts( + (*result).output_data, + (*result).output_size, + )); +} + +pub unsafe extern "C" fn call( + context: *mut ffi::evmc_host_context, + msg: *const ffi::evmc_message, +) -> ffi::evmc_result { + let msg = *msg; + let (output, gas_left, create_address, status_code) = + (*(context as *mut ExtendedContext)).hctx.call( + msg.kind, + &msg.destination.bytes, + &msg.sender.bytes, + &msg.value.bytes, + &std::slice::from_raw_parts(msg.input_data, msg.input_size), + msg.gas, + msg.depth, + msg.flags != 0, + &msg.create2_salt.bytes, + ); + let ptr = output.as_ptr(); + let len = output.len(); + mem::forget(output); + return ffi::evmc_result { + status_code: status_code, + gas_left: gas_left, + output_data: ptr, + output_size: len, + release: Some(release), + create_address: ffi::evmc_address { + bytes: create_address, + }, + padding: [0u8; 4], + }; +} diff --git a/bindings/rust/evmc-client/src/lib.rs b/bindings/rust/evmc-client/src/lib.rs new file mode 100644 index 000000000..dce83bb22 --- /dev/null +++ b/bindings/rust/evmc-client/src/lib.rs @@ -0,0 +1,139 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2019 The EVMC Authors. + * Licensed under the Apache License, Version 2.0. + */ + +#[macro_use] +extern crate enum_primitive; +pub mod host; +mod loader; +pub mod types; +pub use crate::loader::{load_and_create, EvmcLoaderErrorCode}; +use crate::types::*; +use evmc_sys as ffi; +use std::ffi::CStr; + +extern "C" { + fn evmc_create() -> *mut ffi::evmc_vm; +} + +pub struct EvmcVm { + handle: *mut ffi::evmc_vm, + host_interface: *mut ffi::evmc_host_interface, +} + +impl EvmcVm { + pub fn get_abi_version(&self) -> i32 { + unsafe { + let version: i32 = (*self.handle).abi_version; + version + } + } + + pub fn get_name(&self) -> &str { + unsafe { + let c_str: &CStr = CStr::from_ptr((*self.handle).name); + c_str.to_str().unwrap() + } + } + + pub fn get_version(&self) -> &str { + unsafe { + let c_str: &CStr = CStr::from_ptr((*self.handle).version); + c_str.to_str().unwrap() + } + } + + pub fn destroy(&self) { + unsafe { ((*self.handle).destroy.unwrap())(self.handle) } + } + + pub fn execute( + &self, + ctx: &mut dyn host::HostContext, + rev: Revision, + kind: MessageKind, + is_static: bool, + depth: i32, + gas: i64, + destination: &Address, + sender: &Address, + input: &Bytes, + value: &Bytes32, + code: &Bytes, + create2_salt: &Bytes32, + ) -> (&Bytes, i64, StatusCode) { + let ext_ctx = host::ExtendedContext { hctx: ctx }; + let mut evmc_flags: u32 = 0; + unsafe { + if is_static { + evmc_flags |= + std::mem::transmute::(ffi::evmc_flags::EVMC_STATIC); + } + } + let evmc_message = Box::into_raw(Box::new({ + ffi::evmc_message { + kind: kind, + flags: evmc_flags, + depth: depth, + gas: gas, + destination: ffi::evmc_address { + bytes: *destination, + }, + sender: ffi::evmc_address { bytes: *sender }, + input_data: input.as_ptr(), + input_size: input.len(), + value: ffi::evmc_uint256be { bytes: *value }, + create2_salt: ffi::evmc_bytes32 { + bytes: *create2_salt, + }, + } + })); + unsafe { + let result = ((*self.handle).execute.unwrap())( + self.handle, + self.host_interface, + // ext_ctx as *mut ffi::evmc_host_context, + std::mem::transmute::<&host::ExtendedContext, *mut ffi::evmc_host_context>( + &ext_ctx, + ), + rev, + evmc_message, + code.as_ptr(), + code.len(), + ); + return ( + std::slice::from_raw_parts(result.output_data, result.output_size), + result.gas_left, + result.status_code, + ); + } + } + + pub fn has_capability(&self, capability: Capabilities) -> bool { + unsafe { + std::mem::transmute::(capability) + == ((*self.handle).get_capabilities.unwrap())(self.handle) + } + } +} + +pub fn load(fname: &str) -> (EvmcVm, Result) { + let (instance, ec) = load_and_create(fname); + ( + EvmcVm { + handle: instance, + host_interface: Box::into_raw(Box::new(host::get_evmc_host_interface())), + }, + ec, + ) +} + +pub fn create() -> EvmcVm { + unsafe { + EvmcVm { + handle: evmc_create(), + host_interface: Box::into_raw(Box::new(host::get_evmc_host_interface())), + } + } +} diff --git a/bindings/rust/evmc-client/src/loader.rs b/bindings/rust/evmc-client/src/loader.rs new file mode 100644 index 000000000..6d4879b5f --- /dev/null +++ b/bindings/rust/evmc-client/src/loader.rs @@ -0,0 +1,70 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2019 The EVMC Authors. + * Licensed under the Apache License, Version 2.0. + */ + +use evmc_sys as ffi; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::str; +extern crate num; +use num::FromPrimitive; + +#[link(name = "evmc-loader")] +extern "C" { + fn evmc_load_and_create( + filename: *const c_char, + evmc_loader_error_code: *mut i32, + ) -> *mut ffi::evmc_vm; + fn evmc_last_error_msg() -> *const c_char; +} + +enum_from_primitive! { +#[derive(Debug)] +pub enum EvmcLoaderErrorCode { + /** The loader succeeded. */ + EvmcLoaderSucces = 0, + + /** The loader cannot open the given file name. */ + EvmcLoaderCannotOpen = 1, + + /** The VM create function not found. */ + EvmcLoaderSymbolNotFound = 2, + + /** The invalid argument value provided. */ + EvmcLoaderInvalidArgument = 3, + + /** The creation of a VM instance has failed. */ + EvmcLoaderInstanceCreationFailure = 4, + + /** The ABI version of the VM instance has mismatched. */ + EvmcLoaderAbiVersionMismatch = 5, + + /** The VM option is invalid. */ + EvmcLoaderInvalidOptionName = 6, + + /** The VM option value is invalid. */ + EvmcLoaderInvalidOptionValue = 7, +} +} + +fn error(err: EvmcLoaderErrorCode) -> Result { + match err { + EvmcLoaderErrorCode::EvmcLoaderSucces => Ok(EvmcLoaderErrorCode::EvmcLoaderSucces), + _ => unsafe { Err(CStr::from_ptr(evmc_last_error_msg()).to_str().unwrap()) }, + } +} + +pub fn load_and_create( + fname: &str, +) -> (*mut ffi::evmc_vm, Result) { + let c_str = CString::new(fname).unwrap(); + unsafe { + let mut error_code: i32 = 0; + let instance = evmc_load_and_create(c_str.as_ptr() as *const c_char, &mut error_code); + return ( + instance, + error(EvmcLoaderErrorCode::from_i32(error_code).unwrap()), + ); + } +} diff --git a/bindings/rust/evmc-client/src/types.rs b/bindings/rust/evmc-client/src/types.rs new file mode 100644 index 000000000..5489ba52b --- /dev/null +++ b/bindings/rust/evmc-client/src/types.rs @@ -0,0 +1,12 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2019 The EVMC Authors. + * Licensed under the Apache License, Version 2.0. + */ + +pub use evmc_vm::{Capabilities, MessageKind, Revision, StatusCode, StorageStatus}; + +pub const ADDRESS_LENGTH: usize = 20; +pub const BYTES32_LENGTH: usize = 32; +pub type Address = [u8; ADDRESS_LENGTH]; +pub type Bytes32 = [u8; BYTES32_LENGTH]; +pub type Bytes = [u8]; diff --git a/bindings/rust/evmc-sys/build.rs b/bindings/rust/evmc-sys/build.rs index 6c2d9112b..a67100b4e 100644 --- a/bindings/rust/evmc-sys/build.rs +++ b/bindings/rust/evmc-sys/build.rs @@ -24,7 +24,7 @@ fn gen_bindings() { .derive_hash(true) // force deriving the PratialEq trait on basic types (address, bytes32) .derive_partialeq(true) - .opaque_type("evmc_host_context") + .blacklist_type("evmc_host_context") .whitelist_type("evmc_.*") .whitelist_function("evmc_.*") .whitelist_var("EVMC_ABI_VERSION") diff --git a/bindings/rust/evmc-sys/src/lib.rs b/bindings/rust/evmc-sys/src/lib.rs index da4751469..21900d30e 100644 --- a/bindings/rust/evmc-sys/src/lib.rs +++ b/bindings/rust/evmc-sys/src/lib.rs @@ -9,6 +9,12 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +// Defining evmc_host_context here, because bindgen cannot create a useful declaration yet. + +pub type evmc_host_context = ::std::os::raw::c_void; + +//pub struct evmc_host_context { } + // TODO: add `.derive_default(true)` to bindgen instead? impl Default for evmc_address { diff --git a/bindings/rust/evmc-vm/src/types.rs b/bindings/rust/evmc-vm/src/types.rs index 3b7ba601d..8290efd45 100644 --- a/bindings/rust/evmc-vm/src/types.rs +++ b/bindings/rust/evmc-vm/src/types.rs @@ -15,6 +15,9 @@ pub type MessageKind = ffi::evmc_call_kind; /// EVMC message (call) flags. pub type MessageFlags = ffi::evmc_flags; +/// EVMC VM capabilities. +pub type Capabilities = ffi::evmc_capabilities; + /// EVMC status code. pub type StatusCode = ffi::evmc_status_code; @@ -69,6 +72,22 @@ mod tests { assert_eq!(MessageFlags::EVMC_STATIC, ffi::evmc_flags::EVMC_STATIC); } + #[test] + fn capabilities() { + assert_eq!( + Capabilities::EVMC_CAPABILITY_EVM1, + ffi::evmc_capabilities::EVMC_CAPABILITY_EVM1 + ); + assert_eq!( + Capabilities::EVMC_CAPABILITY_EWASM, + ffi::evmc_capabilities::EVMC_CAPABILITY_EWASM + ); + assert_eq!( + Capabilities::EVMC_CAPABILITY_PRECOMPILES, + ffi::evmc_capabilities::EVMC_CAPABILITY_PRECOMPILES + ); + } + #[test] fn status_code() { assert_eq!(