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(example): caller as local variable #270

Merged
merged 4 commits into from
Nov 13, 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: 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