From 71685ca7d7566b1a84075448e8d5a57cb3c5c03d Mon Sep 17 00:00:00 2001 From: Jake Lang Date: Wed, 13 Mar 2019 20:47:55 +0100 Subject: [PATCH] Procedural macro for automatic VM declaration --- Cargo.toml | 2 + bindings/rust/evmc-declare-tests/Cargo.toml | 10 + bindings/rust/evmc-declare-tests/src/lib.rs | 20 ++ bindings/rust/evmc-declare/Cargo.toml | 17 ++ bindings/rust/evmc-declare/src/lib.rs | 310 ++++++++++++++++++++ bindings/rust/evmc-vm/Cargo.toml | 1 + bindings/rust/evmc-vm/src/container.rs | 35 +++ bindings/rust/evmc-vm/src/lib.rs | 233 ++++++++------- examples/example-rust-vm/Cargo.toml | 1 + examples/example-rust-vm/src/lib.rs | 71 ++--- 10 files changed, 536 insertions(+), 164 deletions(-) create mode 100644 bindings/rust/evmc-declare-tests/Cargo.toml create mode 100644 bindings/rust/evmc-declare-tests/src/lib.rs create mode 100644 bindings/rust/evmc-declare/Cargo.toml create mode 100644 bindings/rust/evmc-declare/src/lib.rs create mode 100644 bindings/rust/evmc-vm/src/container.rs diff --git a/Cargo.toml b/Cargo.toml index e2f53611b..d265bcdbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,7 @@ members = [ "bindings/rust/evmc-sys", "bindings/rust/evmc-vm", + "bindings/rust/evmc-declare", + "bindings/rust/evmc-declare-tests", "examples/example-rust-vm" ] diff --git a/bindings/rust/evmc-declare-tests/Cargo.toml b/bindings/rust/evmc-declare-tests/Cargo.toml new file mode 100644 index 000000000..0a9ffe6d7 --- /dev/null +++ b/bindings/rust/evmc-declare-tests/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "evmc-declare-tests" +version = "0.1.0" +authors = ["Jake Lang "] +edition = "2018" + +[dependencies] +evmc-declare = { path = "../evmc-declare" } +evmc-sys = { path = "../evmc-sys" } +evmc-vm = { path = "../evmc-vm" } diff --git a/bindings/rust/evmc-declare-tests/src/lib.rs b/bindings/rust/evmc-declare-tests/src/lib.rs new file mode 100644 index 000000000..d827ec6f0 --- /dev/null +++ b/bindings/rust/evmc-declare-tests/src/lib.rs @@ -0,0 +1,20 @@ +use evmc_vm::EvmcVm; +use evmc_vm::ExecutionContext; +use evmc_vm::ExecutionResult; +#[macro_use] +use evmc_declare::evmc_declare_vm; + +#[evmc_declare_vm("Foo VM", "ewasm")] +pub struct FooVM { + a: i32, +} + +impl EvmcVm for FooVM { + fn init() -> Self { + FooVM { a: 105023 } + } + + fn execute(&self, code: &[u8], context: &ExecutionContext) -> ExecutionResult { + ExecutionResult::new(evmc_sys::evmc_status_code::EVMC_SUCCESS, 235117, None) + } +} diff --git a/bindings/rust/evmc-declare/Cargo.toml b/bindings/rust/evmc-declare/Cargo.toml new file mode 100644 index 000000000..ec3a7d0da --- /dev/null +++ b/bindings/rust/evmc-declare/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "evmc-declare" +version = "0.1.0" +authors = ["Jake Lang "] +edition = "2018" + +[dependencies] +quote = "0.6.12" +heck = "0.3.1" +proc-macro2 = "0.4.29" + +[dependencies.syn] +version = "0.15.33" +features = ["full"] + +[lib] +proc-macro = true diff --git a/bindings/rust/evmc-declare/src/lib.rs b/bindings/rust/evmc-declare/src/lib.rs new file mode 100644 index 000000000..41d549967 --- /dev/null +++ b/bindings/rust/evmc-declare/src/lib.rs @@ -0,0 +1,310 @@ +#![recursion_limit = "128"] + +extern crate proc_macro; + +use heck::ShoutySnakeCase; +use heck::SnakeCase; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; +use syn::spanned::Spanned; +use syn::AttributeArgs; +use syn::Ident; +use syn::IntSuffix; +use syn::ItemStruct; +use syn::Lit; +use syn::LitInt; +use syn::LitStr; +use syn::NestedMeta; + +struct VMNameSet { + type_name: String, + name_allcaps: String, + name_lowercase: String, +} + +struct VMMetaData { + capabilities: u32, + // Not included in VMNameSet because it is parsed from the meta-item arguments. + name_stylized: String, +} + +#[allow(dead_code)] +impl VMNameSet { + fn new(ident: String) -> Self { + let caps = ident.to_shouty_snake_case(); + let lowercase = ident + .to_snake_case() + .chars() + .filter(|c| *c != '_') + .collect(); + VMNameSet { + type_name: ident, + name_allcaps: caps, + name_lowercase: lowercase, + } + } + + /// Return a reference to the struct name, as a string. + fn get_type_name(&self) -> &String { + &self.type_name + } + + /// Return a reference to the name in shouty snake case. + fn get_name_caps(&self) -> &String { + &self.name_allcaps + } + + /// Return a reference to the name in lowercase, with all underscores removed. (Used for + /// symbols like evmc_create_vmname) + fn get_name_lowercase(&self) -> &String { + &self.name_lowercase + } + + /// Get the struct's name as an explicit identifier to be interpolated with quote. + fn get_type_as_ident(&self) -> Ident { + Ident::new(&self.type_name, self.type_name.span()) + } + + /// Get the lowercase name appended with arbitrary text as an explicit ident. + fn get_lowercase_as_ident_append(&self, suffix: &str) -> Ident { + let concat = format!("{}{}", &self.name_lowercase, suffix); + Ident::new(&concat, self.name_lowercase.span()) + } + + /// Get the lowercase name prepended with arbitrary text as an explicit ident. + fn get_lowercase_as_ident_prepend(&self, prefix: &str) -> Ident { + let concat = format!("{}{}", prefix, &self.name_lowercase); + Ident::new(&concat, self.name_lowercase.span()) + } + + /// Get the lowercase name appended with arbitrary text as an explicit ident. + fn get_caps_as_ident_append(&self, suffix: &str) -> Ident { + let concat = format!("{}{}", &self.name_allcaps, suffix); + Ident::new(&concat, self.name_allcaps.span()) + } +} + +impl VMMetaData { + fn new(args: AttributeArgs) -> Self { + assert!(args.len() == 2, "Incorrect number of arguments supplied"); + + let vm_name_meta = &args[0]; + let vm_capabilities_meta = &args[1]; + + let vm_name_string = match vm_name_meta { + NestedMeta::Literal(lit) => { + if let Lit::Str(s) = lit { + s.value() + } else { + panic!("Literal argument type mismatch") + } + } + NestedMeta::Meta(_) => panic!("Argument 1 must be a string literal"), + }; + + let vm_capabilities_string = match vm_capabilities_meta { + NestedMeta::Literal(lit) => { + if let Lit::Str(s) = lit { + s.value() + } else { + panic!("Literal argument type mismatch") + } + } + NestedMeta::Meta(_) => panic!("Argument 2 must be a string literal"), + }; + + // Parse the individual capabilities out of the list and prepare a capabilities flagset. + let capabilities_list = vm_capabilities_string.split(", "); + let capabilities_flags = { + let mut ret: u32 = 0; + for capability in capabilities_list { + match capability { + "ewasm" => ret |= 0x1 << 1, + "evm" => ret |= 0x1, + _ => panic!("Invalid capability specified."), + } + } + ret + }; + + VMMetaData { + capabilities: capabilities_flags, + name_stylized: vm_name_string, + } + } + + fn get_capabilities(&self) -> u32 { + self.capabilities + } + + fn get_name_stylized(&self) -> &String { + &self.name_stylized + } +} + +#[proc_macro_attribute] +pub fn evmc_declare_vm(args: TokenStream, item: TokenStream) -> TokenStream { + // First, try to parse the input token stream into an AST node representing a struct + // declaration. + let input: ItemStruct = parse_macro_input!(item as ItemStruct); + + // Extract the identifier of the struct from the AST node. + let vm_type_name: String = input.ident.to_string(); + + // Build the VM name set. + let names = VMNameSet::new(vm_type_name); + + // Parse the arguments for the macro. + let meta_args = parse_macro_input!(args as AttributeArgs); + let vm_data = VMMetaData::new(meta_args); + + let vm_name_stylized = vm_data.get_name_stylized(); + let vm_capabilities = vm_data.get_capabilities(); + + // Get all the tokens from the respective helpers. + let static_data_tokens = build_static_data(&names, vm_name_stylized); + let capabilities_tokens = build_capabilities_fn(vm_capabilities); + let create_tokens = build_create_fn(&names); + let destroy_tokens = build_destroy_fn(&names); + let execute_tokens = build_execute_fn(&names); + + let quoted = quote! { + #input + #static_data_tokens + #capabilities_tokens + #create_tokens + #destroy_tokens + #execute_tokens + }; + + quoted.into() +} + +/// Generate tokens for the static data associated with an EVMC VM. +fn build_static_data(names: &VMNameSet, name_stylized: &String) -> proc_macro2::TokenStream { + // Stitch together the VM name and the suffix _NAME + let static_name_ident = names.get_caps_as_ident_append("_NAME"); + let static_version_ident = names.get_caps_as_ident_append("_VERSION"); + + // Turn the stylized VM name and version into string literals. + // FIXME: Not sure if the span of name.as_str() is the same as that of name. + let stylized_name_literal = LitStr::new(name_stylized.as_str(), name_stylized.as_str().span()); + + quote! { + static #static_name_ident: &'static str = #stylized_name_literal; + static #static_version_ident: &'static str = env!("CARGO_PKG_VERSION"); + } +} + +/// Takes a capabilities flag and builds the evmc_get_capabilities callback. +fn build_capabilities_fn(capabilities: u32) -> proc_macro2::TokenStream { + let capabilities_literal = + LitInt::new(capabilities as u64, IntSuffix::U32, capabilities.span()); + + quote! { + extern "C" fn __evmc_get_capabilities(instance: *mut ::evmc_sys::evmc_instance) -> ::evmc_sys::evmc_capabilities_flagset { + #capabilities_literal + } + } +} + +/// Takes an identifier and struct definition, builds an evmc_create_* function for FFI. +fn build_create_fn(names: &VMNameSet) -> proc_macro2::TokenStream { + let type_ident = names.get_type_as_ident(); + let fn_ident = names.get_lowercase_as_ident_prepend("evmc_create_"); + + let static_version_ident = names.get_caps_as_ident_append("_VERSION"); + let static_name_ident = names.get_caps_as_ident_append("_NAME"); + + quote! { + #[no_mangle] + extern "C" fn #fn_ident() -> *const ::evmc_sys::evmc_instance { + let new_instance = ::evmc_sys::evmc_instance { + abi_version: ::evmc_sys::EVMC_ABI_VERSION as i32, + destroy: Some(__evmc_destroy), + execute: Some(__evmc_execute), + get_capabilities: Some(__evmc_get_capabilities), + set_option: None, + set_tracer: None, + name: ::std::ffi::CString::new(#static_name_ident).expect("Failed to build VM name string").into_raw() as *const i8, + version: ::std::ffi::CString::new(#static_version_ident).expect("Failed to build VM version string").into_raw() as *const i8, + }; + + unsafe { + ::evmc_vm::EvmcContainer::into_ffi_pointer(Box::new(::evmc_vm::EvmcContainer::<#type_ident>::new(new_instance))) + } + } + } +} + +/// Builds a callback to dispose of the VM instance +fn build_destroy_fn(names: &VMNameSet) -> proc_macro2::TokenStream { + let type_ident = names.get_type_as_ident(); + + quote! { + extern "C" fn __evmc_destroy(instance: *mut ::evmc_sys::evmc_instance) { + unsafe { + ::evmc_vm::EvmcContainer::<#type_ident>::from_ffi_pointer(instance); + } + } + } +} + +fn build_execute_fn(names: &VMNameSet) -> proc_macro2::TokenStream { + let type_name_ident = names.get_type_as_ident(); + + quote! { + extern "C" fn __evmc_execute( + instance: *mut ::evmc_sys::evmc_instance, + context: *mut ::evmc_sys::evmc_context, + rev: ::evmc_sys::evmc_revision, + msg: *const ::evmc_sys::evmc_message, + code: *const u8, + code_size: usize + ) -> ::evmc_sys::evmc_result + { + assert!(!msg.is_null()); + assert!(!context.is_null()); + assert!(!instance.is_null()); + assert!(!code.is_null()); + + let execution_context = unsafe { + ::evmc_vm::ExecutionContext::new( + msg.as_ref().expect("EVMC message is null"), + context.as_mut().expect("EVMC context is null") + ) + }; + + let code_ref: &[u8] = unsafe { + ::std::slice::from_raw_parts(code, code_size) + }; + + let container = unsafe { + ::evmc_vm::EvmcContainer::<#type_name_ident>::from_ffi_pointer(instance) + }; + + let result = container.execute(code_ref, &execution_context); + + unsafe { + ::evmc_vm::EvmcContainer::into_ffi_pointer(container); + } + + result.into() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_camel_to_lower() { + let a = String::from("FooBarBaz"); + let b = a.to_snake_case(); + assert_eq!(b, "foo_bar_baz"); + let c: String = b.chars().filter(|c| *c != '_').collect(); + assert_eq!(c, String::from("foobarbaz")); + } +} diff --git a/bindings/rust/evmc-vm/Cargo.toml b/bindings/rust/evmc-vm/Cargo.toml index ea8dc3452..8f90138dc 100644 --- a/bindings/rust/evmc-vm/Cargo.toml +++ b/bindings/rust/evmc-vm/Cargo.toml @@ -13,3 +13,4 @@ edition = "2018" [dependencies] evmc-sys = { path = "../evmc-sys" } +evmc-declare = { path = "../evmc-declare" } diff --git a/bindings/rust/evmc-vm/src/container.rs b/bindings/rust/evmc-vm/src/container.rs new file mode 100644 index 000000000..3981f7e66 --- /dev/null +++ b/bindings/rust/evmc-vm/src/container.rs @@ -0,0 +1,35 @@ +use crate::EvmcVm; +use crate::ExecutionContext; +use crate::ExecutionResult; + +/// Container struct for EVM-C instances and user-defined data. +pub struct EvmcContainer { + instance: ::evmc_sys::evmc_instance, + vm: T, +} + +impl EvmcContainer { + /// Basic constructor. + pub fn new(_instance: ::evmc_sys::evmc_instance) -> Self { + Self { + instance: _instance, + vm: T::init(), + } + } + + /// Take ownership of the given pointer and return a box. + pub unsafe fn from_ffi_pointer(instance: *mut ::evmc_sys::evmc_instance) -> Box { + assert!(!instance.is_null(), "from_ffi_pointer received NULL"); + Box::from_raw(instance as *mut EvmcContainer) + } + + /// Convert boxed self into an FFI pointer, surrendering ownership of the heap data. + pub unsafe fn into_ffi_pointer(boxed: Box) -> *const ::evmc_sys::evmc_instance { + Box::into_raw(boxed) as *const ::evmc_sys::evmc_instance + } + + // TODO: Maybe this can just be done with the Deref trait. + pub fn execute(&self, code: &[u8], context: &ExecutionContext) -> ExecutionResult { + self.vm.execute(code, context) + } +} diff --git a/bindings/rust/evmc-vm/src/lib.rs b/bindings/rust/evmc-vm/src/lib.rs index cde142fc6..1e401e085 100644 --- a/bindings/rust/evmc-vm/src/lib.rs +++ b/bindings/rust/evmc-vm/src/lib.rs @@ -4,30 +4,35 @@ */ pub extern crate evmc_sys; -pub use evmc_sys as ffi; +mod container; -// TODO: Add convenient helpers for evmc_execute -// TODO: Add a derive macro here for creating evmc_create +pub use container::EvmcContainer; +pub use evmc_declare::evmc_declare_vm; + +pub trait EvmcVm { + fn init() -> Self; + fn execute(&self, code: &[u8], context: &ExecutionContext) -> ExecutionResult; +} /// EVMC result structure. pub struct ExecutionResult { - status_code: ffi::evmc_status_code, + status_code: evmc_sys::evmc_status_code, gas_left: i64, output: Option>, - create_address: Option, + create_address: Option, } /// EVMC context structure. Exposes the EVMC host functions, message data, and transaction context /// to the executing VM. pub struct ExecutionContext<'a> { - message: &'a ffi::evmc_message, - context: &'a mut ffi::evmc_context, - tx_context: ffi::evmc_tx_context, + message: &'a evmc_sys::evmc_message, + context: &'a mut evmc_sys::evmc_context, + tx_context: evmc_sys::evmc_tx_context, } impl ExecutionResult { pub fn new( - _status_code: ffi::evmc_status_code, + _status_code: evmc_sys::evmc_status_code, _gas_left: i64, _output: Option>, ) -> Self { @@ -39,7 +44,7 @@ impl ExecutionResult { } } - pub fn get_status_code(&self) -> ffi::evmc_status_code { + pub fn get_status_code(&self) -> evmc_sys::evmc_status_code { self.status_code } @@ -51,16 +56,19 @@ impl ExecutionResult { self.output.as_ref() } - pub fn get_create_address(&self) -> Option<&ffi::evmc_address> { + pub fn get_create_address(&self) -> Option<&evmc_sys::evmc_address> { self.create_address.as_ref() } } impl<'a> ExecutionContext<'a> { - pub fn new(_message: &'a ffi::evmc_message, _context: &'a mut ffi::evmc_context) -> Self { + pub fn new( + _message: &'a evmc_sys::evmc_message, + _context: &'a mut evmc_sys::evmc_context, + ) -> Self { let _tx_context = unsafe { assert!((*(_context.host)).get_tx_context.is_some()); - (*(_context.host)).get_tx_context.unwrap()(_context as *mut ffi::evmc_context) + (*(_context.host)).get_tx_context.unwrap()(_context as *mut evmc_sys::evmc_context) }; ExecutionContext { @@ -70,97 +78,97 @@ impl<'a> ExecutionContext<'a> { } } - pub fn get_message(&self) -> &ffi::evmc_message { + pub fn get_message(&self) -> &evmc_sys::evmc_message { &self.message } - pub fn get_tx_context(&mut self) -> &ffi::evmc_tx_context { + pub fn get_tx_context(&mut self) -> &evmc_sys::evmc_tx_context { &self.tx_context } - pub fn account_exists(&mut self, address: &ffi::evmc_address) -> bool { + pub fn account_exists(&mut self, address: &evmc_sys::evmc_address) -> bool { unsafe { assert!((*self.context.host).account_exists.is_some()); (*self.context.host).account_exists.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, ) } } pub fn get_storage( &mut self, - address: &ffi::evmc_address, - key: &ffi::evmc_bytes32, - ) -> ffi::evmc_bytes32 { + address: &evmc_sys::evmc_address, + key: &evmc_sys::evmc_bytes32, + ) -> evmc_sys::evmc_bytes32 { unsafe { assert!((*self.context.host).get_storage.is_some()); (*self.context.host).get_storage.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, - key as *const ffi::evmc_bytes32, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, + key as *const evmc_sys::evmc_bytes32, ) } } pub fn set_storage( &mut self, - address: &ffi::evmc_address, - key: &ffi::evmc_bytes32, - value: &ffi::evmc_bytes32, - ) -> ffi::evmc_storage_status { + address: &evmc_sys::evmc_address, + key: &evmc_sys::evmc_bytes32, + value: &evmc_sys::evmc_bytes32, + ) -> evmc_sys::evmc_storage_status { unsafe { assert!((*self.context.host).set_storage.is_some()); (*self.context.host).set_storage.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, - key as *const ffi::evmc_bytes32, - value as *const ffi::evmc_bytes32, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, + key as *const evmc_sys::evmc_bytes32, + value as *const evmc_sys::evmc_bytes32, ) } } - pub fn get_balance(&mut self, address: &ffi::evmc_address) -> ffi::evmc_bytes32 { + pub fn get_balance(&mut self, address: &evmc_sys::evmc_address) -> evmc_sys::evmc_bytes32 { unsafe { assert!((*self.context.host).get_balance.is_some()); (*self.context.host).get_balance.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, ) } } - pub fn get_code_size(&mut self, address: &ffi::evmc_address) -> usize { + pub fn get_code_size(&mut self, address: &evmc_sys::evmc_address) -> usize { unsafe { assert!((*self.context.host).get_code_size.is_some()); (*self.context.host).get_code_size.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, ) } } - pub fn get_code_hash(&mut self, address: &ffi::evmc_address) -> ffi::evmc_bytes32 { + pub fn get_code_hash(&mut self, address: &evmc_sys::evmc_address) -> evmc_sys::evmc_bytes32 { unsafe { assert!((*self.context.host).get_code_size.is_some()); (*self.context.host).get_code_hash.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, ) } } pub fn copy_code( &mut self, - address: &ffi::evmc_address, + address: &evmc_sys::evmc_address, code_offset: usize, buffer: &mut [u8], ) -> usize { unsafe { assert!((*self.context.host).copy_code.is_some()); (*self.context.host).copy_code.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, code_offset, // FIXME: ensure that alignment of the array elements is OK buffer.as_mut_ptr(), @@ -169,33 +177,37 @@ impl<'a> ExecutionContext<'a> { } } - pub fn selfdestruct(&mut self, address: &ffi::evmc_address, beneficiary: &ffi::evmc_address) { + pub fn selfdestruct( + &mut self, + address: &evmc_sys::evmc_address, + beneficiary: &evmc_sys::evmc_address, + ) { unsafe { assert!((*self.context.host).selfdestruct.is_some()); (*self.context.host).selfdestruct.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, - beneficiary as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, + beneficiary as *const evmc_sys::evmc_address, ) } } - pub fn call(&mut self, message: &ffi::evmc_message) -> ExecutionResult { + pub fn call(&mut self, message: &evmc_sys::evmc_message) -> ExecutionResult { unsafe { assert!((*self.context.host).call.is_some()); (*self.context.host).call.unwrap()( - self.context as *mut ffi::evmc_context, - message as *const ffi::evmc_message, + self.context as *mut evmc_sys::evmc_context, + message as *const evmc_sys::evmc_message, ) .into() } } - pub fn get_block_hash(&mut self, num: i64) -> ffi::evmc_bytes32 { + pub fn get_block_hash(&mut self, num: i64) -> evmc_sys::evmc_bytes32 { unsafe { assert!((*self.context.host).get_block_hash.is_some()); (*self.context.host).get_block_hash.unwrap()( - self.context as *mut ffi::evmc_context, + self.context as *mut evmc_sys::evmc_context, num, ) } @@ -203,15 +215,15 @@ impl<'a> ExecutionContext<'a> { pub fn emit_log( &mut self, - address: &ffi::evmc_address, + address: &evmc_sys::evmc_address, data: &[u8], - topics: &[ffi::evmc_bytes32], + topics: &[evmc_sys::evmc_bytes32], ) { unsafe { assert!((*self.context.host).emit_log.is_some()); (*self.context.host).emit_log.unwrap()( - self.context as *mut ffi::evmc_context, - address as *const ffi::evmc_address, + self.context as *mut evmc_sys::evmc_context, + address as *const evmc_sys::evmc_address, // FIXME: ensure that alignment of the array elements is OK data.as_ptr(), data.len(), @@ -222,8 +234,8 @@ impl<'a> ExecutionContext<'a> { } } -impl From for ExecutionResult { - fn from(result: ffi::evmc_result) -> Self { +impl From for ExecutionResult { + fn from(result: evmc_sys::evmc_result) -> Self { let ret = ExecutionResult { status_code: result.status_code, gas_left: result.gas_left, @@ -249,7 +261,7 @@ impl From for ExecutionResult { // Release allocated ffi struct. if result.release.is_some() { unsafe { - result.release.unwrap()(&result as *const ffi::evmc_result); + result.release.unwrap()(&result as *const evmc_sys::evmc_result); } } @@ -283,27 +295,27 @@ unsafe fn deallocate_output_data(ptr: *const u8, size: usize) { } /// Returns a pointer to a heap-allocated evmc_result. -impl Into<*const ffi::evmc_result> for ExecutionResult { - fn into(self) -> *const ffi::evmc_result { - let mut result: ffi::evmc_result = self.into(); +impl Into<*const evmc_sys::evmc_result> for ExecutionResult { + fn into(self) -> *const evmc_sys::evmc_result { + let mut result: evmc_sys::evmc_result = self.into(); result.release = Some(release_heap_result); Box::into_raw(Box::new(result)) } } /// Callback to pass across FFI, de-allocating the optional output_data. -extern "C" fn release_heap_result(result: *const ffi::evmc_result) { +extern "C" fn release_heap_result(result: *const evmc_sys::evmc_result) { unsafe { - let tmp = Box::from_raw(result as *mut ffi::evmc_result); + let tmp = Box::from_raw(result as *mut evmc_sys::evmc_result); deallocate_output_data(tmp.output_data, tmp.output_size); } } /// Returns a pointer to a stack-allocated evmc_result. -impl Into for ExecutionResult { - fn into(self) -> ffi::evmc_result { +impl Into for ExecutionResult { + fn into(self) -> evmc_sys::evmc_result { let (buffer, len) = allocate_output_data(self.output); - ffi::evmc_result { + evmc_sys::evmc_result { status_code: self.status_code, gas_left: self.gas_left, output_data: buffer, @@ -312,7 +324,7 @@ impl Into for ExecutionResult { create_address: if self.create_address.is_some() { self.create_address.unwrap() } else { - ffi::evmc_address { bytes: [0u8; 20] } + evmc_sys::evmc_address { bytes: [0u8; 20] } }, padding: [0u8; 4], } @@ -320,7 +332,7 @@ impl Into for ExecutionResult { } /// Callback to pass across FFI, de-allocating the optional output_data. -extern "C" fn release_stack_result(result: *const ffi::evmc_result) { +extern "C" fn release_stack_result(result: *const evmc_sys::evmc_result) { unsafe { let tmp = *result; deallocate_output_data(tmp.output_data, tmp.output_size); @@ -330,13 +342,12 @@ extern "C" fn release_stack_result(result: *const ffi::evmc_result) { #[cfg(test)] mod tests { use super::*; - use evmc_sys as ffi; #[test] fn new_result() { - let r = ExecutionResult::new(ffi::evmc_status_code::EVMC_FAILURE, 420, None); + let r = ExecutionResult::new(evmc_sys::evmc_status_code::EVMC_FAILURE, 420, None); - assert!(r.get_status_code() == ffi::evmc_status_code::EVMC_FAILURE); + assert!(r.get_status_code() == evmc_sys::evmc_status_code::EVMC_FAILURE); assert!(r.get_gas_left() == 420); assert!(r.get_output().is_none()); assert!(r.get_create_address().is_none()); @@ -344,19 +355,19 @@ mod tests { #[test] fn from_ffi() { - let f = ffi::evmc_result { - status_code: ffi::evmc_status_code::EVMC_SUCCESS, + let f = evmc_sys::evmc_result { + status_code: evmc_sys::evmc_status_code::EVMC_SUCCESS, gas_left: 1337, output_data: Box::into_raw(Box::new([0xde, 0xad, 0xbe, 0xef])) as *const u8, output_size: 4, release: None, - create_address: ffi::evmc_address { bytes: [0u8; 20] }, + create_address: evmc_sys::evmc_address { bytes: [0u8; 20] }, padding: [0u8; 4], }; let r: ExecutionResult = f.into(); - assert!(r.get_status_code() == ffi::evmc_status_code::EVMC_SUCCESS); + assert!(r.get_status_code() == evmc_sys::evmc_status_code::EVMC_SUCCESS); assert!(r.get_gas_left() == 1337); assert!(r.get_output().is_some()); assert!(r.get_output().unwrap().len() == 4); @@ -366,15 +377,15 @@ mod tests { #[test] fn into_heap_ffi() { let r = ExecutionResult::new( - ffi::evmc_status_code::EVMC_FAILURE, + evmc_sys::evmc_status_code::EVMC_FAILURE, 420, Some(vec![0xc0, 0xff, 0xee, 0x71, 0x75]), ); - let f: *const ffi::evmc_result = r.into(); + let f: *const evmc_sys::evmc_result = r.into(); assert!(!f.is_null()); unsafe { - assert!((*f).status_code == ffi::evmc_status_code::EVMC_FAILURE); + assert!((*f).status_code == evmc_sys::evmc_status_code::EVMC_FAILURE); assert!((*f).gas_left == 420); assert!(!(*f).output_data.is_null()); assert!((*f).output_size == 5); @@ -391,12 +402,12 @@ mod tests { #[test] fn into_heap_ffi_empty_data() { - let r = ExecutionResult::new(ffi::evmc_status_code::EVMC_FAILURE, 420, None); + let r = ExecutionResult::new(evmc_sys::evmc_status_code::EVMC_FAILURE, 420, None); - let f: *const ffi::evmc_result = r.into(); + let f: *const evmc_sys::evmc_result = r.into(); assert!(!f.is_null()); unsafe { - assert!((*f).status_code == ffi::evmc_status_code::EVMC_FAILURE); + assert!((*f).status_code == evmc_sys::evmc_status_code::EVMC_FAILURE); assert!((*f).gas_left == 420); assert!((*f).output_data.is_null()); assert!((*f).output_size == 0); @@ -410,14 +421,14 @@ mod tests { #[test] fn into_stack_ffi() { let r = ExecutionResult::new( - ffi::evmc_status_code::EVMC_FAILURE, + evmc_sys::evmc_status_code::EVMC_FAILURE, 420, Some(vec![0xc0, 0xff, 0xee, 0x71, 0x75]), ); - let f: ffi::evmc_result = r.into(); + let f: evmc_sys::evmc_result = r.into(); unsafe { - assert!(f.status_code == ffi::evmc_status_code::EVMC_FAILURE); + assert!(f.status_code == evmc_sys::evmc_status_code::EVMC_FAILURE); assert!(f.gas_left == 420); assert!(!f.output_data.is_null()); assert!(f.output_size == 5); @@ -434,11 +445,11 @@ mod tests { #[test] fn into_stack_ffi_empty_data() { - let r = ExecutionResult::new(ffi::evmc_status_code::EVMC_FAILURE, 420, None); + let r = ExecutionResult::new(evmc_sys::evmc_status_code::EVMC_FAILURE, 420, None); - let f: ffi::evmc_result = r.into(); + let f: evmc_sys::evmc_result = r.into(); unsafe { - assert!(f.status_code == ffi::evmc_status_code::EVMC_FAILURE); + assert!(f.status_code == evmc_sys::evmc_status_code::EVMC_FAILURE); assert!(f.gas_left == 420); assert!(f.output_data.is_null()); assert!(f.output_size == 0); @@ -450,30 +461,30 @@ mod tests { } unsafe extern "C" fn get_dummy_tx_context( - _context: *mut ffi::evmc_context, - ) -> ffi::evmc_tx_context { - ffi::evmc_tx_context { - tx_gas_price: ffi::evmc_uint256be { bytes: [0u8; 32] }, - tx_origin: ffi::evmc_address { bytes: [0u8; 20] }, - block_coinbase: ffi::evmc_address { bytes: [0u8; 20] }, + _context: *mut evmc_sys::evmc_context, + ) -> evmc_sys::evmc_tx_context { + evmc_sys::evmc_tx_context { + tx_gas_price: evmc_sys::evmc_uint256be { bytes: [0u8; 32] }, + tx_origin: evmc_sys::evmc_address { bytes: [0u8; 20] }, + block_coinbase: evmc_sys::evmc_address { bytes: [0u8; 20] }, block_number: 42, block_timestamp: 235117, block_gas_limit: 105023, - block_difficulty: ffi::evmc_uint256be { bytes: [0xaa; 32] }, + block_difficulty: evmc_sys::evmc_uint256be { bytes: [0xaa; 32] }, } } unsafe extern "C" fn get_dummy_code_size( - _context: *mut ffi::evmc_context, - _addr: *const ffi::evmc_address, + _context: *mut evmc_sys::evmc_context, + _addr: *const evmc_sys::evmc_address, ) -> usize { 105023 as usize } // Update these when needed for tests - fn get_dummy_context() -> ffi::evmc_context { - ffi::evmc_context { - host: Box::into_raw(Box::new(ffi::evmc_host_interface { + fn get_dummy_context() -> evmc_sys::evmc_context { + evmc_sys::evmc_context { + host: Box::into_raw(Box::new(evmc_sys::evmc_host_interface { account_exists: None, get_storage: None, set_storage: None, @@ -490,18 +501,18 @@ mod tests { } } - fn get_dummy_message() -> ffi::evmc_message { - ffi::evmc_message { - kind: ffi::evmc_call_kind::EVMC_CALL, + fn get_dummy_message() -> evmc_sys::evmc_message { + evmc_sys::evmc_message { + kind: evmc_sys::evmc_call_kind::EVMC_CALL, flags: 0, depth: 123, gas: 105023, - destination: ffi::evmc_address { bytes: [0u8; 20] }, - sender: ffi::evmc_address { bytes: [0u8; 20] }, + destination: evmc_sys::evmc_address { bytes: [0u8; 20] }, + sender: evmc_sys::evmc_address { bytes: [0u8; 20] }, input_data: std::ptr::null() as *const u8, input_size: 0, - value: ffi::evmc_uint256be { bytes: [0u8; 32] }, - create2_salt: ffi::evmc_uint256be { bytes: [0u8; 32] }, + value: evmc_sys::evmc_uint256be { bytes: [0u8; 32] }, + create2_salt: evmc_sys::evmc_uint256be { bytes: [0u8; 32] }, } } @@ -512,7 +523,9 @@ mod tests { let mut exe_context = ExecutionContext::new(&msg, &mut context_raw); let a = exe_context.get_tx_context(); - let b = unsafe { get_dummy_tx_context(&mut get_dummy_context() as *mut ffi::evmc_context) }; + let b = unsafe { + get_dummy_tx_context(&mut get_dummy_context() as *mut evmc_sys::evmc_context) + }; assert_eq!(a.block_gas_limit, b.block_gas_limit); assert_eq!(a.block_timestamp, b.block_timestamp); @@ -534,7 +547,7 @@ mod tests { let msg = get_dummy_message(); // This address is useless. Just a dummy parameter for the interface function. - let test_addr = ffi::evmc_address { bytes: [0u8; 20] }; + let test_addr = evmc_sys::evmc_address { bytes: [0u8; 20] }; let mut context_raw = get_dummy_context(); let mut exe_context = ExecutionContext::new(&msg, &mut context_raw); diff --git a/examples/example-rust-vm/Cargo.toml b/examples/example-rust-vm/Cargo.toml index 2c2c6adae..732154cb9 100644 --- a/examples/example-rust-vm/Cargo.toml +++ b/examples/example-rust-vm/Cargo.toml @@ -9,4 +9,5 @@ name = "examplerustvm" crate-type = ["staticlib", "dylib"] [dependencies] +evmc-sys = { path = "../../bindings/rust/evmc-sys" } evmc-vm = { path = "../../bindings/rust/evmc-vm" } diff --git a/examples/example-rust-vm/src/lib.rs b/examples/example-rust-vm/src/lib.rs index dc73e1b8f..c684e48be 100644 --- a/examples/example-rust-vm/src/lib.rs +++ b/examples/example-rust-vm/src/lib.rs @@ -2,62 +2,25 @@ extern crate evmc_vm; use evmc_vm::*; -extern "C" fn execute( - instance: *mut ffi::evmc_instance, - context: *mut ffi::evmc_context, - rev: ffi::evmc_revision, - msg: *const ffi::evmc_message, - code: *const u8, - code_size: usize, -) -> ffi::evmc_result { - let execution_ctx = unsafe { - ExecutionContext::new( - msg.as_ref().expect("tester passed nullptr as message"), - context.as_mut().expect("tester passed nullptr as context"), - ) - }; - let is_create = execution_ctx.get_message().kind == ffi::evmc_call_kind::EVMC_CREATE; +#[evmc_declare_vm("ExampleRustVM", "evm")] +pub struct ExampleRustVM; - if is_create { - evmc_vm::ExecutionResult::new(ffi::evmc_status_code::EVMC_FAILURE, 0, None).into() - } else { - evmc_vm::ExecutionResult::new( - ffi::evmc_status_code::EVMC_SUCCESS, - 66, - Some(vec![0xc0, 0xff, 0xee]), - ) - .into() +impl EvmcVm for ExampleRustVM { + fn init() -> Self { + ExampleRustVM {} } -} -extern "C" fn get_capabilities( - instance: *mut ffi::evmc_instance, -) -> ffi::evmc_capabilities_flagset { - ffi::evmc_capabilities::EVMC_CAPABILITY_EVM1 as u32 -} + fn execute(&self, code: &[u8], context: &ExecutionContext) -> ExecutionResult { + let is_create = context.get_message().kind == evmc_sys::evmc_call_kind::EVMC_CREATE; -extern "C" fn destroy(instance: *mut ffi::evmc_instance) { - drop(unsafe { Box::from_raw(instance) }) -} - -#[no_mangle] -pub extern "C" fn evmc_create_examplerustvm() -> *const ffi::evmc_instance { - let ret = ffi::evmc_instance { - abi_version: ffi::EVMC_ABI_VERSION as i32, - destroy: Some(destroy), - execute: Some(execute), - get_capabilities: Some(get_capabilities), - set_option: None, - set_tracer: None, - name: { - let c_str = - std::ffi::CString::new("ExampleRustVM").expect("Failed to build EVMC name string"); - c_str.into_raw() as *const i8 - }, - version: { - let c_str = std::ffi::CString::new("1.0").expect("Failed to build EVMC version string"); - c_str.into_raw() as *const i8 - }, - }; - Box::into_raw(Box::new(ret)) + if is_create { + evmc_vm::ExecutionResult::new(evmc_sys::evmc_status_code::EVMC_FAILURE, 0, None) + } else { + evmc_vm::ExecutionResult::new( + evmc_sys::evmc_status_code::EVMC_SUCCESS, + 66, + Some(vec![0xc0, 0xff, 0xee]), + ) + } + } }