Skip to content

Commit

Permalink
feat(zink): introduce primitive type Address (#220)
Browse files Browse the repository at this point in the history
* feat(evm): add dyn types in sol-abi

* feat(abi): support all primitive types

* chore(example): use u64 for fibonacci

* feat(zink): introduce parameter loader

* chore(zink): parameter bytes, is this the solution?

* chore(zink): support boolean in abi

* feat(zink): cast address with bytes24

* chore(zink): empty pointers for addresses

* feat(zink): introduce primitive type Address
  • Loading branch information
clearloop authored Oct 3, 2024
1 parent d8ad99c commit f687fbe
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 40 deletions.
16 changes: 16 additions & 0 deletions codegen/src/codegen/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ impl Dispatcher {
// 1. drop selector.
// 2. load calldata to stack.
// 3. jump to the callee function.
//
// TODO: Parse bytes from the selector.
fn process(&mut self, len: usize, last: bool) -> Result<bool> {
let len = len as u8;
if last && len == 0 {
Expand All @@ -111,6 +113,20 @@ impl Dispatcher {
}

if len > 0 {
// FIXME: Using the length of parameters here
// is incorrect once we have params have length
// over than 4 bytes.
//
// 1. decode the abi from signature, if contains
// bytes type, use `calldatacopy` to load the data
// on stack.
//
// 2. if the present param is a 4 bytes value, use
// `calldataload[n]` directly.
//
// Actually 1. is more closed to the common cases,
// what 4 bytes for in EVM?

// [ ret, callee ] -> [ param * len, ret, callee ]
for p in (0..len).rev() {
let offset = 4 + p * 32;
Expand Down
2 changes: 2 additions & 0 deletions codegen/src/visitor/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ impl Function {
pub fn _local_get(&mut self, local_index: u32) -> Result<()> {
let local_index = local_index as usize;
if self.is_main && local_index < self.ty.params().len() {
// Parsing data from selector.
self._local_get_calldata(local_index)
} else {
// Passing data between local functions.
self._local_get_var(local_index)
}
}
Expand Down
3 changes: 3 additions & 0 deletions codegen/src/wasm/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub enum HostFunc {
//
/// Emit ABI to the compiler.
EmitABI,
/// check equal of two addresses
AddressEq,
}

impl HostFunc {
Expand Down Expand Up @@ -53,6 +55,7 @@ impl TryFrom<(&str, &str)> for HostFunc {
Error::HostFuncNotFound(module.into(), name.into())
})?)),
("zinkc", "emit_abi") => Ok(Self::EmitABI),
("zinkc", "address_eq") => Ok(Self::Evm(OpCode::EQ)),
_ => {
tracing::warn!("Failed to load host function: {:?}", import);
Err(Error::HostFuncNotFound(module.into(), name.into()))
Expand Down
47 changes: 43 additions & 4 deletions evm/abi/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
# Solidity ABI
# Solidity ABIA

An implementation of solidity ABI in rust.
An straightforward solidity ABI implementation for zink.

This library only contains a part of the solidity ABI implementation,
if you are looking for a complete implementation of solidity ABI
in rust, see [alloy-core][alloy-core].

## Static Types

Only rust primitive types are supported in this static type port,

| rust | solidity |
|--------|-----------|
| `i8` | `int8` |
| `u8` | `uint8` |
| `i16` | `int16` |
| `u16` | `uint16` |
| `i32` | `int32` |
| `u32` | `uint32` |
| `i64` | `int64` |
| `u64` | `uint64` |
| `i128` | `int128` |
| `u128` | `uint128` |
| `bool` | `bool` |


## Dynamic Types

The implementation of dynamic arguments follows [use-of-dynamic-types][dyn-types],
same as the static types, only ports the rust types:

| rust | solidity |
|------------|-----------|
| `Vec<u8>` | `bytes` |
| `[u8; 20]` | `address` |
| `String` | `string` |

More complex types are currently not supported.

Currently only used by the zink language, so the provided features
is syncing with the development of zink.

## LICENSE

GPL-3.0


[alloy-core]: https://github.com/alloy-rs/core
[dyn-types]: https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types
[zink]: https://github.com/zink-lang/zink
41 changes: 36 additions & 5 deletions evm/abi/src/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,50 @@ pub struct Arg {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Param {
/// A 8-bit integer.
Int8,
/// A 16-bit integer.
Int16,
/// A 32-bit integer.
Int32,
/// A 64-bit integer.
Int64,
/// A 8-bit unsigned integer.
UInt8,
/// A 16-bit unsigned integer.
UInt16,
/// A 32-bit unsigned integer.
UInt32,
/// A 64-bit unsigned integer.
UInt64,
/// An unknown type.
/// A boolean type.
Bool,
/// An EVM address.
Address,
/// A byte array.
#[default]
Unknown,
Bytes,
/// A string type.
String,
/// An unknown type.
Unknown(String),
}

impl From<&str> for Param {
fn from(s: &str) -> Self {
match s {
"i8" | "int8" => Param::Int8,
"u8" | "uint8" => Param::UInt8,
"i32" | "int32" => Param::Int32,
"i64" | "int64" => Param::Int64,
"u16" | "uint16" => Param::UInt16,
"u32" | "uint32" => Param::UInt32,
"usize" | "u64" | "uint64" => Param::UInt64,
_ => Param::Unknown,
"u64" | "uint64" => Param::UInt64,
"bool" => Param::Bool,
"address" | "Address" => Param::Address,
"Bytes" | "Vec<u8>" => Param::Bytes,
"String" => Param::String,
_ => Param::Unknown(s.to_string()),
}
}
}
Expand All @@ -57,11 +80,19 @@ impl FromStr for Param {
impl AsRef<str> for Param {
fn as_ref(&self) -> &str {
match self {
Param::Int8 => "int8",
Param::Int16 => "int16",
Param::Int32 => "int32",
Param::Int64 => "int64",
Param::UInt8 => "uint8",
Param::UInt16 => "uint16",
Param::UInt32 => "uint32",
Param::UInt64 => "uint64",
Param::Unknown => "unknown",
Param::Address => "address",
Param::Bool => "boolean",
Param::Bytes => "bytes",
Param::String => "string",
Param::Unknown(ty) => ty.as_ref(),
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions examples/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extern crate zink;

/// Calculates the nth fibonacci number.
#[zink::external]
pub fn fib(n: usize) -> usize {
pub fn fib(n: u64) -> u64 {
if n < 2 {
n
} else {
Expand All @@ -25,27 +25,27 @@ fn test() -> anyhow::Result<()> {
let selector = "fib(uint64)".as_bytes();

// x = 0
let info = contract.execute([selector, &0usize.to_bytes32()])?;
let info = contract.execute([selector, &0u64.to_bytes32()])?;
assert_eq!(0.to_bytes32().to_vec(), info.ret);

// x = 1
let info = contract.execute([selector, &1usize.to_bytes32()])?;
let info = contract.execute([selector, &1u64.to_bytes32()])?;
assert_eq!(1.to_bytes32().to_vec(), info.ret);

// x = 2
let info = contract.execute([selector, &2usize.to_bytes32()])?;
let info = contract.execute([selector, &2u64.to_bytes32()])?;
assert_eq!(1.to_bytes32().to_vec(), info.ret);

// x = 3
let info = contract.execute([selector, &3usize.to_bytes32()])?;
let info = contract.execute([selector, &3u64.to_bytes32()])?;
assert_eq!(2.to_bytes32().to_vec(), info.ret);

// x = 4
let info = contract.execute([selector, &4usize.to_bytes32()])?;
let info = contract.execute([selector, &4u64.to_bytes32()])?;
assert_eq!(3.to_bytes32().to_vec(), info.ret);

// x = 5
let info = contract.execute([selector, &5usize.to_bytes32()])?;
let info = contract.execute([selector, &5u64.to_bytes32()])?;
assert_eq!(5.to_bytes32().to_vec(), info.ret);

Ok(())
Expand Down
58 changes: 58 additions & 0 deletions examples/owner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Bytes example.
#![cfg_attr(target_arch = "wasm32", no_std)]
#![cfg_attr(target_arch = "wasm32", no_main)]

extern crate zink;

use zink::{primitives::Address, Storage};

/// Contract owner storage
#[zink::storage(Address)]
pub struct Owner;

/// check if the passing address is owner
#[zink::external]
pub fn is_owner(owner: Address) -> bool {
Owner::get().eq(owner)
}

#[cfg(not(target_arch = "wasm32"))]
fn main() {}

#[test]
fn test_owner() -> anyhow::Result<()> {
use zint::{Bytes32, Contract};
let mut contract = Contract::search("owner")?.compile()?;
let not_owner = [8; 20];
let mut evm = contract
.construct(
[(vec![0].try_into()?, not_owner.to_vec().try_into()?)]
.into_iter()
.collect(),
)?
.deploy()?
.commit(true);

assert_eq!(
evm.storage(contract.address, [0; 32])?,
not_owner.to_bytes32(),
);

assert_eq!(
evm.calldata(&contract.encode(&[b"is_owner(address)".to_vec(), [0; 32].to_vec()])?)
.call(contract.address)?
.ret,
false.to_bytes32().to_vec()
);

assert_eq!(
evm.calldata(&contract.encode(&[
b"is_owner(address)".to_vec(),
not_owner.to_bytes32().to_vec(),
])?)
.call(contract.address)?
.ret,
true.to_bytes32().to_vec()
);
Ok(())
}
14 changes: 1 addition & 13 deletions zink/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,9 @@ macro_rules! impl_asm {
}
}
};
($len:expr) => {
impl Asm for [u8; $len] {
fn push(self) {
unsafe {
paste! { ffi::evm::[<push $len>](self.as_ptr() as i32); }
}
}
}
};
($($ty:tt),+) => {
$(impl_asm!($ty);)+
};
}

impl_asm!(
i8, u8, i16, u16, i32, u32, i64, u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
);
impl_asm!(i8, u8, i16, u16, i32, u32, i64, u64);
8 changes: 8 additions & 0 deletions zink/src/ffi/asm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Assembly FFI.
use crate::primitives::Address;

#[link(wasm_import_module = "asm")]
#[allow(improper_ctypes)]
extern "C" {
Expand Down Expand Up @@ -27,6 +29,9 @@ extern "C" {
/// Push a 64-bit unsigned integer to the stack.
pub fn push_u64(val: u64);

/// Push address to stack
pub fn push_address(address: Address);

/// Load a 8-bit signed integer from the storage.
pub fn sload_i8() -> i8;

Expand All @@ -50,4 +55,7 @@ extern "C" {

/// Load a 64-bit unsigned integer from the storage.
pub fn sload_u64() -> u64;

/// Load address from storage
pub fn sload_address() -> Address;
}
4 changes: 4 additions & 0 deletions zink/src/ffi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Zink FFI.
use crate::primitives::Address;

pub mod asm;
pub mod evm;

Expand All @@ -9,4 +11,6 @@ extern "C" {
/// Emit ABI to host state.
pub fn emit_abi(ptr: u32, len: u32);

/// Equal operation for addresses
pub fn address_eq(this: Address, other: Address) -> bool;
}
10 changes: 0 additions & 10 deletions zink/src/primitives.rs

This file was deleted.

28 changes: 28 additions & 0 deletions zink/src/primitives/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::{ffi, storage::StorageValue, Asm};

/// Account address
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Address(i32);

impl Address {
/// if self equal to another
///
/// NOTE: not using core::cmp because it uses registers in wasm
#[allow(clippy::should_implement_trait)]
pub fn eq(self, other: Self) -> bool {
unsafe { ffi::address_eq(self, other) }
}
}

impl Asm for Address {
fn push(self) {
unsafe { ffi::asm::push_address(self) }
}
}

impl StorageValue for Address {
fn sload() -> Self {
unsafe { ffi::asm::sload_address() }
}
}
5 changes: 5 additions & 0 deletions zink/src/primitives/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Zink primitive types
mod address;

pub use address::Address;
Loading

0 comments on commit f687fbe

Please sign in to comment.