Skip to content

Commit

Permalink
feat(zink): introduce proc-macro revert (#267)
Browse files Browse the repository at this point in the history
* feat(zink): introduce macro revert

* feat(zink): pre-calculate length of revert messages

* feat(codegen): parse revert from host functions

* feat(zink): example of revert
  • Loading branch information
clearloop authored Nov 4, 2024
1 parent 9358de8 commit 3191069
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 9 deletions.
2 changes: 1 addition & 1 deletion codegen/src/visitor/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ 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),
HostFunc::Evm(OpCode::LOG2) => self.log(2),
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:?}");
Expand Down
43 changes: 40 additions & 3 deletions codegen/src/visitor/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = self.masm.buffer().into();

// Pop offset and size from the bytecode.
Expand Down Expand Up @@ -53,7 +59,7 @@ impl Function {
pub fn log(&mut self, count: usize) -> Result<()> {
let mut topics = Vec::<Vec<u8>>::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)?;

Expand All @@ -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)?;

Expand Down Expand Up @@ -93,4 +99,35 @@ impl Function {

Ok(())
}

/// Revert with message.
pub fn revert(&mut self, count: usize) -> Result<()> {
let mut message = Vec::<Vec<u8>>::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::<Vec<u8>>())
);

self.masm.push(&(count * 32).to_ls_bytes())?;
self.masm._push0()?;

// 3. run log for the data
self.masm._revert()?;
Ok(())
}
}
8 changes: 8 additions & 0 deletions codegen/src/wasm/host.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Host functions
use crate::{Error, Result};
use anyhow::anyhow;
use core::str::FromStr;
use opcodes::{OpCode as _, ShangHai as OpCode};

Expand All @@ -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),
}
Expand Down Expand Up @@ -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)
}
Expand Down
24 changes: 24 additions & 0 deletions examples/revert.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
14 changes: 13 additions & 1 deletion zink/codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
//! 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};
use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct, LitStr};

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 {
let input = parse_macro_input!(input as LitStr);
revert::parse(input)
}

/// Event logging interface
///
/// ```ignore
Expand Down
36 changes: 36 additions & 0 deletions zink/codegen/src/revert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Revert macro
use proc_macro::TokenStream;
use proc_macro2::{Literal, Span};
use quote::{quote, ToTokens};
use syn::{Ident, LitStr};

/// Revert with message
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.");
}

// TODO: handle the string correctly
let lit = Literal::string(&message.replace("\"", ""));
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! {
unsafe { zink::ffi::asm::#rev(#lit) }
}
.into()
}
2 changes: 0 additions & 2 deletions zink/codegen/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
extern crate proc_macro;

use heck::AsSnakeCase;
use proc_macro::TokenStream;
use proc_macro2::{Literal, Span, TokenTree};
Expand Down
12 changes: 12 additions & 0 deletions zink/src/ffi/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ extern "C" {
/// Push address to stack
pub fn push_address(address: Address);

/// Revert with message in 32 bytes
pub fn revert1(message: &'static str);

/// Revert with message in 64 bytes
pub fn revert2(message: &'static str);

/// Revert with message in 96 bytes
pub fn revert3(message: &'static str);

/// Revert with message in 128 bytes
pub fn revert4(message: &'static str);

/// Load a 8-bit signed integer from the storage.
pub fn sload_i8() -> i8;

Expand Down
2 changes: 1 addition & 1 deletion zink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down
11 changes: 10 additions & 1 deletion zint/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub struct Info {
pub logs: Vec<Log>,
/// Transaction halt reason.
pub halt: Option<HaltReason>,
/// The revert message.
pub revert: Option<String>,
}

impl TryFrom<ExecutionResult> for Info {
Expand Down Expand Up @@ -166,7 +168,14 @@ impl TryFrom<ExecutionResult> 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)
Expand Down

0 comments on commit 3191069

Please sign in to comment.