Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(zink): introduce proc-macro revert #267

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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