Skip to content

Commit

Permalink
feat(zink): introduce storage mapping (#233)
Browse files Browse the repository at this point in the history
* feat(zink): introduce storage registry

* docs(storage): design of storage

* feat(zink): introduce mapping interface

* feat(zink): expand module storage

* feat(zink): mainly use trait for kv storage

* feat(storage): introduce mapping interface

* refactor(zink): declare storage on exposed struct

* feat(zink): proc-macro for mapping storage

* feat(zink): add tests of mapping

* feat(zink): mapping tests in bytecode level

* feat(zink): complete the test of mappings

* feat(zink): clean unused work

* feat(zink): bump version to 0.1.11
  • Loading branch information
clearloop authored Sep 24, 2024
1 parent 9043c51 commit f03cab8
Show file tree
Hide file tree
Showing 27 changed files with 496 additions and 148 deletions.
21 changes: 12 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ members = [
resolver = "2"

[workspace.package]
version = "0.1.10"
version = "0.1.11"
authors = ["clearloop"]
edition = "2021"
license = "GPL-3.0-only"
Expand All @@ -43,7 +43,7 @@ serde_json = "1.0.113"
smallvec = "1.13.1"
syn = { version = "2.0.48", features = [ "full" ] }
thiserror = "1.0.56"
tiny-keccak = "2.0.2"
tiny-keccak = { version = "2.0.2", features = ["keccak"], default-features = false }
toml = "0.8.9"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
Expand All @@ -52,18 +52,18 @@ wasmparser = "0.121.0"
wat = "1.0.85"

## EVM packages
opcodes = { package = "evm-opcodes", path = "evm/opcodes", version = "=0.0.3", features = [ "data" ] }
opcodes = { package = "evm-opcodes", path = "evm/opcodes", version = "=0.0.4", features = [ "data" ] }
sol-abi = { path = "evm/abi", version = "=0.0.1" }

## Zink packages
elko = { path = "elko", version = "0.1.10" }
filetests = { package = "zinkc-filetests", path = "compiler/filetests", version = "0.1.10" }
zabi = { path = "abi", version = "0.1.10" }
zingen = { path = "codegen", version = "0.1.10" }
zink = { path = ".", version = "0.1.10" }
zink-codegen = { path = "zink/codegen", version = "0.1.10" }
zinkc = { path = "compiler", version = "0.1.10" }
zint = { path = "zint", version = "0.1.10" }
elko = { path = "elko", version = "0.1.11" }
filetests = { package = "zinkc-filetests", path = "compiler/filetests", version = "0.1.11" }
zabi = { path = "abi", version = "0.1.11" }
zingen = { path = "codegen", version = "0.1.11" }
zink = { path = ".", version = "0.1.11" }
zink-codegen = { path = "zink/codegen", version = "0.1.11" }
zinkc = { path = "compiler", version = "0.1.11" }
zint = { path = "zint", version = "0.1.11" }

[workspace.metadata.conta]
packages = [
Expand Down Expand Up @@ -102,4 +102,7 @@ zink-codegen.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
anyhow.workspace = true
filetests.workspace = true
opcodes = { workspace = true, features = ["data"] }
tracing.workspace = true
zint.workspace = true
hex.workspace = true
4 changes: 2 additions & 2 deletions abi/src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
#![cfg(feature = "selector")]

use crate::Abi;
use tiny_keccak::{Hasher, Sha3};
use tiny_keccak::{Hasher, Keccak};

/// Generate a keccak hash of the input (sha3)
pub fn keccak256(input: &[u8]) -> [u8; 32] {
let mut hasher = Sha3::v256();
let mut hasher = Keccak::v256();
let mut output = [0; 32];
hasher.update(input);
hasher.finalize(&mut output);
Expand Down
10 changes: 5 additions & 5 deletions codegen/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ impl Assembler {
return Ok(());
}

tracing::trace!(
"increment stack pointer {}({items}) -> {}",
self.sp,
self.sp + items
);
// tracing::trace!(
// "increment stack pointer {}({items}) -> {}",
// self.sp,
// self.sp + items
// );
self.sp += items;

// TODO: fix this limitation: should be 1024. (#127)
Expand Down
9 changes: 4 additions & 5 deletions codegen/src/wasm/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ impl TryFrom<(&str, &str)> for HostFunc {
Ok(Self::NoOp)
}
}
("evm", name) => {
Ok(Self::Evm(OpCode::from_str(name).map_err(|_| {
Error::HostFuncNotFound(module.into(), name.into())
})?))
}
("evm", name) => Ok(Self::Evm(OpCode::from_str(name).map_err(|_| {
tracing::error!("Failed to load host function: {:?}", import);
Error::HostFuncNotFound(module.into(), name.into())
})?)),
("zinkc", "emit_abi") => Ok(Self::EmitABI),
_ => {
tracing::warn!("Failed to load host function: {:?}", import);
Expand Down
7 changes: 4 additions & 3 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@
- [Log](./examples/log.md)
- [Select](./examples/select.md)
- [Storage](./examples/storage.md)
- [Command Line Tool](./cli/README.md)
- [elko](./cli/elko.md)
- [zinkc](./cli/zinkc.md)
- [Styles](./styles/README.md)
- [Compiler](./compiler/README.md)
- [Arithmetic](./compiler/arithmetic.md)
- [Calls](./compiler/calls.md)
- [Control Flow](./compiler/control-flow.md)
- [Locals](./compiler/locals.md)
- [Recursion](./compiler/recursion.md)
- [Storage](./compiler/storage.md)
- [Stability](./stability/README.md)
- [v0.1.0](./stability/v0.1.0.md)
- [Security](./security.md)
- [Benchmarks](./benchmarks/README.md)
- [Fibonacci](./benchmarks/fibonacci.md)
- [Log](./benchmarks/log.md)
- [Storage](./benchmarks/storage.md)
- [Command Line Tool](./cli/README.md)
- [elko](./cli/elko.md)
- [zinkc](./cli/zinkc.md)
- [Contributing](./contributing/README.md)
- [Architecture](./contributing/architecture.md)
- [Building](./contributing/building.md)
Expand Down
28 changes: 28 additions & 0 deletions docs/compiler/storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Storage

The storage keys in Zink is slot based, for example, the first detected
storage in compilation will be using `0` as storage key.

```solidity
// Loading storage at 0
PUSH0
SLOAD
// Loading storage at 1
PUSH1 0x01
SLOAD
```

## Key-Value

As mentioned above, all key-value pairs follows using number as storage key, however, the value
will be limited with 32 bytes, dynamic value like string is currently not supported.

## Mapping

Mapping keys are generated via `keccak256(slot, key)`

## Array

Similar to mappings, but the keys will be using `u32` / `u64` for indexing due to the optimization
on the wasm side in the zink compiler, which means, the max size of an array is `max(u64)`.
10 changes: 7 additions & 3 deletions evm/abi/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
use crate::Arg;
use core::{convert::Infallible, fmt, str::FromStr};

#[cfg(feature = "syn")]
use quote::ToTokens;

#[cfg(not(feature = "std"))]
use crate::std::{String, ToString, Vec};

Expand All @@ -28,9 +31,9 @@ impl From<&syn::Signature> for Abi {
.inputs
.iter()
.filter_map(|arg| {
if let syn::FnArg::Typed(syn::PatType { ty, .. }) = arg {
if let syn::FnArg::Typed(syn::PatType { pat, ty, .. }) = arg {
Some(Arg {
name: sig.ident.to_string(),
name: pat.to_token_stream().to_string(),
ty: crate::Param::from(ty),
})
} else {
Expand All @@ -41,7 +44,8 @@ impl From<&syn::Signature> for Abi {

let outputs = if let syn::ReturnType::Type(_, ty) = &sig.output {
vec![Arg {
name: sig.ident.to_string(),
// TODO: how to name the output?
name: "output".into(),
ty: crate::Param::from(ty),
}]
} else {
Expand Down
2 changes: 1 addition & 1 deletion evm/opcodes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "evm-opcodes"
description = "Rust implementation of EVM opcode"
documentation = "https://docs.rs/evm-opcodes"
version = "0.0.3"
version = "0.0.4"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion evm/opcodes/src/shanghai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ opcodes! {
(0x1b, SHL, 3, 2, 1, "Left shift operation", Constantinople, ComparisonBitwiseLogic),
(0x1c, SHR, 3, 2, 1, "Logical right shift operation", Constantinople, ComparisonBitwiseLogic),
(0x1d, SAR, 3, 2, 1, "Arithmetic (signed) right shift operation", Constantinople, ComparisonBitwiseLogic),
(0x20, SHA3, 30, 2, 1, "Compute Keccak-256 hash.", Frontier, StopArithmetic),
(0x20, KECCAK256, 30, 2, 1, "Compute Keccak-256 hash.", Frontier, StopArithmetic),
(0x30, ADDRESS, 2, 0, 1, "Get address of currently executing account.", Frontier, EnvironmentalInformation),
(0x31, BALANCE, 20, 1, 1, "Get balance of the given account.", Frontier, EnvironmentalInformation),
(0x32, ORIGIN, 2, 0, 1, "Get execution origination address.", Frontier, EnvironmentalInformation),
Expand Down
2 changes: 1 addition & 1 deletion examples/addition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn test() -> anyhow::Result<()> {
&1u64.to_bytes32(),
&2u64.to_bytes32(),
])?;
assert_eq!(info.ret, 3u64.to_bytes32());

assert_eq!(info.ret, 3u64.to_bytes32());
Ok(())
}
4 changes: 2 additions & 2 deletions examples/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use zink::Storage;
/// Storage key is taken based on macro order
/// (e.g this macro is first and only in this project,
/// so it will take 0x0 contract storage key)
#[zink::storage]
pub type Counter = i32;
#[zink::storage(i32)]
pub struct Counter;

/// Get value from the storage.
#[zink::external]
Expand Down
60 changes: 60 additions & 0 deletions examples/mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Storage example.
#![cfg_attr(target_arch = "wasm32", no_std)]
#![cfg_attr(target_arch = "wasm32", no_main)]

extern crate zink;

use zink::Mapping as _;

/// Counter with value type `i32`
#[zink::storage(i32 => i32)]
pub struct Mapping;

/// Set the mapping
#[zink::external]
pub fn mset(key: i32, value: i32) {
Mapping::set(key, value);
}

/// Get from ampping
#[zink::external]
pub fn mget(key: i32) -> i32 {
Mapping::get(key)
}

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

#[test]
fn mapping() -> anyhow::Result<()> {
use zint::{Bytes32, Contract};

let mut contract = Contract::search("mapping")?.compile()?;
let mut evm = contract.deploy()?.commit(true);

let key = 0x00;
let value: i32 = 0x42;

// set value to storage
let calldata = contract.encode(&[
b"mset(int32,int32)".to_vec(),
value.to_bytes32().to_vec(),
key.to_bytes32().to_vec(),
])?;
let info = evm.calldata(&calldata).call(contract.address)?;
assert!(info.ret.is_empty());

tracing::debug!("{info:?}");
// verify result with database
let storage_key = zint::keccak256(&[0; 0x40]);
assert_eq!(
evm.storage(contract.address, storage_key)?,
value.to_bytes32(),
);

// get value from storage
let calldata = contract.encode(&[b"mget(int32)".to_vec(), key.to_bytes32().to_vec()])?;
let info = evm.calldata(&calldata).call(contract.address)?;
assert_eq!(info.ret, value.to_bytes32(), "{info:#?}",);
Ok(())
}
Loading

0 comments on commit f03cab8

Please sign in to comment.