diff --git a/Cargo.toml b/Cargo.toml index 0e0e1b774..1747c137c 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", "bindings/rust/evmc-loader", 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..cb40019c6 --- /dev/null +++ b/bindings/rust/evmc-client/Cargo.toml @@ -0,0 +1,19 @@ +# 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" + +[dependencies] +evmc-loader = { path = "../evmc-loader" } +evmc-sys = { path = "../evmc-sys" } +evmc-vm = { path = "../evmc-vm" } +enum_primitive = "0.1.1" +num = "0.3" diff --git a/bindings/rust/evmc-client/src/host.rs b/bindings/rust/evmc-client/src/host.rs new file mode 100644 index 000000000..678d46de8 --- /dev/null +++ b/bindings/rust/evmc-client/src/host.rs @@ -0,0 +1,261 @@ +/* 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, + 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, + recipient: &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), + access_account: None, // TODO + access_storage: None, // TODO + } +} + +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, + prev_randao, + chain_id, + base_fee, + ) = (*(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_prev_randao: evmc_sys::evmc_bytes32 { bytes: prev_randao }, + chain_id: evmc_sys::evmc_bytes32 { bytes: chain_id }, + block_base_fee: evmc_sys::evmc_bytes32 { bytes: base_fee }, + }; +} + +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.recipient.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..3f7b471a7 --- /dev/null +++ b/bindings/rust/evmc-client/src/lib.rs @@ -0,0 +1,128 @@ +/* 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; +pub mod types; +use crate::types::*; +use evmc_loader; +use evmc_sys as ffi; +use std::ffi::CStr; + +pub struct EvmcVm { + handle: *mut ffi::evmc_vm, + host_interface: *mut ffi::evmc_host_interface, +} + +impl Drop for EvmcVm { + fn drop(&mut self) { + debug_assert!(!self.handle.is_null()); + unsafe { ((*self.handle).destroy.unwrap())(self.handle) } + } +} + +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 execute( + &self, + ctx: &mut dyn host::HostContext, + rev: Revision, + kind: MessageKind, + is_static: bool, + depth: i32, + gas: i64, + recipient: &Address, + sender: &Address, + input: &Bytes, + value: &Bytes32, + code: &Bytes, + create2_salt: &Bytes32, + code_address: &Address, + ) -> (&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, + recipient: ffi::evmc_address { bytes: *recipient }, + 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, + }, + code_address: ffi::evmc_address { + bytes: *code_address, + }, + } + })); + 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_and_configure( + config: &str, +) -> Result { + Ok(EvmcVm { + handle: evmc_loader::load_and_configure(config)? as *mut ffi::evmc_vm, + host_interface: Box::into_raw(Box::new(host::get_evmc_host_interface())), + }) +} diff --git a/bindings/rust/evmc-client/src/types.rs b/bindings/rust/evmc-client/src/types.rs new file mode 100644 index 000000000..15fcec4d9 --- /dev/null +++ b/bindings/rust/evmc-client/src/types.rs @@ -0,0 +1,10 @@ +/* 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 type Address = [u8; 20]; +pub type Bytes32 = [u8; 32]; +pub type Bytes = [u8]; diff --git a/bindings/rust/evmc-loader/src/lib.rs b/bindings/rust/evmc-loader/src/lib.rs index 21b70066a..cd592f006 100644 --- a/bindings/rust/evmc-loader/src/lib.rs +++ b/bindings/rust/evmc-loader/src/lib.rs @@ -4,7 +4,7 @@ use std::ffi::{CStr, CString}; -mod sys; +pub mod sys; pub fn load_and_configure( config: &str, diff --git a/bindings/rust/evmc-vm/src/types.rs b/bindings/rust/evmc-vm/src/types.rs index 336400a57..ebb04f03c 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; @@ -72,6 +75,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!(