diff --git a/Cargo.toml b/Cargo.toml index 88bef034e..3415610a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,3 +110,7 @@ opcodes = { workspace = true, features = ["data"] } tracing.workspace = true zint.workspace = true hex.workspace = true + +# [features] +# default = [ "evm" ] +# evm = [ ] diff --git a/examples/erc20.rs b/examples/erc20.rs index ff81a685c..35e6cb7e4 100644 --- a/examples/erc20.rs +++ b/examples/erc20.rs @@ -4,23 +4,26 @@ extern crate zink; -use zink::{Mapping, Storage}; +use zink::{ + primitives::{Address, String32, U256}, + Mapping, Storage, +}; -#[zink::storage(u32)] +#[zink::storage(String32)] pub struct Name; -#[zink::storage(u32)] +#[zink::storage(String32)] pub struct Symbol; -#[zink::storage(u32)] +#[zink::storage(U256)] pub struct TotalSupply; -#[zink::storage(i32, i32)] +#[zink::storage(Address, U256)] pub struct Balances; /// Get value from the storage. #[zink::external] -pub fn init(name: u32, symbol: u32) { +pub fn init(name: String32, symbol: String32) { Name::set(name); Symbol::set(symbol); } @@ -31,6 +34,12 @@ pub fn decimals() -> u32 { 8 } +fn _transfer(_from: Address, _to: Address) { + // TODO: check and reverts +} + +fn _update(_from: Address) {} + #[cfg(not(target_arch = "wasm32"))] fn main() {} @@ -41,18 +50,23 @@ fn deploy() -> anyhow::Result<()> { let mut contract = Contract::search("erc20")?.compile()?; let mut evm = EVM::default(); - let mut calldata: Vec = Default::default(); - calldata.extend_from_slice(&42.to_bytes32()); - calldata.extend_from_slice(&42.to_bytes32()); + let name = "The Zink Language"; + let symbol = "zink"; // 1. deploy let info = evm.deploy( &contract .construct( [ - (vec![0].try_into()?, vec![42].try_into()?), - (vec![1].try_into()?, vec![42].try_into()?), - (vec![2].try_into()?, vec![42].try_into()?), + (Name::STORAGE_KEY.to_bytes32().into(), name.to_vec().into()), + ( + Symbol::STORAGE_KEY.to_bytes32().into(), + symbol.to_vec().into(), + ), + ( + TotalSupply::STORAGE_KEY.to_bytes32().into(), + vec![42].try_into()?, + ), ] .into_iter() .collect(), @@ -65,13 +79,13 @@ fn deploy() -> anyhow::Result<()> { let info = evm .calldata(&contract.encode(&[b"name()".to_vec()])?) .call(address)?; - assert_eq!(info.ret, 42u64.to_bytes32()); + assert_eq!(info.ret, name.to_bytes32()); // 3. get symbol let info = evm .calldata(&contract.encode(&[b"symbol()".to_vec()])?) .call(address)?; - assert_eq!(info.ret, 42u64.to_bytes32()); + assert_eq!(info.ret, symbol.to_bytes32()); // 3. get total supply let info = evm diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs index b2edf79c1..431c2da3b 100644 --- a/zink/codegen/src/lib.rs +++ b/zink/codegen/src/lib.rs @@ -7,6 +7,7 @@ use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct}; mod event; mod selector; mod storage; +mod utils; /// Event logging interface /// diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs index dad1b3c24..6ea312ac8 100644 --- a/zink/codegen/src/storage.rs +++ b/zink/codegen/src/storage.rs @@ -1,5 +1,6 @@ extern crate proc_macro; +use crate::utils::Bytes32; use heck::AsSnakeCase; use proc_macro::TokenStream; use proc_macro2::{Literal, Span, TokenTree}; @@ -47,8 +48,7 @@ impl Storage { let is = &self.target; let name = self.target.ident.clone(); let slot = storage_slot(name.to_string()); - let mut key = [0; 32]; - key[28..].copy_from_slice(&slot.to_le_bytes()); + let key = slot.to_bytes32(); let keyl = Literal::byte_string(&key); let mut expanded = quote! { @@ -98,7 +98,7 @@ impl Storage { let mut seed = [0; 64]; seed[..32].copy_from_slice(&key.bytes32()); - seed[60..].copy_from_slice(&Self::STORAGE_SLOT.to_le_bytes()); + seed[60..].copy_from_slice(&Self::STORAGE_SLOT.bytes32()); zink::keccak256(&seed) } } @@ -140,7 +140,7 @@ impl Storage { let mut seed = [0; 64]; seed[..32].copy_from_slice(&key1.bytes32()); - seed[60..].copy_from_slice(&Self::STORAGE_SLOT.to_le_bytes()); + seed[60..].copy_from_slice(&Self::STORAGE_SLOT.bytes32()); let skey1 = zink::keccak256(&seed); seed[..32].copy_from_slice(&skey1); seed[32..].copy_from_slice(&key2.bytes32()); diff --git a/zink/codegen/src/utils.rs b/zink/codegen/src/utils.rs new file mode 100644 index 000000000..7ab578804 --- /dev/null +++ b/zink/codegen/src/utils.rs @@ -0,0 +1,121 @@ +//! Utils for bytes conversion. +//! +//! TODO: move this util to other library + +/// Trait for converting type to bytes32. +pub trait Bytes32: Sized { + /// Convert type to the lowest significant bytes 32. + fn to_bytes32(&self) -> [u8; 32]; + + /// Convert type to vec of bytes. + fn to_vec(&self) -> Vec { + self.to_bytes32().to_vec() + } +} + +/// Implement Bytes32 for types. +macro_rules! impl_bytes32 { + ($($ty:ident),+) => { + $( + impl Bytes32 for $ty { + fn to_bytes32(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + let ls_bytes = { + self.to_le_bytes() + .into_iter() + .rev() + .skip_while(|b| *b == 0) + .collect::>() + .into_iter() + .rev() + .collect::>() + }; + + bytes[(32 - ls_bytes.len())..].copy_from_slice(&ls_bytes); + bytes + } + + fn to_vec(&self) -> Vec { + self.to_le_bytes().to_vec() + } + } + )+ + }; +} + +impl Bytes32 for Vec { + fn to_bytes32(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + bytes[(32 - self.len())..].copy_from_slice(self); + bytes + } + + fn to_vec(&self) -> Vec { + self.clone() + } +} + +impl Bytes32 for [u8; 20] { + fn to_bytes32(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + bytes[12..].copy_from_slice(self); + bytes + } +} + +impl Bytes32 for [u8; 32] { + fn to_bytes32(&self) -> [u8; 32] { + *self + } + + fn to_vec(&self) -> Vec { + self.as_ref().into() + } +} + +impl Bytes32 for &[u8] { + fn to_bytes32(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + bytes[(32 - self.len())..].copy_from_slice(self); + bytes + } + + fn to_vec(&self) -> Vec { + (*self).into() + } +} + +impl Bytes32 for () { + fn to_bytes32(&self) -> [u8; 32] { + [0; 32] + } + + fn to_vec(&self) -> Vec { + Default::default() + } +} + +impl Bytes32 for &str { + fn to_bytes32(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + bytes[(32 - self.len())..].copy_from_slice(self.as_bytes()); + bytes + } + + fn to_vec(&self) -> Vec { + self.as_bytes().into() + } +} + +impl Bytes32 for bool { + fn to_bytes32(&self) -> [u8; 32] { + let mut output = [0; 32]; + if *self { + output[31] = 1; + } + + output + } +} + +impl_bytes32!(i8, u8, i16, u16, i32, u32, usize, i64, u64, i128, u128); diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs index 77d88ec08..dceea900b 100644 --- a/zink/src/ffi/asm.rs +++ b/zink/src/ffi/asm.rs @@ -1,6 +1,6 @@ //! Assembly FFI. -use crate::primitives::Address; +use crate::primitives::{Address, U256}; #[link(wasm_import_module = "asm")] #[allow(improper_ctypes)] @@ -32,6 +32,9 @@ extern "C" { /// Push address to stack pub fn push_address(address: Address); + /// Push u256 to stack + pub fn push_u256(u256: U256); + /// Load a 8-bit signed integer from the storage. pub fn sload_i8() -> i8; @@ -58,4 +61,7 @@ extern "C" { /// Load address from storage pub fn sload_address() -> Address; + + /// Load address from storage + pub fn sload_u256() -> U256; } diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs index 398ec523c..e2d2cdcaa 100644 --- a/zink/src/ffi/evm.rs +++ b/zink/src/ffi/evm.rs @@ -100,7 +100,7 @@ extern "C" { pub fn push31(val: i32); /// Push 32 bytes to the stack. - pub fn push32(val: i32); + pub fn push32(); /// Store a value in the storage pub fn sstore(); diff --git a/zink/src/primitives/address.rs b/zink/src/primitives/address.rs index be979b915..6df50fc27 100644 --- a/zink/src/primitives/address.rs +++ b/zink/src/primitives/address.rs @@ -9,6 +9,12 @@ pub struct Address( ); impl Address { + /// Returns empty address + #[cfg(not(target_family = "wasm"))] + pub const fn empty() -> Self { + Address([0; 20]) + } + /// if self equal to another /// /// NOTE: not using core::cmp because it uses registers in wasm diff --git a/zink/src/primitives/mod.rs b/zink/src/primitives/mod.rs index d0f41fb0b..67b4af318 100644 --- a/zink/src/primitives/mod.rs +++ b/zink/src/primitives/mod.rs @@ -1,5 +1,11 @@ //! Zink primitive types mod address; +mod u256; pub use address::Address; +pub use u256::U256; + +pub type Bytes20 = Address; +pub type Bytes32 = U256; +pub type String32 = U256; diff --git a/zink/src/primitives/u256.rs b/zink/src/primitives/u256.rs new file mode 100644 index 000000000..73ced9aca --- /dev/null +++ b/zink/src/primitives/u256.rs @@ -0,0 +1,34 @@ +use crate::{ffi, storage::StorageValue, Asm}; + +/// Account address +#[repr(C)] +#[derive(Clone, Copy)] +pub struct U256( + #[cfg(target_family = "wasm")] i32, + #[cfg(not(target_family = "wasm"))] [u8; 32], +); + +impl U256 { + /// Returns empty address + #[cfg(not(target_family = "wasm"))] + pub const fn empty() -> Self { + U256([0; 32]) + } +} + +impl Asm for U256 { + fn push(self) { + unsafe { ffi::asm::push_u256(self) } + } + + #[cfg(not(target_family = "wasm"))] + fn bytes32(&self) -> [u8; 32] { + self.0 + } +} + +impl StorageValue for U256 { + fn sload() -> Self { + unsafe { ffi::asm::sload_u256() } + } +}