diff --git a/codegen/src/asm.rs b/codegen/src/asm.rs index 088d8e6a9..73b713ca1 100644 --- a/codegen/src/asm.rs +++ b/codegen/src/asm.rs @@ -3,7 +3,7 @@ //! TODO: refactor this module with Result as outputs. (issue-21) use crate::{Buffer, Error, Result}; -use opcodes::{for_each_shanghai_operator, OpCode as _, ShangHai as OpCode}; +use opcodes::{for_each_cancun_operator, Cancun as OpCode, OpCode as _}; /// Low level assembler implementation for EVM. #[derive(Default, Clone, Debug)] @@ -141,5 +141,5 @@ macro_rules! impl_opcodes { /// Basic instruction implementations impl Assembler { - for_each_shanghai_operator!(impl_opcodes); + for_each_cancun_operator!(impl_opcodes); } diff --git a/codegen/src/masm/cmp.rs b/codegen/src/masm/cmp.rs index a2a148049..a2357c0fb 100644 --- a/codegen/src/masm/cmp.rs +++ b/codegen/src/masm/cmp.rs @@ -1,7 +1,7 @@ // Comparison Instructions use crate::{MacroAssembler, Result}; -use opcodes::ShangHai as OpCode; +use opcodes::Cancun as OpCode; impl MacroAssembler { /// Greater than or equal comparison. diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index ad2531bbe..c61c93df0 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -10,7 +10,7 @@ use crate::{ Error, Function, Result, }; use anyhow::anyhow; -use opcodes::ShangHai as OpCode; +use opcodes::Cancun as OpCode; impl Function { /// The call indirect instruction calls a function indirectly diff --git a/codegen/src/wasm/host.rs b/codegen/src/wasm/host.rs index c004d1a5b..ff7bcf9b1 100644 --- a/codegen/src/wasm/host.rs +++ b/codegen/src/wasm/host.rs @@ -3,7 +3,7 @@ use crate::{Error, Result}; use anyhow::anyhow; use core::str::FromStr; -use opcodes::{OpCode as _, ShangHai as OpCode}; +use opcodes::{Cancun as OpCode, OpCode as _}; /// EVM built-in function. #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] @@ -53,10 +53,10 @@ impl TryFrom<(&str, &str)> for HostFunc { ("asm", name) => { if name.starts_with("sload") { Ok(Self::Evm(OpCode::SLOAD)) + } else if name.starts_with("tload") { + Ok(Self::Evm(OpCode::TLOAD)) } 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 if name.starts_with("mulmod") { Ok(Self::Evm(OpCode::MULMOD)) @@ -66,10 +66,17 @@ impl TryFrom<(&str, &str)> for HostFunc { Ok(Self::NoOp) } } - ("evm", name) => Ok(Self::Evm(OpCode::from_str(name).map_err(|_| { - tracing::error!("Failed to load host function: {:?}", import); - Error::HostFuncNotFound(module.into(), name.into()) - })?)), + ("evm", name) => match name { + "tstore" => Ok(Self::Evm(OpCode::TSTORE)), + "tload" => Ok(Self::Evm(OpCode::TLOAD)), + "mcopy" => Ok(Self::Evm(OpCode::MCOPY)), + "blobhash" => Ok(Self::Evm(OpCode::BLOBHASH)), + "blobbasefee" => Ok(Self::Evm(OpCode::BLOBBASEFEE)), + _ => Ok(Self::Evm(OpCode::from_str(name).map_err(|_| { + tracing::error!("Failed to load host function: {:?}", import); + Error::HostFuncNotFound(module.into(), name.into()) + })?)), + }, ("zinkc", "emit_abi") => Ok(Self::EmitABI), ("zinkc", "address_eq") => Ok(Self::Evm(OpCode::EQ)), ("zinkc", "u256_add") => Ok(Self::Evm(OpCode::ADD)), diff --git a/evm/opcodes/src/cancun.rs b/evm/opcodes/src/cancun.rs new file mode 100644 index 000000000..43a078cf6 --- /dev/null +++ b/evm/opcodes/src/cancun.rs @@ -0,0 +1,156 @@ +//! Instructions for Cancun. + +use crate::{opcodes, Group, OpCode, Upgrade}; + +opcodes! { + Cancun, + (0x00, STOP, 0, 0, 0, "Halts execution.", Frontier, StopArithmetic), + (0x01, ADD, 3, 2, 1, "Addition operation.", Frontier, StopArithmetic), + (0x02, MUL, 5, 2, 1, "Multiplication operation.", Frontier, StopArithmetic), + (0x03, SUB, 3, 2, 1, "Subtraction operation.", Frontier, StopArithmetic), + (0x04, DIV, 5, 2, 1, "Integer division operation.", Frontier, StopArithmetic), + (0x05, SDIV, 5, 2, 1, "Signed integer division operation (truncated).", Frontier, StopArithmetic), + (0x06, MOD, 5, 2, 1, "Modulo remainder operation.", Frontier, StopArithmetic), + (0x07, SMOD, 5, 2, 1, "Signed modulo remainder operation.", Frontier, StopArithmetic), + (0x08, ADDMOD, 8, 3, 1, "Modulo addition operation.", Frontier, StopArithmetic), + (0x09, MULMOD, 8, 3, 1, "Modulo multiplication operation.", Frontier, StopArithmetic), + (0x0a, EXP, 10, 2, 1, "Exponential operation.", Frontier, StopArithmetic), + (0x0b, SIGNEXTEND, 5, 2, 1, "Extend length of two's complement signed integer.", Frontier, StopArithmetic), + (0x10, LT, 3, 2, 1, "Less-than comparison.", Frontier, ComparisonBitwiseLogic), + (0x11, GT, 3, 2, 1, "Greater-than comparison.", Frontier, ComparisonBitwiseLogic), + (0x12, SLT, 3, 2, 1, "Signed less-than comparison.", Frontier, ComparisonBitwiseLogic), + (0x13, SGT, 3, 2, 1, "Signed greater-than comparison.", Frontier, ComparisonBitwiseLogic), + (0x14, EQ, 3, 2, 1, "Equality comparison.", Frontier, ComparisonBitwiseLogic), + (0x15, ISZERO, 3, 1, 1, "Simple not operator.", Frontier, ComparisonBitwiseLogic), + (0x16, AND, 3, 2, 1, "Bitwise AND operation.", Frontier, ComparisonBitwiseLogic), + (0x17, OR, 3, 2, 1, "Bitwise OR operation.", Frontier, ComparisonBitwiseLogic), + (0x18, XOR, 3, 2, 1, "Bitwise XOR operation.", Frontier, ComparisonBitwiseLogic), + (0x19, NOT, 3, 1, 1, "Bitwise NOT operation.", Frontier, ComparisonBitwiseLogic), + (0x1a, BYTE, 3, 2, 1, "Retrieve single byte from word.", Frontier, ComparisonBitwiseLogic), + (0x1b, SHL, 3, 2, 1, "Left shift operation", Constantinople, ComparisonBitwiseLogic), + (0x1c, SHR, 3, 2, 1, "Logical right shift operation", Constantinople, ComparisonBitwiseLogic), + (0x1d, SAR, 3, 2, 1, "Arithmetic (signed) right shift operation", Constantinople, ComparisonBitwiseLogic), + (0x20, KECCAK256, 30, 2, 1, "Compute Keccak-256 hash.", Frontier, StopArithmetic), + (0x30, ADDRESS, 2, 0, 1, "Get address of currently executing account.", Frontier, EnvironmentalInformation), + (0x31, BALANCE, 20, 1, 1, "Get balance of the given account.", Frontier, EnvironmentalInformation), + (0x32, ORIGIN, 2, 0, 1, "Get execution origination address.", Frontier, EnvironmentalInformation), + (0x33, CALLER, 2, 0, 1, "Get caller address.", Frontier, EnvironmentalInformation), + (0x34, CALLVALUE, 2, 0, 1, "Get deposited value by the instruction/transaction responsible for this execution.", Frontier, EnvironmentalInformation), + (0x35, CALLDATALOAD, 3, 1, 1, "Get input data of current environment.", Frontier, EnvironmentalInformation), + (0x36, CALLDATASIZE, 2, 0, 1, "Get size of input data in current environment.", Frontier, EnvironmentalInformation), + (0x37, CALLDATACOPY, 3, 3, 0, "Copy input data in current environment to memory.", Frontier, EnvironmentalInformation), + (0x38, CODESIZE, 2, 0, 1, "Get size of code running in current environment.", Frontier, EnvironmentalInformation), + (0x39, CODECOPY, 3, 3, 0, "Copy code running in current environment to memory.", Frontier, EnvironmentalInformation), + (0x3a, GASPRICE, 2, 0, 1, "Get price of gas in current environment", Frontier, EnvironmentalInformation), + (0x3b, EXTCODESIZE, 20, 1, 1, "Get size of an account's code.", Frontier, EnvironmentalInformation), + (0x3c, EXTCODECOPY, 20, 4, 0, "Copy an account's code to memory.", Frontier, EnvironmentalInformation), + (0x3d, RETURNDATASIZE, 2, 0, 1, "Get size of output data from the previous call from the current environment.", Byzantium, EnvironmentalInformation), + (0x3e, RETURNDATACOPY, 3, 3, 0, "Copy output data from the previous call to memory.", Byzantium, EnvironmentalInformation), + (0x3f, EXTCODEHASH, 100, 1, 1, "Get hash of an account’s code.", Cancun, EnvironmentalInformation), + (0x40, BLOCKHASH, 20, 1, 1, "Get the hash of one of the 256 most recent complete blocks.", Cancun, EnvironmentalInformation), + (0x41, COINBASE, 2, 0, 1, "Get the block's beneficiary address.", Frontier, BlockInformation), + (0x42, TIMESTAMP, 2, 0, 1, "Get the block's timestamp.", Frontier, BlockInformation), + (0x43, NUMBER, 2, 0, 1, "Get the block's number.", Frontier, BlockInformation), + (0x44, DIFFICULTY, 2, 0, 1, "Get the block's difficulty.", Frontier, BlockInformation), + (0x45, GASLIMIT, 2, 0, 1, "Get the block's gas limit.", Frontier, BlockInformation), + (0x46, CHAINID, 2, 0, 1, "Get the chain ID.", Istanbul, BlockInformation), + (0x47, SELFBALANCE, 5, 0, 1, "Get balance of currently executing account.", Istanbul, BlockInformation), + (0x48, BASEFEE, 2, 0, 1, "Get the base fee.", London, BlockInformation), + (0x49, BLOBHASH, 3, 1, 1, "Get versioned hash at index.", Cancun, BlockInformation), + (0x4a, BLOBBASEFEE, 2, 0, 1, "Get the current blob base fee.", Cancun, BlockInformation), + (0x50, POP, 2, 1, 0, "Remove item from stack.", Frontier, StackMemoryStorageFlow), + (0x51, MLOAD, 3, 1, 1, "Load word from memory.", Frontier, StackMemoryStorageFlow), + (0x52, MSTORE, 3, 2, 0, "Save word to memory.", Frontier, StackMemoryStorageFlow), + (0x53, MSTORE8, 3, 2, 0, "Save byte to memory.", Frontier, StackMemoryStorageFlow), + (0x54, SLOAD, 50, 1, 1, "Load word from storage.", Frontier, StackMemoryStorageFlow), + (0x55, SSTORE, 0, 2, 0, "Save word to storage.", Frontier, StackMemoryStorageFlow), + (0x56, JUMP, 8, 1, 0, "Alter the program counter.", Frontier, StackMemoryStorageFlow), + (0x57, JUMPI, 10, 2, 0, "Conditionally alter the program counter.", Frontier, StackMemoryStorageFlow), + (0x58, PC, 2, 0, 1, "Get the value of the program counter prior to the increment.", Frontier, StackMemoryStorageFlow), + (0x59, MSIZE, 2, 0, 1, "Get the size of active memory in bytes.", Frontier, StackMemoryStorageFlow), + (0x5a, GAS, 2, 0, 1, "Get the amount of available gas.", Frontier, StackMemoryStorageFlow), + (0x5b, JUMPDEST, 1, 0, 0, "Mark a valid destination for jumps.", Frontier, StackMemoryStorageFlow), + (0x5c, TLOAD, 50, 1, 1, "Load word from transient storage", Cancun, StackMemoryStorageFlow), + (0x5d, TSTORE, 0, 2, 0, "Save word to transient storage.", Cancun, StackMemoryStorageFlow), + (0x5e, MCOPY, 3,3,0, "copy memory areas", Cancun,StackMemoryStorageFlow), + (0x5f, PUSH0, 2, 0, 1, "Place 0 byte item on stack.", Shanghai, Push), + (0x60, PUSH1, 3, 0, 1, "Place 1 byte item on stack.", Frontier, Push), + (0x61, PUSH2, 3, 0, 1, "Place 2-byte item on stack.", Frontier, Push), + (0x62, PUSH3, 3, 0, 1, "Place 3-byte item on stack.", Frontier, Push), + (0x63, PUSH4, 3, 0, 1, "Place 4-byte item on stack.", Frontier, Push), + (0x64, PUSH5, 3, 0, 1, "Place 5-byte item on stack.", Frontier, Push), + (0x65, PUSH6, 3, 0, 1, "Place 6-byte item on stack.", Frontier, Push), + (0x66, PUSH7, 3, 0, 1, "Place 7-byte item on stack.", Frontier, Push), + (0x67, PUSH8, 3, 0, 1, "Place 8-byte item on stack.", Frontier, Push), + (0x68, PUSH9, 3, 0, 1, "Place 9-byte item on stack.", Frontier, Push), + (0x69, PUSH10, 3, 0, 1, "Place 10-byte item on stack.", Frontier, Push), + (0x6a, PUSH11, 3, 0, 1, "Place 11-byte item on stack.", Frontier, Push), + (0x6b, PUSH12, 3, 0, 1, "Place 12-byte item on stack.", Frontier, Push), + (0x6c, PUSH13, 3, 0, 1, "Place 13-byte item on stack.", Frontier, Push), + (0x6d, PUSH14, 3, 0, 1, "Place 14-byte item on stack.", Frontier, Push), + (0x6e, PUSH15, 3, 0, 1, "Place 15-byte item on stack.", Frontier, Push), + (0x6f, PUSH16, 3, 0, 1, "Place 16-byte item on stack.", Frontier, Push), + (0x70, PUSH17, 3, 0, 1, "Place 17-byte item on stack.", Frontier, Push), + (0x71, PUSH18, 3, 0, 1, "Place 18-byte item on stack.", Frontier, Push), + (0x72, PUSH19, 3, 0, 1, "Place 19-byte item on stack.", Frontier, Push), + (0x73, PUSH20, 3, 0, 1, "Place 20-byte item on stack.", Frontier, Push), + (0x74, PUSH21, 3, 0, 1, "Place 21-byte item on stack.", Frontier, Push), + (0x75, PUSH22, 3, 0, 1, "Place 22-byte item on stack.", Frontier, Push), + (0x76, PUSH23, 3, 0, 1, "Place 23-byte item on stack.", Frontier, Push), + (0x77, PUSH24, 3, 0, 1, "Place 24-byte item on stack.", Frontier, Push), + (0x78, PUSH25, 3, 0, 1, "Place 25-byte item on stack.", Frontier, Push), + (0x79, PUSH26, 3, 0, 1, "Place 26-byte item on stack.", Frontier, Push), + (0x7a, PUSH27, 3, 0, 1, "Place 27-byte item on stack.", Frontier, Push), + (0x7b, PUSH28, 3, 0, 1, "Place 28-byte item on stack.", Frontier, Push), + (0x7c, PUSH29, 3, 0, 1, "Place 29-byte item on stack.", Frontier, Push), + (0x7d, PUSH30, 3, 0, 1, "Place 30-byte item on stack.", Frontier, Push), + (0x7e, PUSH31, 3, 0, 1, "Place 31-byte item on stack.", Frontier, Push), + (0x7f, PUSH32, 3, 0, 1, "Place 32-byte (full word) item on stack.", Frontier, Push), + (0x80, DUP1, 3, 1, 2, "Duplicate 1st stack item.", Frontier, Duplication), + (0x81, DUP2, 3, 2, 3, "Duplicate 2nd stack item.", Frontier, Duplication), + (0x82, DUP3, 3, 3, 4, "Duplicate 3rd stack item.", Frontier, Duplication), + (0x83, DUP4, 3, 4, 5, "Duplicate 4th stack item.", Frontier, Duplication), + (0x84, DUP5, 3, 5, 6, "Duplicate 5th stack item.", Frontier, Duplication), + (0x85, DUP6, 3, 6, 7, "Duplicate 6th stack item.", Frontier, Duplication), + (0x86, DUP7, 3, 7, 8, "Duplicate 7th stack item.", Frontier, Duplication), + (0x87, DUP8, 3, 8, 9, "Duplicate 8th stack item.", Frontier, Duplication), + (0x88, DUP9, 3, 9, 10, "Duplicate 9th stack item.", Frontier, Duplication), + (0x89, DUP10, 3, 10, 11, "Duplicate 10th stack item.", Frontier, Duplication), + (0x8a, DUP11, 3, 11, 12, "Duplicate 11th stack item.", Frontier, Duplication), + (0x8b, DUP12, 3, 12, 13, "Duplicate 12th stack item.", Frontier, Duplication), + (0x8c, DUP13, 3, 13, 14, "Duplicate 13th stack item.", Frontier, Duplication), + (0x8d, DUP14, 3, 14, 15, "Duplicate 14th stack item.", Frontier, Duplication), + (0x8e, DUP15, 3, 15, 16, "Duplicate 15th stack item.", Frontier, Duplication), + (0x8f, DUP16, 3, 16, 17, "Duplicate 16th stack item.", Frontier, Duplication), + (0x90, SWAP1, 3, 2, 2, "Exchange 1st and 2nd stack items.", Frontier, Exchange), + (0x91, SWAP2, 3, 3, 3, "Exchange 1st and 3rd stack items.", Frontier, Exchange), + (0x92, SWAP3, 3, 4, 4, "Exchange 1st and 4th stack items.", Frontier, Exchange), + (0x93, SWAP4, 3, 5, 5, "Exchange 1st and 5th stack items.", Frontier, Exchange), + (0x94, SWAP5, 3, 6, 6, "Exchange 1st and 6th stack items.", Frontier, Exchange), + (0x95, SWAP6, 3, 7, 7, "Exchange 1st and 7th stack items.", Frontier, Exchange), + (0x96, SWAP7, 3, 8, 8, "Exchange 1st and 8th stack items.", Frontier, Exchange), + (0x97, SWAP8, 3, 9, 9, "Exchange 1st and 9th stack items.", Frontier, Exchange), + (0x98, SWAP9, 3, 10, 10, "Exchange 1st and 10th stack items.", Frontier, Exchange), + (0x99, SWAP10, 3, 11, 11, "Exchange 1st and 11th stack items.", Frontier, Exchange), + (0x9a, SWAP11, 3, 12, 12, "Exchange 1st and 12th stack items.", Frontier, Exchange), + (0x9b, SWAP12, 3, 13, 13, "Exchange 1st and 13th stack items.", Frontier, Exchange), + (0x9c, SWAP13, 3, 14, 14, "Exchange 1st and 14th stack items.", Frontier, Exchange), + (0x9d, SWAP14, 3, 15, 15, "Exchange 1st and 15th stack items.", Frontier, Exchange), + (0x9e, SWAP15, 3, 16, 16, "Exchange 1st and 16th stack items.", Frontier, Exchange), + (0x9f, SWAP16, 3, 17, 17, "Exchange 1st and 17th stack items.", Frontier, Exchange), + (0xa0, LOG0, 375, 2, 0, "Append log record with no topics.", Frontier, Logging), + (0xa1, LOG1, 750, 3, 0, "Append log record with one topic.", Frontier, Logging), + (0xa2, LOG2, 1125, 4, 0, "Append log record with two topics.", Frontier, Logging), + (0xa3, LOG3, 1500, 5, 0, "Append log record with three topics.", Frontier, Logging), + (0xa4, LOG4, 1875, 6, 0, "Append log record with four topics.", Frontier, Logging), + (0xf0, CREATE, 32000, 3, 1, "Create a new account with associated code.", Frontier, System), + (0xf1, CALL, 100, 7, 1, "Message-call into an account.", Frontier, System), + (0xf2, CALLCODE, 100, 7, 1, "Message-call into this account with alternative account's code.", Frontier, System), + (0xf3, RETURN, 0, 2, 0, "Halt execution returning output data.", Frontier, System), + (0xf4, DELEGATECALL, 40, 6, 1, "Message-call with an alternative account's code, persisting the current context.", Frontier, System), + (0xf5, CREATE2, 32000, 4, 1, "Create a new account with associated code at a specified address.", Constantinople, System), + (0xfa, STATICCALL, 40, 6, 1, "Static message-call into an account.", Byzantium, System), + (0xfd, REVERT, 0, 2, 0, "Stop execution and revert state changes, without consuming all gas and providing a reason.", Byzantium, System), + (0xfe, INVALID, 0, 0, 0, "Designated invalid instruction.", Frontier, System), + (0xff, SELFDESTRUCT, 5000, 1, 0, "Halt execution and register account for later deletion.", Frontier, System) +} diff --git a/evm/opcodes/src/lib.rs b/evm/opcodes/src/lib.rs index d9f531a94..f8279d75f 100644 --- a/evm/opcodes/src/lib.rs +++ b/evm/opcodes/src/lib.rs @@ -1,8 +1,10 @@ //! Ethereum virtual machine opcode #![deny(missing_docs)] +mod cancun; mod shanghai; +pub use cancun::Cancun; pub use shanghai::ShangHai; /// Ethereum virtual machine opcode generator. @@ -185,6 +187,8 @@ pub enum Upgrade { London, /// Shanghai Shanghai, + /// Cancun + Cancun, } /// Ethereum virtual machine opcode. diff --git a/examples/transient_storage.rs b/examples/transient_storage.rs new file mode 100644 index 000000000..4b162f918 --- /dev/null +++ b/examples/transient_storage.rs @@ -0,0 +1,39 @@ +//! Transient Storage example. +#![cfg_attr(target_arch = "wasm32", no_std)] +#![cfg_attr(target_arch = "wasm32", no_main)] + +extern crate zink; + +use zink::storage::TransientStorage; +/// Temporary counter with value type `i32` that resets after each transaction +#[zink::transient_storage(i32)] +pub struct TempCounter; + +/// Set and get value via the transient storage. +#[zink::external] +pub fn set_and_get_temp(value: i32) -> i32 { + TempCounter::set(value); + TempCounter::get() +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() {} + +#[test] +fn transient_value() -> anyhow::Result<()> { + use zint::{Bytes32, Contract, U256}; + + let mut contract = Contract::search("transient_storage")?.compile()?; + let value: i32 = 42; + + { + let info = contract.execute(&[ + b"set_and_get_temp(int32)".to_vec(), + value.to_bytes32().to_vec(), + ])?; + assert!(!info.ret.is_empty()); + assert_eq!(info.ret.to_bytes32(), value.to_bytes32()); + } + + Ok(()) +} diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs index 3194ab4e9..776ddaa54 100644 --- a/zink/codegen/src/lib.rs +++ b/zink/codegen/src/lib.rs @@ -81,6 +81,24 @@ pub fn storage(attr: TokenStream, input: TokenStream) -> TokenStream { storage::Storage::parse(ty, input) } +/// Declare transient storage (cleared after each transaction) +/// +/// ```ignore +/// /// transient storage value +/// #[zink::transient_storage(i32)] +/// pub struct TempCounter; +/// +/// /// transient storage mapping +/// #[zink::transient_storage(i32, i32)] +/// pub struct TempMapping; +/// ``` +#[proc_macro_attribute] +pub fn transient_storage(attr: TokenStream, input: TokenStream) -> TokenStream { + let ty = storage::StorageType::from(attr); + let input = parse_macro_input!(input as ItemStruct); + storage::Storage::parse_transient(ty, input) +} + /// Mark the function as an external entry point. #[proc_macro_attribute] pub fn external(_args: TokenStream, input: TokenStream) -> TokenStream { diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs index 58b33a1ac..7f73ca863 100644 --- a/zink/codegen/src/storage.rs +++ b/zink/codegen/src/storage.rs @@ -12,10 +12,20 @@ use syn::{ thread_local! { static STORAGE_REGISTRY: RefCell> = RefCell::new(HashSet::new()); + static TRANSIENT_STORAGE_REGISTRY: RefCell> = RefCell::new(HashSet::new()); +} + +/// Storage type (persistent or transient) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StorageKind { + Persistent, + Transient, } /// Storage attributes parser pub struct Storage { + /// Storage kind (persistent or transient) + kind: StorageKind, /// kind of the storage ty: StorageType, /// The source and the target storage struct @@ -25,12 +35,48 @@ pub struct Storage { } impl Storage { - /// Parse from proc_macro attribute + /// Parse from proc_macro attribute for persistent storage pub fn parse(ty: StorageType, target: ItemStruct) -> TokenStream { - let storage = Self::from((ty, target)); + let storage = Self::from_parts(StorageKind::Persistent, ty, target); + storage.expand() + } + + /// Parse from proc_macro attribute for transient storage + pub fn parse_transient(ty: StorageType, target: ItemStruct) -> TokenStream { + let storage = Self::from_parts(StorageKind::Transient, ty, target); storage.expand() } + fn from_parts(kind: StorageKind, ty: StorageType, target: ItemStruct) -> Self { + let mut this = Self { + kind, + ty, + target, + getter: None, + }; + + let mut attrs: Vec = Default::default(); + for attr in this.target.attrs.iter().cloned() { + if !attr.path().is_ident("getter") { + attrs.push(attr); + continue; + } + + let Ok(list) = attr.meta.require_list().clone() else { + panic!("Invalid getter arguments"); + }; + + let Some(TokenTree::Ident(getter)) = list.tokens.clone().into_iter().nth(0) else { + panic!("Invalid getter function name"); + }; + + this.getter = Some(getter); + } + + this.target.attrs = attrs; + this + } + fn expand(mut self) -> TokenStream { match &self.ty { StorageType::Value(value) => self.expand_value(value.clone()), @@ -45,14 +91,19 @@ impl Storage { fn expand_value(&mut self, value: Ident) -> TokenStream { let is = &self.target; let name = self.target.ident.clone(); - let slot = storage_slot(name.to_string()); + let slot = self.get_storage_slot(name.to_string()); let key = slot.to_bytes32(); let keyl = Literal::byte_string(&key); + let trait_path = match self.kind { + StorageKind::Persistent => quote!(zink::storage::Storage), + StorageKind::Transient => quote!(zink::storage::TransientStorage), + }; + let mut expanded = quote! { #is - impl zink::storage::Storage for #name { + impl #trait_path for #name { #[cfg(not(target_family = "wasm"))] const STORAGE_KEY: [u8; 32] = *#keyl; const STORAGE_SLOT: i32 = #slot; @@ -62,7 +113,6 @@ impl Storage { }; if let Some(getter) = self.getter() { - // TODO: generate docs from the storage doc let gs: proc_macro2::TokenStream = parse_quote! { #[allow(missing_docs)] #[zink::external] @@ -79,12 +129,17 @@ impl Storage { fn expand_mapping(&mut self, key: Ident, value: Ident) -> TokenStream { let is = &self.target; let name = self.target.ident.clone(); - let slot = storage_slot(name.to_string()); + let slot = self.get_storage_slot(name.to_string()); + + let trait_path = match self.kind { + StorageKind::Persistent => quote!(zink::storage::Mapping), + StorageKind::Transient => quote!(zink::transient_storage::TransientMapping), + }; let mut expanded = quote! { #is - impl zink::storage::Mapping for #name { + impl #trait_path for #name { const STORAGE_SLOT: i32 = #slot; type Key = #key; @@ -103,7 +158,6 @@ impl Storage { }; if let Some(getter) = self.getter() { - // TODO: generate docs from the storage doc let gs: proc_macro2::TokenStream = parse_quote! { #[allow(missing_docs)] #[zink::external] @@ -120,12 +174,17 @@ impl Storage { fn expand_dk_mapping(&mut self, key1: Ident, key2: Ident, value: Ident) -> TokenStream { let is = &self.target; let name = self.target.ident.clone(); - let slot = storage_slot(name.to_string()); + let slot = self.get_storage_slot(name.to_string()); + + let trait_path = match self.kind { + StorageKind::Persistent => quote!(zink::storage::DoubleKeyMapping), + StorageKind::Transient => quote!(zink::transient_storage::DoubleKeyTransientMapping), + }; let mut expanded = quote! { #is - impl zink::DoubleKeyMapping for #name { + impl #trait_path for #name { const STORAGE_SLOT: i32 = #slot; type Key1 = #key1; @@ -148,7 +207,6 @@ impl Storage { }; if let Some(getter) = self.getter() { - // TODO: generate docs from the storage doc let gs: proc_macro2::TokenStream = parse_quote! { #[allow(missing_docs)] #[zink::external] @@ -162,6 +220,25 @@ impl Storage { expanded.into() } + fn get_storage_slot(&self, name: String) -> i32 { + match self.kind { + StorageKind::Persistent => STORAGE_REGISTRY.with_borrow_mut(|r| { + let key = r.len(); + if !r.insert(name.clone()) { + panic!("Storage {name} has already been declared"); + } + key + }) as i32, + StorageKind::Transient => TRANSIENT_STORAGE_REGISTRY.with_borrow_mut(|r| { + let key = r.len(); + if !r.insert(name.clone()) { + panic!("Transient storage {name} has already been declared"); + } + key + }) as i32, + } + } + /// Get the getter of this storage fn getter(&mut self) -> Option { let mut getter = if matches!(self.target.vis, Visibility::Public(_)) { @@ -178,37 +255,6 @@ impl Storage { } } -impl From<(StorageType, ItemStruct)> for Storage { - fn from(patts: (StorageType, ItemStruct)) -> Self { - let mut this = Self { - ty: patts.0, - target: patts.1, - getter: None, - }; - - let mut attrs: Vec = Default::default(); - for attr in this.target.attrs.iter().cloned() { - if !attr.path().is_ident("getter") { - attrs.push(attr); - continue; - } - - let Ok(list) = attr.meta.require_list().clone() else { - panic!("Invali getter arguments"); - }; - - let Some(TokenTree::Ident(getter)) = list.tokens.clone().into_iter().nth(0) else { - panic!("Invalid getter function name"); - }; - - this.getter = Some(getter); - } - - this.target.attrs = attrs; - this - } -} - /// Zink storage type parser #[derive(Default, Debug)] pub enum StorageType { @@ -246,14 +292,3 @@ impl From for StorageType { } } } - -fn storage_slot(name: String) -> i32 { - STORAGE_REGISTRY.with_borrow_mut(|r| { - let key = r.len(); - if !r.insert(name.clone()) { - panic!("Storage {name} has already been declared"); - } - - key - }) as i32 -} diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index cf1062888..d3c858993 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -116,4 +116,64 @@ extern "C" { /// Load address from storage pub fn sload_u256() -> U256; + + /// Load a 8-bit signed integer from the transient storage. + pub fn tload_i8() -> i8; + + /// Load a 8-bit unsigned integer from the transient storage. + pub fn tload_u8() -> u8; + + /// Load a 16-bit signed integer from the transient storage. + pub fn tload_i16() -> i16; + + /// Load a 16-bit unsigned integer from the transient storage. + pub fn tload_u16() -> u16; + + /// Load a 32-bit signed integer from the transient storage. + pub fn tload_i32() -> i32; + + /// Load a 32-bit unsigned integer from the transient storage. + pub fn tload_u32() -> u32; + + /// Load a 64-bit signed integer from the transient storage. + pub fn tload_i64() -> i64; + + /// Load a 64-bit unsigned integer from the transient storage. + pub fn tload_u64() -> u64; + + /// Load address from transient storage + pub fn tload_address() -> Address; + + /// Load U256 from transient storage + pub fn tload_u256() -> U256; + + /// Store a 8-bit signed integer to the transient storage. + pub fn tstore_i8(val: i8); + + /// Store a 8-bit unsigned integer to the transient storage. + pub fn tstore_u8(val: u8); + + /// Store a 16-bit signed integer to the transient storage. + pub fn tstore_i16(val: i16); + + /// Store a 16-bit unsigned integer to the transient storage. + pub fn tstore_u16(val: u16); + + /// Store a 32-bit signed integer to the transient storage. + pub fn tstore_i32(val: i32); + + /// Store a 32-bit unsigned integer to the transient storage. + pub fn tstore_u32(val: u32); + + /// Store a 64-bit signed integer to the transient storage. + pub fn tstore_i64(val: i64); + + /// Store a 64-bit unsigned integer to the transient storage. + pub fn tstore_u64(val: u64); + + /// Store address to transient storage + pub fn tstore_address(address: Address); + + /// Store U256 to transient storage + pub fn tstore_u256(u256: U256); } diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs index c832282b0..b543644b8 100644 --- a/zink/src/ffi/evm.rs +++ b/zink/src/ffi/evm.rs @@ -110,6 +110,12 @@ extern "C" { /// Load a value from the storage pub fn sload(); + /// Store a value in the transient storage + pub fn tstore(); + + /// Load a value from the transient storage + pub fn tload(); + /// Save word to memory pub fn mstore(); @@ -119,12 +125,21 @@ extern "C" { /// Load word from memory pub fn mload(); + /// Copy memory to memory + pub fn mcopy(); + /// Compute Keccak-256 hash pub fn keccak256(); /// Get the current message sender pub fn caller() -> Address; + /// Get the current blob hash at index + pub fn blobhash(); + + /// Get the current blob base fee + pub fn blobbasefee(); + /// Append log record with no topics pub fn log0(name: &'static [u8]); diff --git a/zink/src/lib.rs b/zink/src/lib.rs index 3b26061af..870538e3a 100644 --- a/zink/src/lib.rs +++ b/zink/src/lib.rs @@ -10,10 +10,9 @@ mod event; pub mod ffi; pub mod primitives; pub mod storage; - pub use self::{asm::Asm, event::Event}; -pub use storage::{DoubleKeyMapping, Mapping, Storage}; -pub use zink_codegen::{assert, external, revert, storage, Event}; +pub use storage::{DoubleKeyMapping, Mapping, Storage, TransientStorage}; +pub use zink_codegen::{assert, external, revert, storage, transient_storage, Event}; /// Generate a keccak hash of the input (sha3) #[cfg(not(target_family = "wasm"))] diff --git a/zink/src/primitives/address.rs b/zink/src/primitives/address.rs index acd527cbf..bc3a0fdf4 100644 --- a/zink/src/primitives/address.rs +++ b/zink/src/primitives/address.rs @@ -1,4 +1,8 @@ -use crate::{ffi, storage::StorageValue, Asm}; +use crate::{ + ffi, + storage::{StorageValue, TransientStorageValue}, + Asm, +}; /// Account address #[repr(C)] @@ -55,3 +59,9 @@ impl StorageValue for Address { unsafe { ffi::asm::sload_address() } } } + +impl TransientStorageValue for Address { + fn tload() -> Self { + unsafe { ffi::asm::tload_address() } + } +} diff --git a/zink/src/primitives/u256.rs b/zink/src/primitives/u256.rs index 7e2866cc5..eb435d98b 100644 --- a/zink/src/primitives/u256.rs +++ b/zink/src/primitives/u256.rs @@ -1,5 +1,9 @@ #![allow(clippy::should_implement_trait)] -use crate::{ffi, storage::StorageValue, Asm}; +use crate::{ + ffi, + storage::{StorageValue, TransientStorageValue}, + Asm, +}; /// Account address #[repr(C)] @@ -89,3 +93,10 @@ impl StorageValue for U256 { unsafe { ffi::asm::sload_u256() } } } + +impl TransientStorageValue for U256 { + #[inline(always)] + fn tload() -> Self { + unsafe { ffi::asm::tload_u256() } + } +} diff --git a/zink/src/storage/dkmapping.rs b/zink/src/storage/dkmapping.rs index d2c26c2c5..003f9a818 100644 --- a/zink/src/storage/dkmapping.rs +++ b/zink/src/storage/dkmapping.rs @@ -1,6 +1,10 @@ //! Double key mapping -use crate::{ffi, storage::StorageValue, Asm}; +use crate::{ + ffi, + storage::{StorageValue, TransientStorageValue}, + Asm, +}; /// Storage mapping interface pub trait DoubleKeyMapping { @@ -31,6 +35,35 @@ pub trait DoubleKeyMapping { } } +/// Transient storage mapping interface +pub trait DoubleKeyTransientMapping { + const STORAGE_SLOT: i32; + + type Key1: Asm; + type Key2: Asm; + type Value: TransientStorageValue; + + #[cfg(not(target_family = "wasm"))] + fn storage_key(key1: Self::Key1, key2: Self::Key2) -> [u8; 32]; + + /// Get value from transient storage key. + #[inline(always)] + fn get(key1: Self::Key1, key2: Self::Key2) -> Self::Value { + load_double_key(key1, key2, Self::STORAGE_SLOT); + Self::Value::tload() + } + + /// Set key and value in transient storage + #[inline(always)] + fn set(key1: Self::Key1, key2: Self::Key2, value: Self::Value) { + value.push(); + load_double_key(key1, key2, Self::STORAGE_SLOT); + unsafe { + ffi::evm::tstore(); + } + } +} + /// Load storage key to stack #[inline(always)] fn load_double_key(key1: impl Asm, key2: impl Asm, index: i32) { diff --git a/zink/src/storage/mapping.rs b/zink/src/storage/mapping.rs index 669c8f836..d38b1c201 100644 --- a/zink/src/storage/mapping.rs +++ b/zink/src/storage/mapping.rs @@ -1,6 +1,10 @@ //! Storage Mapping -use crate::{ffi, storage::StorageValue, Asm}; +use crate::{ + ffi, + storage::{StorageValue, TransientStorageValue}, + Asm, +}; /// Storage mapping interface pub trait Mapping { @@ -28,6 +32,32 @@ pub trait Mapping { } } +/// Transient storage mapping interface +pub trait TransientMapping { + const STORAGE_SLOT: i32; + + type Key: Asm; + type Value: TransientStorageValue; + + #[cfg(not(target_family = "wasm"))] + fn storage_key(key: Self::Key) -> [u8; 32]; + + /// Get value from transient storage key. + fn get(key: Self::Key) -> Self::Value { + load_key(key, Self::STORAGE_SLOT); + Self::Value::tload() + } + + /// Set key and value in transient storage + fn set(key: Self::Key, value: Self::Value) { + value.push(); + load_key(key, Self::STORAGE_SLOT); + unsafe { + ffi::evm::tstore(); + } + } +} + /// Load storage key to stack fn load_key(key: impl Asm, index: i32) { unsafe { diff --git a/zink/src/storage/mod.rs b/zink/src/storage/mod.rs index d7ecc018e..3d16da345 100644 --- a/zink/src/storage/mod.rs +++ b/zink/src/storage/mod.rs @@ -1,7 +1,11 @@ //! Zink storage implementation. use crate::{ffi, Asm}; -pub use {dkmapping::DoubleKeyMapping, mapping::Mapping, value::Storage}; +pub use { + dkmapping::{DoubleKeyMapping, DoubleKeyTransientMapping}, + mapping::{Mapping, TransientMapping}, + value::{Storage, TransientStorage}, +}; mod dkmapping; mod mapping; @@ -13,6 +17,12 @@ pub trait StorageValue: Asm { fn sload() -> Self; } +/// Interface for the value of kv based transient storage +pub trait TransientStorageValue: Asm { + /// Load from transient storage + fn tload() -> Self; +} + impl StorageValue for i32 { fn sload() -> Self { unsafe { ffi::asm::sload_i32() } @@ -24,3 +34,15 @@ impl StorageValue for u32 { unsafe { ffi::asm::sload_u32() } } } + +impl TransientStorageValue for i32 { + fn tload() -> Self { + unsafe { ffi::asm::tload_i32() } + } +} + +impl TransientStorageValue for u32 { + fn tload() -> Self { + unsafe { ffi::asm::tload_u32() } + } +} diff --git a/zink/src/storage/value.rs b/zink/src/storage/value.rs index d3c5caa3d..20d817fee 100644 --- a/zink/src/storage/value.rs +++ b/zink/src/storage/value.rs @@ -1,5 +1,9 @@ //! Key-Value storage -use crate::{ffi, storage::StorageValue, Asm}; +use crate::{ + ffi, + storage::{StorageValue, TransientStorageValue}, + Asm, +}; /// Storage trait. Currently not for public use pub trait Storage { @@ -24,3 +28,27 @@ pub trait Storage { } } } + +/// Transient storage trait. Currently not for public use +pub trait TransientStorage { + #[cfg(not(target_family = "wasm"))] + const STORAGE_KEY: [u8; 32]; + const STORAGE_SLOT: i32; + + type Value: TransientStorageValue + Asm; + + /// Get value from transient storage. + fn get() -> Self::Value { + Asm::push(Self::STORAGE_SLOT); + Self::Value::tload() + } + + /// Set value to transient storage. + fn set(value: Self::Value) { + value.push(); + Asm::push(Self::STORAGE_SLOT); + unsafe { + ffi::evm::tstore(); + } + } +}