Skip to content

Commit

Permalink
feat(example): caller as local variable (#270)
Browse files Browse the repository at this point in the history
* feat(example): introduce example for caller

* feat(zint): introduce caller for address

* chore(example): apply no_mangle for internal functions in ERC20

* chore(example) preview tests for caller in memory
  • Loading branch information
clearloop authored Nov 13, 2024
1 parent a217af6 commit 6ac3241
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 8 deletions.
2 changes: 0 additions & 2 deletions codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
1 change: 0 additions & 1 deletion codegen/src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/visitor/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down
105 changes: 105 additions & 0 deletions examples/approval.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
11 changes: 8 additions & 3 deletions examples/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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));
Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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()) {
Expand All @@ -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";

Expand Down
6 changes: 6 additions & 0 deletions zink/src/primitives/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Expand Down
1 change: 1 addition & 0 deletions zink/src/storage/dkmapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions zint/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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<Info> {
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 {
Expand Down

0 comments on commit 6ac3241

Please sign in to comment.