diff --git a/codegen/src/codegen/function.rs b/codegen/src/codegen/function.rs index 09c40e625..380983fab 100644 --- a/codegen/src/codegen/function.rs +++ b/codegen/src/codegen/function.rs @@ -98,8 +98,6 @@ impl Function { // Record the offset for validation. while let Ok((count, val)) = locals.read() { for _ in 0..count { - // TODO: the below here is outdated, sp is not required anymore after #245 - // Define locals. self.locals .push(LocalSlot::new(val, LocalSlotType::Variable, sp)); diff --git a/codegen/src/local.rs b/codegen/src/local.rs index 32e0989e5..ed2ffc801 100644 --- a/codegen/src/local.rs +++ b/codegen/src/local.rs @@ -87,7 +87,6 @@ impl Locals { let offset = if local.ty() == &LocalSlotType::Parameter { self.inner[..index].iter().fold(0, |acc, x| acc + x.align()) } else { - // panic!("This should never be reached"); self.inner[..index] .iter() .filter(|x| x.ty() == &LocalSlotType::Variable) diff --git a/codegen/src/visitor/local.rs b/codegen/src/visitor/local.rs index ac7c73051..3d8525dd4 100644 --- a/codegen/src/visitor/local.rs +++ b/codegen/src/visitor/local.rs @@ -56,7 +56,7 @@ impl Function { /// Local get for variables. fn _local_get_var(&mut self, local_index: usize) -> Result<()> { - tracing::trace!("Local get variable: {local_index}"); + tracing::debug!("Local get variable: {local_index}"); if local_index + 1 > self.locals.len() { // The local we want is not from function arguments return Err(Error::InvalidLocalIndex(local_index)); diff --git a/codegen/src/wasm/mod.rs b/codegen/src/wasm/mod.rs index bd08d56a2..a4e5cc38f 100644 --- a/codegen/src/wasm/mod.rs +++ b/codegen/src/wasm/mod.rs @@ -154,7 +154,7 @@ impl Env { /// Allocate memory slots from local index pub fn alloc(&self, index: u32) -> SmallVec<[u8; 4]> { let slots = index + self.reserved(); - tracing::trace!( + tracing::debug!( "allocating memory for local {index} of function {:?}, slot: {slots}", self.index ); diff --git a/examples/approval.rs b/examples/approval.rs new file mode 100644 index 000000000..914a10147 --- /dev/null +++ b/examples/approval.rs @@ -0,0 +1,105 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] +#![cfg_attr(target_arch = "wasm32", no_main)] + +extern crate zink; + +use zink::{ + primitives::{Address, U256}, + DoubleKeyMapping, +}; + +#[zink::storage(Address, Address, U256)] +pub struct Allowance; + +#[zink::external] +pub fn approve(spender: Address, value: U256) -> bool { + let owner = Address::caller(); + _approve(owner, spender, value); + true +} + +#[zink::external] +pub fn spend_allowance(spender: Address, value: U256) -> bool { + let owner = Address::caller(); + let current_allowance = Allowance::get(owner, spender); + if current_allowance.lt(U256::max()) { + if current_allowance.lt(value) { + zink::revert!("ERC20 Insufficient allowance"); + } + + _approve(owner, spender, current_allowance.sub(value)) + } + true +} + +// NOTE: issue #272 +// +// #[no_mangle] here is required otherwise the inner functions could +// not get the passing variables correctly. +#[no_mangle] +fn _approve(owner: Address, spender: Address, value: U256) { + if owner.eq(Address::empty()) { + zink::revert!("ERC20 Invalid approval"); + } + + if spender.eq(Address::empty()) { + zink::revert!("ERC20 Invalid spender"); + } + + Allowance::set(owner, spender, value); +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() {} + +#[test] +fn test_approval() -> anyhow::Result<()> { + use zint::{Bytes32, Contract, EVM}; + + let mut evm = EVM::default().commit(true).caller([1; 20]); + let contract = Contract::search("approval")?.compile()?; + let info = evm.deploy(&contract.bytecode()?)?; + let address = info.address; + + let value = 42; + + let spender = [42; 20]; + let info = evm + .calldata(&contract.encode(&[ + b"approve(address,uint256)".to_vec(), + spender.to_bytes32().to_vec(), + value.to_bytes32().to_vec(), + ])?) + .call(address)?; + assert_eq!(info.ret, true.to_bytes32()); + + let allowance = evm.storage( + address, + Allowance::storage_key(Address(evm.caller), Address(spender)), + )?; + assert_eq!(value.to_bytes32(), allowance); + + // TODO: #273 + // + // error: invalid jump while there are to composed functions + // + // This could be caused by the `caller()` on stack is not consumed correctly + // + // // spend allowance + // let half_value = 21; + // let info = evm + // .calldata(&contract.encode(&[ + // b"spend_allowance(address,uint256)".to_vec(), + // spender.to_bytes32().to_vec(), + // half_value.to_bytes32().to_vec(), + // ])?) + // .call(address)?; + // println!("{info:?}"); + // assert_eq!(info.ret, true.to_bytes32()); + // let allowance = evm.storage( + // address, + // Allowance::storage_key(Address(evm.caller), Address(spender)), + // )?; + // assert_eq!(half_value.to_bytes32(), allowance); + Ok(()) +} diff --git a/examples/erc20.rs b/examples/erc20.rs index 81edb0b1c..6ea154447 100644 --- a/examples/erc20.rs +++ b/examples/erc20.rs @@ -60,6 +60,7 @@ pub fn transfer_from(from: Address, to: Address, value: U256) -> bool { true } +#[no_mangle] fn _transfer(from: Address, to: Address, value: U256) { if from.eq(Address::empty()) { zink::revert!("Empty from address"); @@ -72,6 +73,7 @@ fn _transfer(from: Address, to: Address, value: U256) { _update(from, to, value) } +#[no_mangle] fn _update(from: Address, to: Address, value: U256) { if from.eq(Address::empty()) { TotalSupply::set(TotalSupply::get().add(value)); @@ -91,6 +93,7 @@ fn _update(from: Address, to: Address, value: U256) { } } +#[no_mangle] fn _mint(account: Address, value: U256) { if account.eq(Address::empty()) { zink::revert!("ERC20 invalid receiver"); @@ -99,6 +102,7 @@ fn _mint(account: Address, value: U256) { _update(Address::empty(), account, value) } +#[no_mangle] fn _burn(account: Address, value: U256) { if account.eq(Address::empty()) { zink::revert!("ERC20 invalid sender"); @@ -107,6 +111,7 @@ fn _burn(account: Address, value: U256) { _update(account, Address::empty(), value) } +#[no_mangle] fn _approve(owner: Address, spender: Address, value: U256, _emit_event: bool) { if owner.eq(Address::empty()) { zink::revert!("ERC20 Invalid approval"); @@ -119,6 +124,7 @@ fn _approve(owner: Address, spender: Address, value: U256, _emit_event: bool) { Allowance::set(owner, spender, value); } +#[no_mangle] fn _spend_allowance(owner: Address, spender: Address, value: U256) { let current_allowance = Allowance::get(owner, spender); if current_allowance.lt(U256::max()) { @@ -137,14 +143,13 @@ fn main() {} // // 1. nested function allocations // 2. memory slots for local variables -#[ignore] #[test] +#[ignore] fn deploy() -> anyhow::Result<()> { use zint::{Bytes32, Contract, EVM}; let mut contract = Contract::search("erc20")?.compile()?; - - let mut evm = EVM::default(); + let mut evm = EVM::default().commit(true); let name = "The Zink Language"; let symbol = "zink"; diff --git a/zink/src/primitives/address.rs b/zink/src/primitives/address.rs index 92ffb2649..0f3431ec3 100644 --- a/zink/src/primitives/address.rs +++ b/zink/src/primitives/address.rs @@ -21,10 +21,16 @@ impl Address { Address(0) } + /// Returns empty address + pub fn caller() -> Self { + unsafe { ffi::evm::caller() } + } + /// if self equal to another /// /// NOTE: not using core::cmp because it uses registers in wasm #[allow(clippy::should_implement_trait)] + #[inline(always)] pub fn eq(self, other: Self) -> bool { unsafe { ffi::address_eq(self, other) } } diff --git a/zink/src/storage/dkmapping.rs b/zink/src/storage/dkmapping.rs index 95aa21d1d..5762536b6 100644 --- a/zink/src/storage/dkmapping.rs +++ b/zink/src/storage/dkmapping.rs @@ -20,6 +20,7 @@ pub trait DoubleKeyMapping { } /// Set key and value + #[inline(always)] fn set(key1: Self::Key1, key2: Self::Key2, value: Self::Value) { value.push(); load_double_key(key1, key2, Self::STORAGE_SLOT); diff --git a/zint/src/evm.rs b/zint/src/evm.rs index d0d034ef1..b12316282 100644 --- a/zint/src/evm.rs +++ b/zint/src/evm.rs @@ -23,6 +23,8 @@ pub const CONTRACT: [u8; 20] = [1; 20]; /// Wrapper of full REVM pub struct EVM<'e> { inner: Revm<'e, (), InMemoryDB>, + /// Caller for the execution + pub caller: [u8; 20], /// If commit changes commit: bool, } @@ -35,6 +37,7 @@ impl<'e> Default for EVM<'e> { let evm = Revm::<'e, (), EmptyDB>::builder().with_db(db).build(); Self { inner: evm, + caller: [0; 20], commit: false, } } @@ -63,11 +66,19 @@ impl<'e> EVM<'e> { self } + /// Set caller for the execution + pub fn caller(mut self, caller: [u8; 20]) -> Self { + self.caller = caller; + self + } + /// Send transaction to the provided address. pub fn call(&mut self, to: [u8; 20]) -> Result { let to = TransactTo::Call(to.into()); self.inner.tx_mut().gas_limit = GAS_LIMIT; self.inner.tx_mut().transact_to = to; + self.inner.tx_mut().caller = self.caller.into(); + if self.commit { self.inner.transact_commit()?.try_into() } else {