From 1362b1df41c5c84ab2a7ef0c5dd8497fd49a9990 Mon Sep 17 00:00:00 2001 From: clearloop Date: Mon, 4 Nov 2024 19:41:56 +0800 Subject: [PATCH 1/4] feat(zink): introduce macro revert --- codegen/src/visitor/call.rs | 1 - codegen/src/visitor/log.rs | 12 +++++++++--- zink/codegen/src/lib.rs | 11 +++++++++++ zink/codegen/src/revert.rs | 17 +++++++++++++++++ zink/src/ffi/asm.rs | 3 +++ 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 zink/codegen/src/revert.rs diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index 115c86080..540ea897c 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -104,7 +104,6 @@ impl Function { .ok_or(Error::ImportedFuncNotFound(index))?; tracing::trace!("call imported function, index={index}, func={func:?}"); - match func { HostFunc::Evm(OpCode::LOG0) => self.log(0), HostFunc::Evm(OpCode::LOG1) => self.log(1), diff --git a/codegen/src/visitor/log.rs b/codegen/src/visitor/log.rs index d0268db00..33ea5bd61 100644 --- a/codegen/src/visitor/log.rs +++ b/codegen/src/visitor/log.rs @@ -4,7 +4,13 @@ use crate::{masm::MemoryInfo, wasm::ToLSBytes, Error, Function, Result}; impl Function { /// Parse log data from the bytecode. - fn log_data(&mut self) -> Result<(i32, i32)> { + /// + /// WASM example: + /// ``` + /// i32.const 1048576 ;; offset + /// i32.const 4 ;; 4 bytes + /// ``` + fn data(&mut self) -> Result<(i32, i32)> { let buffer: Vec = self.masm.buffer().into(); // Pop offset and size from the bytecode. @@ -53,7 +59,7 @@ impl Function { pub fn log(&mut self, count: usize) -> Result<()> { let mut topics = Vec::>::default(); for topic in (1..=count).rev() { - let (offset, size) = self.log_data()?; + let (offset, size) = self.data()?; let size = size as usize; let data = self.env.data.load(offset, size)?; @@ -62,7 +68,7 @@ impl Function { } let name = { - let (offset, size) = self.log_data()?; + let (offset, size) = self.data()?; let size = size as usize; let data = self.env.data.load(offset, size)?; diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs index b2edf79c1..5ca1527d1 100644 --- a/zink/codegen/src/lib.rs +++ b/zink/codegen/src/lib.rs @@ -1,13 +1,24 @@ //! Code generation library for the zink API #![allow(unused)] +extern crate proc_macro; + use proc_macro::TokenStream; use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct}; mod event; +mod revert; mod selector; mod storage; +/// Revert with the input message +/// +/// Only raw string is supported, formatter currently doesn't work. +#[proc_macro] +pub fn revert(input: TokenStream) -> TokenStream { + revert::parse(input) +} + /// Event logging interface /// /// ```ignore diff --git a/zink/codegen/src/revert.rs b/zink/codegen/src/revert.rs new file mode 100644 index 000000000..e5b05390d --- /dev/null +++ b/zink/codegen/src/revert.rs @@ -0,0 +1,17 @@ +//! Revert macro + +use proc_macro::TokenStream; +use proc_macro2::Literal; +use quote::quote; + +/// Revert with message +pub fn parse(input: TokenStream) -> TokenStream { + let message = input.to_string(); + let len = message.len() as i32; + let lit = Literal::string(&message); + + quote! { + zink::ffi::asm::revert(len, #lit) + } + .into() +} diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index 77d88ec08..fc6c08a11 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -32,6 +32,9 @@ extern "C" { /// Push address to stack pub fn push_address(address: Address); + /// Revert with message + pub fn revert(len: i32, message: &'static [u8]); + /// Load a 8-bit signed integer from the storage. pub fn sload_i8() -> i8; From 65e684a27d9b9a66436772b82a83920e8d8f326c Mon Sep 17 00:00:00 2001 From: clearloop Date: Mon, 4 Nov 2024 20:49:23 +0800 Subject: [PATCH 2/4] feat(zink): pre-calculate length of revert messages --- zink/codegen/src/revert.rs | 22 ++++++++++++++++++++-- zink/codegen/src/storage.rs | 2 -- zink/src/ffi/evm.rs | 12 ++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/zink/codegen/src/revert.rs b/zink/codegen/src/revert.rs index e5b05390d..7cbc82591 100644 --- a/zink/codegen/src/revert.rs +++ b/zink/codegen/src/revert.rs @@ -1,17 +1,35 @@ //! Revert macro use proc_macro::TokenStream; -use proc_macro2::Literal; +use proc_macro2::{Literal, Span}; use quote::quote; +use syn::Ident; /// Revert with message pub fn parse(input: TokenStream) -> TokenStream { let message = input.to_string(); let len = message.len() as i32; + if len > 128 { + panic!("Only support revert message less than 128 bytes atm."); + } + let lit = Literal::string(&message); + let rev = Ident::new( + &format!( + "revert{}", + match len { + len if len > 96 => 4, + len if len > 64 => 3, + len if len > 32 => 2, + len if len > 0 => 1, + _ => panic!("Only support revert message less than 128 bytes atm."), + }, + ), + Span::call_site(), + ); quote! { - zink::ffi::asm::revert(len, #lit) + zink::ffi::asm::#rev(#lit) } .into() } diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs index dad1b3c24..aa2723612 100644 --- a/zink/codegen/src/storage.rs +++ b/zink/codegen/src/storage.rs @@ -1,5 +1,3 @@ -extern crate proc_macro; - use heck::AsSnakeCase; use proc_macro::TokenStream; use proc_macro2::{Literal, Span, TokenTree}; diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs index 398ec523c..ee7eaf1af 100644 --- a/zink/src/ffi/evm.rs +++ b/zink/src/ffi/evm.rs @@ -120,6 +120,18 @@ extern "C" { /// Compute Keccak-256 hash pub fn keccak256(); + /// Revert with message in 32 bytes + pub fn revert1(message: &'static [u8]); + + /// Revert with message in 64 bytes + pub fn revert2(message: &'static [u8]); + + /// Revert with message in 96 bytes + pub fn revert3(message: &'static [u8]); + + /// Revert with message in 128 bytes + pub fn revert4(message: &'static [u8]); + /// Append log record with no topics pub fn log0(name: &'static [u8]); From b635d97462b9b24c29a724631d9c3729d8829915 Mon Sep 17 00:00:00 2001 From: clearloop Date: Mon, 4 Nov 2024 22:37:25 +0800 Subject: [PATCH 3/4] feat(codegen): parse revert from host functions --- codegen/src/wasm/host.rs | 8 ++++++++ zink/src/ffi/asm.rs | 13 +++++++++++-- zink/src/ffi/evm.rs | 12 ------------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/codegen/src/wasm/host.rs b/codegen/src/wasm/host.rs index 22301c9c8..9e9ec5dab 100644 --- a/codegen/src/wasm/host.rs +++ b/codegen/src/wasm/host.rs @@ -1,6 +1,7 @@ //! Host functions use crate::{Error, Result}; +use anyhow::anyhow; use core::str::FromStr; use opcodes::{OpCode as _, ShangHai as OpCode}; @@ -17,6 +18,8 @@ pub enum HostFunc { EmitABI, /// check equal of two addresses AddressEq, + /// Revert messages with length of slots + Revert(usize), /// Compiler labels Label(CompilerLabel), } @@ -48,6 +51,11 @@ impl TryFrom<(&str, &str)> for HostFunc { ("asm", name) => { if name.starts_with("sload") { Ok(Self::Evm(OpCode::SLOAD)) + } else if name.starts_with("revert") { + let count = name.trim_start_matches("revert"); + + // TODO: use anyhow instead of Error + Ok(Self::Revert(count.parse().map_err(|e| anyhow!("{e}"))?)) } else { Ok(Self::NoOp) } diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index fc6c08a11..c606339d7 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -32,8 +32,17 @@ extern "C" { /// Push address to stack pub fn push_address(address: Address); - /// Revert with message - pub fn revert(len: i32, message: &'static [u8]); + /// Revert with message in 32 bytes + pub fn revert1(message: &'static [u8]); + + /// Revert with message in 64 bytes + pub fn revert2(message: &'static [u8]); + + /// Revert with message in 96 bytes + pub fn revert3(message: &'static [u8]); + + /// Revert with message in 128 bytes + pub fn revert4(message: &'static [u8]); /// Load a 8-bit signed integer from the storage. pub fn sload_i8() -> i8; diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs index ee7eaf1af..398ec523c 100644 --- a/zink/src/ffi/evm.rs +++ b/zink/src/ffi/evm.rs @@ -120,18 +120,6 @@ extern "C" { /// Compute Keccak-256 hash pub fn keccak256(); - /// Revert with message in 32 bytes - pub fn revert1(message: &'static [u8]); - - /// Revert with message in 64 bytes - pub fn revert2(message: &'static [u8]); - - /// Revert with message in 96 bytes - pub fn revert3(message: &'static [u8]); - - /// Revert with message in 128 bytes - pub fn revert4(message: &'static [u8]); - /// Append log record with no topics pub fn log0(name: &'static [u8]); From c6d7aea2bc5577c5f4898887df394a873118bfb9 Mon Sep 17 00:00:00 2001 From: clearloop Date: Mon, 4 Nov 2024 23:56:11 +0800 Subject: [PATCH 4/4] feat(zink): example of revert --- codegen/src/visitor/call.rs | 1 + codegen/src/visitor/log.rs | 31 +++++++++++++++++++++++++++++++ examples/revert.rs | 24 ++++++++++++++++++++++++ zink/codegen/src/lib.rs | 3 ++- zink/codegen/src/revert.rs | 13 +++++++------ zink/src/ffi/asm.rs | 8 ++++---- zink/src/lib.rs | 2 +- zint/src/evm.rs | 11 ++++++++++- 8 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 examples/revert.rs diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index 540ea897c..72e94f9e2 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -111,6 +111,7 @@ impl Function { HostFunc::Evm(OpCode::LOG3) => self.log(3), HostFunc::Evm(OpCode::LOG4) => self.log(4), HostFunc::Evm(op) => self.masm.emit_op(op), + HostFunc::Revert(count) => self.revert(count), HostFunc::NoOp | HostFunc::Label(_) => Ok(()), _ => { tracing::error!("unsupported host function {func:?}"); diff --git a/codegen/src/visitor/log.rs b/codegen/src/visitor/log.rs index 33ea5bd61..801c95bd1 100644 --- a/codegen/src/visitor/log.rs +++ b/codegen/src/visitor/log.rs @@ -99,4 +99,35 @@ impl Function { Ok(()) } + + /// Revert with message. + pub fn revert(&mut self, count: usize) -> Result<()> { + let mut message = Vec::>::default(); + for slot in 0..count { + let (offset, size) = self.data()?; + let size = size as usize; + let data = self.env.data.load(offset, size)?; + + self.masm.push(&data)?; + if slot == 0 { + self.masm._push0()?; + } else { + self.masm.push(&slot.to_ls_bytes())?; + } + self.masm._mstore()?; + message.push(data); + } + + tracing::debug!( + "revert message: {}", + String::from_utf8_lossy(&message.into_iter().flatten().collect::>()) + ); + + self.masm.push(&(count * 32).to_ls_bytes())?; + self.masm._push0()?; + + // 3. run log for the data + self.masm._revert()?; + Ok(()) + } } diff --git a/examples/revert.rs b/examples/revert.rs new file mode 100644 index 000000000..d0ae2d7cf --- /dev/null +++ b/examples/revert.rs @@ -0,0 +1,24 @@ +//! Example of revert +#![cfg_attr(target_arch = "wasm32", no_std)] +#![cfg_attr(target_arch = "wasm32", no_main)] + +extern crate zink; + +#[cfg(not(target_arch = "wasm32"))] +fn main() {} + +/// check if the passing address is owner +#[zink::external] +pub fn run_revert() { + zink::revert!("revert works") +} + +#[test] +fn test_revert() -> anyhow::Result<()> { + use zint::Contract; + let mut contract = Contract::search("revert")?.compile()?; + + let info = contract.execute(["revert()".as_bytes()])?; + assert_eq!(info.revert, Some("revert works".into())); + Ok(()) +} diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs index 5ca1527d1..9974113bf 100644 --- a/zink/codegen/src/lib.rs +++ b/zink/codegen/src/lib.rs @@ -4,7 +4,7 @@ extern crate proc_macro; use proc_macro::TokenStream; -use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct}; +use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct, LitStr}; mod event; mod revert; @@ -16,6 +16,7 @@ mod storage; /// Only raw string is supported, formatter currently doesn't work. #[proc_macro] pub fn revert(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LitStr); revert::parse(input) } diff --git a/zink/codegen/src/revert.rs b/zink/codegen/src/revert.rs index 7cbc82591..e03076cdb 100644 --- a/zink/codegen/src/revert.rs +++ b/zink/codegen/src/revert.rs @@ -2,18 +2,19 @@ use proc_macro::TokenStream; use proc_macro2::{Literal, Span}; -use quote::quote; -use syn::Ident; +use quote::{quote, ToTokens}; +use syn::{Ident, LitStr}; /// Revert with message -pub fn parse(input: TokenStream) -> TokenStream { - let message = input.to_string(); +pub fn parse(input: LitStr) -> TokenStream { + let message = input.value(); let len = message.len() as i32; if len > 128 { panic!("Only support revert message less than 128 bytes atm."); } - let lit = Literal::string(&message); + // TODO: handle the string correctly + let lit = Literal::string(&message.replace("\"", "")); let rev = Ident::new( &format!( "revert{}", @@ -29,7 +30,7 @@ pub fn parse(input: TokenStream) -> TokenStream { ); quote! { - zink::ffi::asm::#rev(#lit) + unsafe { zink::ffi::asm::#rev(#lit) } } .into() } diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index c606339d7..fc8930e84 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -33,16 +33,16 @@ extern "C" { pub fn push_address(address: Address); /// Revert with message in 32 bytes - pub fn revert1(message: &'static [u8]); + pub fn revert1(message: &'static str); /// Revert with message in 64 bytes - pub fn revert2(message: &'static [u8]); + pub fn revert2(message: &'static str); /// Revert with message in 96 bytes - pub fn revert3(message: &'static [u8]); + pub fn revert3(message: &'static str); /// Revert with message in 128 bytes - pub fn revert4(message: &'static [u8]); + pub fn revert4(message: &'static str); /// Load a 8-bit signed integer from the storage. pub fn sload_i8() -> i8; diff --git a/zink/src/lib.rs b/zink/src/lib.rs index 8fa280035..3aeee8205 100644 --- a/zink/src/lib.rs +++ b/zink/src/lib.rs @@ -13,7 +13,7 @@ pub mod storage; pub use self::{asm::Asm, event::Event}; pub use storage::{DoubleKeyMapping, Mapping, Storage}; -pub use zink_codegen::{external, storage, Event}; +pub use zink_codegen::{external, revert, storage, Event}; /// Generate a keccak hash of the input (sha3) #[cfg(not(target_family = "wasm"))] diff --git a/zint/src/evm.rs b/zint/src/evm.rs index 9d82c6f47..d0d034ef1 100644 --- a/zint/src/evm.rs +++ b/zint/src/evm.rs @@ -124,6 +124,8 @@ pub struct Info { pub logs: Vec, /// Transaction halt reason. pub halt: Option, + /// The revert message. + pub revert: Option, } impl TryFrom for Info { @@ -166,7 +168,14 @@ impl TryFrom for Info { ExecutionResult::Halt { reason, .. } => { info.halt = Some(reason); } - _ => unreachable!("This should never happen"), + ExecutionResult::Revert { gas_used, output } => { + info.gas = gas_used; + info.revert = Some( + String::from_utf8_lossy(&output) + .trim_start_matches("\0") + .to_string(), + ); + } } Ok(info)