diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 25f382366..de6e0fc33 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,5 +3,29 @@ Resolves #
### Changes
- [x] step 1
-- [ ] step 2
- [ ] ...
+
+
+
+
diff --git a/Cargo.lock b/Cargo.lock
index 8e63714fe..dcb8df2f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -943,6 +943,12 @@ dependencies = [
"static_assertions",
]
+[[package]]
+name = "fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09904adae26440d46daeacb5ed7922fee69d43ebf84d31e078cd49652cecd718"
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2646,6 +2652,7 @@ version = "0.1.11"
dependencies = [
"anyhow",
"evm-opcodes",
+ "fmt",
"hex",
"paste",
"tiny-keccak",
diff --git a/Cargo.toml b/Cargo.toml
index 3415610a8..807a43dcc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,18 +1,18 @@
[profile]
-dev = { panic = "abort"}
+dev = { panic = "abort" }
release = { panic = "unwind" }
[workspace]
members = [
- "abi",
"codegen",
"compiler",
"compiler/filetests",
- "elko",
"evm/opcodes",
"evm/abi",
+ "zink/abi",
"zink/codegen",
- "zint",
+ "zink/elko",
+ "zink/zint",
]
resolver = "2"
@@ -42,9 +42,11 @@ semver = "1.0.21"
serde = { version = "1.0.196", default-features = false }
serde_json = "1.0.113"
smallvec = "1.13.1"
-syn = { version = "2.0.77", features = [ "full" ] }
+syn = { version = "2.0.77", features = ["full"] }
thiserror = "1.0.56"
-tiny-keccak = { version = "2.0.2", features = ["keccak"], default-features = false }
+tiny-keccak = { version = "2.0.2", features = [
+ "keccak",
+], default-features = false }
toml = "0.8.9"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
@@ -53,18 +55,20 @@ wasmparser = "0.121.0"
wat = "1.0.85"
## EVM packages
-opcodes = { package = "evm-opcodes", path = "evm/opcodes", version = "=0.0.4", 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.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" }
+zabi = { path = "zink/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" }
+zint = { path = "zink/zint", version = "0.1.11" }
[workspace.metadata.conta]
packages = [
@@ -75,7 +79,7 @@ packages = [
"zint",
"zink-codegen",
"zink",
- "elko"
+ "elko",
]
# Zink Programming Language
@@ -97,6 +101,7 @@ readme = "zink/README.md"
path = "zink/src/lib.rs"
[dependencies]
+fmt = "0.1.0"
paste.workspace = true
zink-codegen.workspace = true
diff --git a/README.md b/README.md
index e876791da..be7b0b2d5 100644
--- a/README.md
+++ b/README.md
@@ -1,91 +1,69 @@
-# The Zink Project
+# The Zink Language
> [!CAUTION]
>
-> This project is still under active development, plz DO NOT use it in production.
+> This project is still under active development, please DO NOT use it in production.
[![zink][version-badge]][version-link]
[![ci][ci-badge]][ci-link]
[![telegram][telegram-badge]][telegram-group]
-[The Zink project][book] mainly provides a singlepass compiler `zinkc` which compiles
-WASM to EVM bytecode, the source code of your smart contracts could be any language you like!
+Welcome to the Zink Language! [Bounty issues](https://zink-lang.org/budgets) are now available, join the development of Zink by reading the [book](https://zink-lang.org/).
-```mermaid
-flowchart LR
- R{{Rust}} --> W(WebAssembly)
- O[...] --> W
- W --> Z{Zink Compiler}
- Z --> V[(EVM)]
-```
+```rust
+//! ERC20 Example (WIP)
+#[zink::contract]
+pub struct ERC20;
+
+#[zink::calls]
+impl ERC20 {
+ /// VMs that zink supports
+ pub fn support() -> [zink::String; 4] {
+ ["EVM", "WASM", "RISC-V", "...OTHER_VMS"]
+ }
+}
-Here we highly recommend you to choose `rust` as the language of your smart contracts
-which will unlock all of the following features:
+#[zink::interface]
+impl ERC20 for ERC20 {
+ fn name() -> zink::String {
+ "Zink Language".to_string()
+ }
+}
+```
-- **Safe**: `rustc` is watching you! Furthermore, after compiling your rust code to WASM,
- `zinkc` will precompute all of the stack and memory usage in your contracts to ensure they
- are safe in EVM bytecode as well!
+- **Safe**: `rustc` monitors your code!
-- **High Performance**: The optimizations are provided by the three of `rustc`, `wasm-opt`
- and `zinkc`, your contracts will have the smallest size with **strong performance** in EVM
- bytecode at the end!
+- **Efficient**: Efficient EVM bytecode from `rustc`, `wasm-opt`, and `zinkc`.
-- **Compatible**: All of the `no_std` libraries in rust are your libraries, you can use your
- solidity contracts as part of your zink contracts and your zink contracts as part of your
- solidity contracts :)
+- **Modular**: Upload and download your contract components via `crates.io`.
-- **Easy Debugging**: Developing your smart contracts with only one programming language!
- zink will provide everything you need for developing your contracts officially based on the
- stable projects in rust like the `foundry` tools.
+- **Rusty**: All of the rust tools are available for your contracts!
Run `cargo install zinkup` to install the toolchain!
-## Fibonacci Example
-
-| fib(n) | Zink | Solidity@0.8.21 |
-| ------ | ---- | --------------- |
-| 0 | 110 | 614 |
-| 1 | 110 | 614 |
-| 2 | 262 | 1322 |
-| 3 | 414 | 2030 |
-| 4 | 718 | 3446 |
-| 5 | 1174 | 5570 |
-
-```rust
-/// Calculates the nth fibonacci number using recursion.
-#[no_mangle]
-pub extern "C" fn recursion(n: usize) -> usize {
- if n < 2 {
- n
- } else {
- recursion(n - 1) + recursion(n - 2)
- }
-}
-```
-
-As an example for the benchmark, calculating fibonacci sequence with recursion, missed
-vyper because it doesn't support recursion...Zink is 5x fast on this, but it is mainly
-caused by our current implementation is not completed yet ( missing logic to adapt to more
-situations ), let's stay tuned for `v0.3.0`.
+## Testing & Development
-## Donation
+| Command | Description |
+| ---------- | ---------------------- |
+| `cargo cc` | Clippy all packages |
+| `cargo tt` | Run all tests |
+| `cargo be` | Build all examples |
+| `cargo te` | Run tests for examples |
-After completing the ERC20 implementation, Zink will focus on MEV logic since everything could
-be even more compact and realistic from this dark forest.
+We're using `cargo-nextest` for testing, the commands above are described in [.cargo/config.toml](.cargo/config.toml).
-Zink is now moving forward without any grants or backups, if you like this dreaming project,
-please feel free to reach out, would be appreciated for any opportunities ^ ^
+## Special Thanks
-- ETH: `0xf0306047Fa598fe95502f466aeb49b68dd94365B`
-- SOL: `AZGXAerErfwVzJkiSR8moVPZxe1nEhvjdkvxQ7qR6Yst`
+- [MegaETH](https://github.com/megaeth-labs) for the funding and trust!
+- [revm](https://github.com/bluealloy/revm) for the EVM in rust!
## LICENSE
GPL-3.0-only
-[book]: https://docs.zink-lang.org/
+[book]: https://zink-lang.org/
[telegram-badge]: https://img.shields.io/endpoint?label=chat&style=flat&url=https%3A%2F%2Fmogyo.ro%2Fquart-apis%2Ftgmembercount%3Fchat_id%3Dzinklang
[telegram-group]: https://t.me/zinklang
[version-badge]: https://img.shields.io/crates/v/zinkc
diff --git a/codegen/src/asm.rs b/codegen/src/asm.rs
index 088d8e6a9..8ab558e43 100644
--- a/codegen/src/asm.rs
+++ b/codegen/src/asm.rs
@@ -3,7 +3,9 @@
//! TODO: refactor this module with Result as outputs. (issue-21)
use crate::{Buffer, Error, Result};
-use opcodes::{for_each_shanghai_operator, OpCode as _, ShangHai as OpCode};
+use opcodes::{for_each_cancun_operator, Cancun as OpCode, OpCode as _};
+
+const MAX_STACK_SIZE: u16 = 1024;
/// Low level assembler implementation for EVM.
#[derive(Default, Clone, Debug)]
@@ -18,8 +20,8 @@ pub struct Assembler {
gas: u128,
/// Memory pointer for byte offset.
pub mp: usize,
- /// Stack pointer, maximum 1024 items.
- pub sp: u8,
+ /// Stack pointer, maximum `MAX_STACK_SIZE` items.
+ pub sp: u16,
}
impl Assembler {
@@ -41,7 +43,7 @@ impl Assembler {
}
/// Increment stack pointer
- pub fn increment_sp(&mut self, items: u8) -> Result<()> {
+ pub fn increment_sp(&mut self, items: u16) -> Result<()> {
if items == 0 {
return Ok(());
}
@@ -51,18 +53,20 @@ impl Assembler {
self.sp,
self.sp + items
);
- self.sp += items;
+ self.sp = self
+ .sp
+ .checked_add(items)
+ .ok_or(Error::StackOverflow(self.sp, items))?;
- // TODO: fix this limitation: should be 1024. (#127)
- if self.sp > 254 {
- return Err(Error::StackOverflow(self.sp));
+ if self.sp > MAX_STACK_SIZE {
+ return Err(Error::StackOverflow(self.sp, items));
}
Ok(())
}
/// Decrement stack pointer
- pub fn decrement_sp(&mut self, items: u8) -> Result<()> {
+ pub fn decrement_sp(&mut self, items: u16) -> Result<()> {
if items == 0 {
return Ok(());
}
@@ -118,10 +122,10 @@ impl Assembler {
/// the stack usages.
pub fn emit_op(&mut self, opcode: OpCode) -> Result<()> {
tracing::trace!("emit opcode: {:?}", opcode);
- self.decrement_sp(opcode.stack_in() as u8)?;
+ self.decrement_sp(opcode.stack_in())?;
self.emit(opcode.into());
self.increment_gas(opcode.gas().into());
- self.increment_sp(opcode.stack_out() as u8)?;
+ self.increment_sp(opcode.stack_out())?;
Ok(())
}
@@ -141,5 +145,5 @@ macro_rules! impl_opcodes {
/// Basic instruction implementations
impl Assembler {
- for_each_shanghai_operator!(impl_opcodes);
+ for_each_cancun_operator!(impl_opcodes);
}
diff --git a/codegen/src/codegen/dispatcher.rs b/codegen/src/codegen/dispatcher.rs
index ceae36c57..628bfcd51 100644
--- a/codegen/src/codegen/dispatcher.rs
+++ b/codegen/src/codegen/dispatcher.rs
@@ -75,7 +75,7 @@ impl Dispatcher {
self.asm.increment_sp(1)?;
// Prepare the `PC` of the callee function.
- self.table.call(self.asm.pc_offset(), func);
+ self.table.call(self.asm.pc(), func);
if last {
self.asm._swap1()?;
diff --git a/codegen/src/codegen/function.rs b/codegen/src/codegen/function.rs
index 380983fab..3b5596adb 100644
--- a/codegen/src/codegen/function.rs
+++ b/codegen/src/codegen/function.rs
@@ -137,7 +137,7 @@ impl Function {
/// Finish code generation.
pub fn finish(self, jump_table: &mut JumpTable, pc: u16) -> Result {
let sp = self.masm.sp();
- if !self.is_main && self.abi.is_none() && self.masm.sp() != self.ty.results().len() as u8 {
+ if !self.is_main && self.abi.is_none() && self.masm.sp() != self.ty.results().len() as u16 {
return Err(Error::StackNotBalanced(sp));
}
diff --git a/codegen/src/control.rs b/codegen/src/control.rs
index 418c829f4..61d342431 100644
--- a/codegen/src/control.rs
+++ b/codegen/src/control.rs
@@ -38,7 +38,7 @@ pub struct ControlStackFrame {
result: BlockType,
/// Original stack pointer.
- pub original_sp: u8,
+ pub original_sp: u16,
}
impl ControlStackFrame {
@@ -46,7 +46,7 @@ impl ControlStackFrame {
pub fn new(
ty: ControlStackFrameType,
original_pc_offset: u16,
- original_sp: u8,
+ original_sp: u16,
result: BlockType,
) -> Self {
Self {
diff --git a/codegen/src/jump/mod.rs b/codegen/src/jump/mod.rs
index bf7a83f05..bf5c38026 100644
--- a/codegen/src/jump/mod.rs
+++ b/codegen/src/jump/mod.rs
@@ -16,8 +16,6 @@ mod target;
/// Represents the different types of jumps in the program.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Jump {
- /// Offset to the program counter.
- Offset(u16),
/// Jump to a specific label, which corresponds to the original program counter.
Label(u16),
/// Jump to a function identified by its index.
@@ -29,7 +27,6 @@ pub enum Jump {
impl Display for Jump {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
- Jump::Offset(offset) => write!(f, "Offset(0x{offset:x})"),
Jump::Label(offset) => write!(f, "Label(0x{offset:x})"),
Jump::Func(index) => write!(f, "Func({index})"),
Jump::ExtFunc(_) => write!(f, "ExtFunc"),
@@ -43,14 +40,9 @@ impl Jump {
matches!(self, Jump::Label { .. })
}
- /// Checks if the target is a fixed offset of the program counter.
- pub fn is_offset(&self) -> bool {
- matches!(self, Jump::Offset(_))
- }
-
/// Checks if the target is a function call.
pub fn is_call(&self) -> bool {
- !self.is_label() && !self.is_offset()
+ !self.is_label()
}
}
@@ -92,7 +84,6 @@ mod tests {
// Register jumps with known offsets and labels
table.register(0x10, Jump::Label(0x20)); // Jump to label at 0x20
- table.register(0x20, Jump::Offset(0x10)); // Offset jump forward by 0x10
table.register(0x30, Jump::Label(0x40)); // Jump to label at 0x40
assert_target_shift_vs_relocation(table)
@@ -105,37 +96,144 @@ mod tests {
// Simulate multiple functions calling _approve
table.register(0x10, Jump::Label(0x100)); // approve() -> _approve
table.register(0x20, Jump::Label(0x100)); // spend_allowance() -> _approve
- table.register(0x60, Jump::Offset(0x30)); // _approve implementation
assert_target_shift_vs_relocation(table)
}
#[test]
- fn test_nested_internal_calls() -> anyhow::Result<()> {
+ fn test_nested_function_calls() -> anyhow::Result<()> {
let mut table = JumpTable::default();
- // Simulate transfer_from calling both _spend_allowance and _transfer
- table.register(0x10, Jump::Label(0x100)); // transfer_from -> _spend_allowance
- table.register(0x100, Jump::Label(0x200)); // _spend_allowance -> _approve
- table.register(0x20, Jump::Label(0x300)); // transfer_from -> _transfer
- table.register(0x300, Jump::Label(0x400)); // _transfer -> _update
+ // Simulate ERC20's approve -> _approve call chain
+ table.register(0x100, Jump::Label(0x200)); // approve entry
+ table.register(0x110, Jump::Label(0x300)); // approve -> _approve
+ table.register(0x200, Jump::Label(0x400)); // _approve entry
- assert_target_shift_vs_relocation(table)
+ let mut buffer = smallvec![0; table.max_target() as usize];
+ table.relocate(&mut buffer)?;
+
+ // Check if all jumps use correct PUSH instructions
+ assert_eq!(buffer[0x100], 0x61); // PUSH2
+ assert_eq!(buffer[0x113], 0x61); // PUSH2
+ assert_eq!(buffer[0x206], 0x61); // PUSH2
+
+ Ok(())
}
- // FIXME: refactor offset handling (#280)
- #[ignore]
#[test]
- fn test_conditional_jumps() -> anyhow::Result<()> {
+ fn test_label_call_interaction() -> anyhow::Result<()> {
+ init_tracing();
let mut table = JumpTable::default();
- // Simulate the conditional logic in _spend_allowance
- table.register(0x10, Jump::Label(0x100)); // Entry point
- table.register(0x20, Jump::Label(0x200)); // If branch
- table.register(0x30, Jump::Label(0x300)); // Else branch
- table.register(0x100, Jump::Offset(0x50)); // Condition check
- table.register(0x200, Jump::Label(0x400)); // Call to _approve
+ table.func.insert(1, 0x317);
+ table.label(0x10, 0x12);
+ table.call(0x11, 1);
- assert_target_shift_vs_relocation(table)
+ let mut buffer = smallvec![0; table.max_target() as usize];
+ table.relocate(&mut buffer)?;
+
+ assert_eq!(buffer[0x11], 0x17, "{buffer:?}");
+ assert_eq!(buffer[0x14], 0x03, "{buffer:?}");
+ assert_eq!(buffer[0x15], 0x1c, "{buffer:?}");
+ Ok(())
+ }
+
+ #[test]
+ fn test_large_target_offset_calculation() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+
+ // Register a jump with target < 0xff
+ table.register(0x10, Jump::Label(0x80));
+
+ // Register a jump with target > 0xff
+ table.register(0x20, Jump::Label(0x100));
+
+ // Register a jump with target > 0xfff
+ table.register(0x30, Jump::Label(0x1000));
+
+ let mut buffer = smallvec![0; table.max_target() as usize];
+ table.relocate(&mut buffer)?;
+
+ // Check if offsets are correctly calculated
+ // For target 0x80: PUSH1 (1 byte) + target (1 byte)
+ // For target 0x100: PUSH2 (1 byte) + target (2 bytes)
+ // For target 0x1000: PUSH2 (1 byte) + target (2 bytes)
+ assert_eq!(buffer[0x11], 0x88); // Small target
+ assert_eq!(buffer[0x23], 0x01); // First byte of large target
+ assert_eq!(buffer[0x24], 0x08); // Second byte of large target
+ assert_eq!(buffer[0x36], 0x10); // First byte of large target
+ assert_eq!(buffer[0x37], 0x08); // Second byte of large target
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_sequential_large_jumps() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+
+ // Register multiple sequential jumps with increasing targets
+ // This mirrors the ERC20 pattern where we have many functions
+ for i in 0..20 {
+ let target = 0x100 + (i * 0x20);
+ table.register(0x10 + i, Jump::Label(target));
+ }
+
+ let mut buffer = smallvec![0; table.max_target() as usize];
+ table.relocate(&mut buffer)?;
+
+ // Check first jump (should use PUSH2)
+ assert_eq!(buffer[0x10], 0x61); // PUSH2
+ assert_eq!(buffer[0x11], 0x01); // First byte
+ assert_eq!(buffer[0x12], 0x3c); // Second byte
+ assert_eq!(0x013c, 0x100 + 20 * 3);
+
+ // Check last jump (should still use PUSH2 but with adjusted offset)
+ let last_idx = 0x10 + 19 + 19 * 3;
+ assert_eq!(buffer[last_idx], 0x61); // PUSH2
+ assert_eq!(buffer[last_idx + 1], 0x03); // First byte should be larger
+ assert_eq!(buffer[last_idx + 2], 0x9c); // Second byte accounts for all previous jumps
+ assert_eq!(0x039c, 0x100 + 0x20 * 19 + 20 * 3);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_dispatcher_jump_targets() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+ let selectors = 5;
+
+ // Register jumps for each selector check
+ for i in 0..selectors {
+ let i = i as u16;
+ let check_pc = 0x10 + i * 0x20;
+ let target_pc = 0x100 + i * 0x40;
+
+ // Register both the comparison jump and function jump
+ table.register(check_pc, Jump::Label(check_pc + 0x10));
+ table.register(check_pc + 0x10, Jump::Label(target_pc));
+ }
+
+ let mut buffer = smallvec![0; table.max_target() as usize];
+ table.relocate(&mut buffer)?;
+
+ // Verify each selector's jump chain
+ let mut total_offset = 0;
+ for i in 0..selectors {
+ let check_pc = 0x10 + i * 0x20 + total_offset;
+ let check_pc_offset = if check_pc + 0x10 > 0xff { 3 } else { 2 };
+
+ let func_pc = check_pc + 0x10 + check_pc_offset;
+
+ let check_jump = buffer[check_pc];
+ let func_jump = buffer[func_pc];
+
+ assert_eq!(check_jump, if func_pc > 0xff { 0x61 } else { 0x60 });
+ assert_eq!(func_jump, 0x61);
+
+ // Update total offset for next iteration
+ total_offset += check_pc_offset + 3;
+ }
+
+ Ok(())
}
}
diff --git a/codegen/src/jump/relocate.rs b/codegen/src/jump/relocate.rs
index 9e2afff3f..bdc92148a 100644
--- a/codegen/src/jump/relocate.rs
+++ b/codegen/src/jump/relocate.rs
@@ -36,12 +36,7 @@ impl JumpTable {
pc,
self.code.offset()
);
- let mut target = self.target(&jump)?;
-
- // If the jump is an offset, adjust the target accordingly.
- if jump.is_offset() {
- self.update_offset_target(pc, &mut target)?;
- }
+ let target = self.target(&jump)?;
tracing::debug!(
"relocate: pc=0x{:x}, jump={:?}, target=0x{:x}",
@@ -59,31 +54,6 @@ impl JumpTable {
buffer.extend_from_slice(&self.code.finish());
Ok(())
}
-
- /// Relocate the target of an offset jump.
- ///
- /// This function adjusts the target program counter for jumps that are
- /// represented as offsets. It modifies the target based on the original
- /// program counter and checks for specific conditions that may require
- /// further adjustments.
- fn update_offset_target(&self, pc: u16, target: &mut u16) -> Result<()> {
- // NOTE: If the target is offset, the return data is the offset instead of the PC.
- *target += pc;
-
- // Check if the original program counter of the offset is greater than 0xff.
- if *target > 0xff {
- *target += 1;
- }
-
- // Check if the offset of the embedded call is greater than 0xff.
- if let Some((_, next_target)) = self.jump.first_key_value() {
- if next_target.is_call() && self.target(next_target)? > 0xff {
- *target += 1
- }
- }
-
- Ok(())
- }
}
/// Relocate program counter to buffer.
diff --git a/codegen/src/jump/table.rs b/codegen/src/jump/table.rs
index f52680e26..6c466527c 100644
--- a/codegen/src/jump/table.rs
+++ b/codegen/src/jump/table.rs
@@ -53,11 +53,6 @@ impl JumpTable {
self.jump.insert(pc, Jump::Label(label));
}
- /// Registers a label at a specific program counter offset.
- pub fn offset(&mut self, pc: u16, offset: u16) {
- self.jump.insert(pc, Jump::Offset(offset));
- }
-
/// Merges another jump table into this one.
///
/// This function updates the program counters of the target jump table and
@@ -108,31 +103,11 @@ fn test_multiple_jumps_same_target() -> anyhow::Result<()> {
// Setup multiple jumps to same target
table.register(0x10, Jump::Label(0x100));
table.register(0x20, Jump::Label(0x100));
- table.register(0x30, Jump::Offset(0x10));
-
table.shift_targets()?;
// Verify each jump's final target
- assert_eq!(table.target(table.jump.get(&0x10).unwrap())?, 0x108);
- assert_eq!(table.target(table.jump.get(&0x20).unwrap())?, 0x108);
- assert_eq!(table.target(table.jump.get(&0x30).unwrap())?, 0x10);
- Ok(())
-}
-
-#[test]
-fn test_multiple_jumps_with_backwards() -> anyhow::Result<()> {
- let mut table = JumpTable::default();
-
- // Simulate multiple functions calling _approve
- table.register(0x10, Jump::Label(0x100)); // approve() -> _approve
- table.register(0x20, Jump::Label(0x100)); // spend_allowance() -> _approve
- table.register(0x100, Jump::Offset(0x30)); // _approve implementation
-
- table.shift_targets()?;
-
assert_eq!(table.target(table.jump.get(&0x10).unwrap())?, 0x106);
assert_eq!(table.target(table.jump.get(&0x20).unwrap())?, 0x106);
- assert_eq!(table.target(table.jump.get(&0x100).unwrap())?, 0x30);
Ok(())
}
@@ -154,25 +129,6 @@ fn test_nested_jumps() -> anyhow::Result<()> {
Ok(())
}
-#[test]
-fn test_offset_label_interaction() -> anyhow::Result<()> {
- let mut table = JumpTable::default();
-
- // Create offset and label jumps targeting same area
- table.register(0x10, Jump::Offset(0x50)); // Offset jump forward
- table.register(0x20, Jump::Label(0x60)); // Label jump to area after offset
- table.register(0x30, Jump::Label(0x50)); // Label jump to offset target
-
- table.shift_targets()?;
-
- // Verify jumps are processed correctly
- assert_eq!(table.target(table.jump.get(&0x10).unwrap())?, 0x50);
- assert_eq!(table.target(table.jump.get(&0x20).unwrap())?, 0x66);
- assert_eq!(table.target(table.jump.get(&0x30).unwrap())?, 0x56);
-
- Ok(())
-}
-
#[test]
fn test_sequential_jumps() -> anyhow::Result<()> {
let mut table = JumpTable::default();
@@ -204,3 +160,65 @@ fn test_jump_backwards() -> anyhow::Result<()> {
assert_eq!(table.target(table.jump.get(&0x30).unwrap())?, 0x22);
Ok(())
}
+
+#[test]
+fn test_jump_table_state_consistency() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+
+ // Register a sequence of jumps that mirror ERC20's pattern
+ table.register(0x10, Jump::Label(0x100)); // First jump
+ table.register(0x20, Jump::Label(0x100)); // Second jump to same target
+
+ // Record state before and after each operation
+ let initial_state = table.jump.clone();
+ table.shift_targets()?;
+ let shifted_state = table.jump.clone();
+
+ // Verify jump table consistency
+ assert_eq!(table.jump.len(), initial_state.len());
+ assert!(shifted_state.values().all(|j| matches!(j, Jump::Label(_))));
+ Ok(())
+}
+
+#[test]
+fn test_jump_target_ordering() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+
+ // Register jumps in reverse order
+ table.register(0x30, Jump::Label(0x100));
+ table.register(0x20, Jump::Label(0x100));
+ table.register(0x10, Jump::Label(0x100));
+
+ // Track all target shifts
+ let mut shifts = Vec::new();
+ let cloned = table.clone();
+ let original_targets: Vec<_> = cloned.jump.values().collect();
+
+ table.shift_targets()?;
+
+ // Verify target consistency
+ for (orig, shifted) in original_targets.iter().zip(table.jump.values()) {
+ shifts.push((orig, shifted));
+ }
+
+ Ok(())
+}
+
+#[test]
+fn test_mixed_jump_types() -> anyhow::Result<()> {
+ let mut table = JumpTable::default();
+
+ // Mix function calls and labels like in ERC20
+ table.func.insert(1, 0x100);
+ table.call(0x10, 1); // Function call
+ table.register(0x20, Jump::Label(0x100)); // Label jump to same target
+
+ let before_shift = table.jump.clone();
+ table.shift_targets()?;
+ let after_shift = table.jump.clone();
+
+ // Compare states
+ assert_eq!(before_shift.len(), after_shift.len());
+
+ Ok(())
+}
diff --git a/codegen/src/jump/target.rs b/codegen/src/jump/target.rs
index 7770d76a3..f810f490d 100644
--- a/codegen/src/jump/target.rs
+++ b/codegen/src/jump/target.rs
@@ -15,7 +15,6 @@ impl JumpTable {
/// (offset, label, function, or external function).
pub fn target(&self, jump: &Jump) -> Result {
match jump {
- Jump::Offset(offset) => Ok(*offset),
Jump::Label(label) => Ok(*label),
Jump::Func(func) => Ok(*self.func.get(func).ok_or(Error::FuncNotFound(*func))?),
Jump::ExtFunc(ext) => Ok(self.code.offset_of(ext).ok_or(Error::ExtFuncNotFound)?),
@@ -28,26 +27,40 @@ impl JumpTable {
/// counter and the target, adjusting for any offsets.
pub fn shift_targets(&mut self) -> Result<()> {
let mut total_offset = 0;
- for (original_pc, jump) in self.jump.clone().iter() {
- tracing::debug!("shift targets for {jump} <- (0x{original_pc:x})");
- let pc = original_pc + total_offset;
+ let mut target_sizes = Vec::new();
+ let jumps = self.jump.clone();
- // Determine the size of the target PC based on its value.
- let target = self.target(jump)? + total_offset;
+ // First pass: calculate all target sizes and accumulate offsets
+ for (original_pc, jump) in jumps.iter() {
+ let pc = original_pc + total_offset;
+ let raw_target = self.target(jump)?;
- /* if jump.is_offset() {
- target += original_pc;
- } */
+ // Calculate the absolute target including future offsets
+ let target = if raw_target > *original_pc {
+ raw_target + total_offset
+ } else {
+ raw_target
+ };
- let offset = if target > 0xff {
- 3 // Requires 3 bytes for processing the JUMP target offset
+ // Calculate instruction size based on absolute target value
+ let instr_size = if target > 0xff {
+ 3 // PUSH2 + 2 bytes
} else {
- 2 // Requires 2 bytes
+ 2 // PUSH1 + 1 byte
};
- self.shift_target(pc, offset)?;
- total_offset += offset;
+ target_sizes.push((pc, instr_size));
+ total_offset += instr_size;
}
+
+ // Second pass: apply shifts with accumulated offsets
+ total_offset = 0;
+ for (pc, size) in target_sizes {
+ tracing::debug!("shift target at pc=0x{pc:x} with size={size}");
+ self.shift_target(pc, size)?;
+ total_offset += size;
+ }
+
Ok(())
}
@@ -56,22 +69,19 @@ impl JumpTable {
/// This function handles the shifting of the code section, label targets, and
/// function targets.
pub fn shift_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
+ // First shift the code section
self.code.shift(offset);
+
+ // Only shift targets that are after ptr
self.shift_label_target(ptr, offset)?;
self.shift_func_target(ptr, offset)
}
/// Shifts the program counter for functions.
pub fn shift_func_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
- if self.func.is_empty() {
- tracing::trace!("No functions to shift.");
- return Ok(());
- }
-
self.func.iter_mut().try_for_each(|(index, target)| {
- let next_target = *target + offset;
-
if *target > ptr {
+ let next_target = *target + offset;
tracing::trace!(
"shift Func({index}) target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}",
next_target
@@ -86,26 +96,22 @@ impl JumpTable {
/// Shifts the program counter for labels.
pub fn shift_label_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
- if self.jump.is_empty() {
- tracing::trace!("No labels to shift.");
- return Ok(());
- }
+ for (_, jump) in self.jump.iter_mut() {
+ let Jump::Label(target) = jump else {
+ continue;
+ };
- self.jump.iter_mut().try_for_each(|(pc, jump)| {
- if let Jump::Label(target) = jump {
+ // Only shift targets that come after ptr AND
+ // only if the jump instruction itself comes after ptr
+ if *target > ptr {
let next_target = *target + offset;
-
- if *target > ptr {
- tracing::trace!(
- "shift Label(0x{pc:x}) target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}",
- next_target,
- );
-
- *target = next_target;
- }
+ tracing::trace!(
+ "shift Label target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}",
+ next_target,
+ );
+ *target = next_target;
}
-
- Ok(())
- })
+ }
+ Ok(())
}
}
diff --git a/codegen/src/masm/cmp.rs b/codegen/src/masm/cmp.rs
index a2a148049..a2357c0fb 100644
--- a/codegen/src/masm/cmp.rs
+++ b/codegen/src/masm/cmp.rs
@@ -1,7 +1,7 @@
// Comparison Instructions
use crate::{MacroAssembler, Result};
-use opcodes::ShangHai as OpCode;
+use opcodes::Cancun as OpCode;
impl MacroAssembler {
/// Greater than or equal comparison.
diff --git a/codegen/src/masm/mod.rs b/codegen/src/masm/mod.rs
index b8a1577e8..0978a3ef1 100644
--- a/codegen/src/masm/mod.rs
+++ b/codegen/src/masm/mod.rs
@@ -80,10 +80,19 @@ impl MacroAssembler {
}
/// Get the current program counter offset.
- pub fn pc_offset(&self) -> u16 {
+ pub fn pc(&self) -> u16 {
self.asm.buffer().len() as u16
}
+ /// Get the current program counter offset.
+ pub fn pc_offset(&self) -> u16 {
+ if self.pc() > 0xff {
+ 3
+ } else {
+ 2
+ }
+ }
+
/// Place n bytes on stack.
pub fn push(&mut self, bytes: &[u8]) -> Result<()> {
tracing::trace!("push bytes: 0x{}", hex::encode(bytes));
@@ -130,7 +139,7 @@ impl MacroAssembler {
30 => self.asm._push30(),
31 => self.asm._push31(),
32 => self.asm._push32(),
- _ => return Err(Error::StackIndexOutOfRange(len as u8)),
+ _ => return Err(Error::StackIndexOutOfRange(len as u16)),
}?;
self.asm.emitn(bytes);
@@ -146,12 +155,12 @@ impl MacroAssembler {
}
/// Get the stack pointer.
- pub fn sp(&self) -> u8 {
+ pub fn sp(&self) -> u16 {
self.asm.sp
}
/// Swap memory by target index.
- pub fn swap(&mut self, index: u8) -> Result<()> {
+ pub fn swap(&mut self, index: u16) -> Result<()> {
tracing::trace!("swap index: {}", index);
match index {
0 => Ok(()),
@@ -176,7 +185,7 @@ impl MacroAssembler {
}
/// Duplicate stack item by target index.
- pub fn dup(&mut self, index: u8) -> Result<()> {
+ pub fn dup(&mut self, index: u16) -> Result<()> {
tracing::trace!("dup index: {}", index);
match index {
0 => Ok(()),
@@ -203,7 +212,7 @@ impl MacroAssembler {
/// Shift the program counter to the bottom or the top of the
/// parameters. This is used by the callee function for jumping
/// back to the caller function.
- pub fn shift_stack(&mut self, count: u8, from_top: bool) -> Result<()> {
+ pub fn shift_stack(&mut self, count: u16, from_top: bool) -> Result<()> {
let mut swaps = 0;
if from_top {
diff --git a/codegen/src/masm/ret.rs b/codegen/src/masm/ret.rs
index 84ef44ab2..ec5b65a04 100644
--- a/codegen/src/masm/ret.rs
+++ b/codegen/src/masm/ret.rs
@@ -31,7 +31,7 @@ impl MacroAssembler {
/// Handle the return of a call.
pub fn call_return(&mut self, results: &[ValType]) -> Result<()> {
- let len = results.len() as u8;
+ let len = results.len() as u16;
tracing::trace!("cleaning frame stack, target: {}", len + 1);
// TODO: clean stacks via the count of nested control stacks.
diff --git a/codegen/src/result.rs b/codegen/src/result.rs
index 1889e1bbd..12816f3b4 100644
--- a/codegen/src/result.rs
+++ b/codegen/src/result.rs
@@ -86,16 +86,16 @@ pub enum Error {
SelectorNotFound,
/// Failed to index data on stack.
#[error("Stack index is out of range {0}, max is 255 (0x400)")]
- StackIndexOutOfRange(u8),
+ StackIndexOutOfRange(u16),
/// Failed to increment stack pointer.
- #[error("Stack overflow, max is 1024 stack items, got {0}")]
- StackOverflow(u8),
+ #[error("Stack overflow, max is 1024 stack items, but add {1} to {0}")]
+ StackOverflow(u16, u16),
/// Failed to decrement stack pointer.
#[error("Stack underflow, current stack items {0}, expect at least {1}")]
- StackUnderflow(u8, u8),
+ StackUnderflow(u16, u16),
/// Failed to pop stack.
#[error("Stack not balanced, current stack items {0}")]
- StackNotBalanced(u8),
+ StackNotBalanced(u16),
/// Failed to queue host functions.
#[error("Unsupported host function {0:?}")]
UnsupportedHostFunc(crate::wasm::HostFunc),
diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs
index c28e99e26..cdf9066af 100644
--- a/codegen/src/visitor/call.rs
+++ b/codegen/src/visitor/call.rs
@@ -10,7 +10,7 @@ use crate::{
Error, Function, Result,
};
use anyhow::anyhow;
-use opcodes::ShangHai as OpCode;
+use opcodes::Cancun as OpCode;
impl Function {
/// The call indirect instruction calls a function indirectly
@@ -67,26 +67,10 @@ impl Function {
let reserved = self.env.slots.get(&index).unwrap_or(&0);
let (params, results) = self.env.funcs.get(&index).unwrap_or(&(0, 0));
- // Prepare the stack structure for the function call.
- // The stack will be structured as follows:
- // [ ..,
- // , // The current program counter
- // params[SWAP], // Swap the parameters for the call
- // params[PUSH, SLOT, MSTORE], // Push parameters to the stack
- // {(PUSH, PC), JUMP, JUMPDEST} // Prepare for the jump to the callee
- // ]
- let base_offset = 5 + ((params + reserved) * 0x20).saturating_sub(0xff) / 0x20;
-
- // Move the PC before the parameters in the stack.
- self.table.offset(
- self.masm.pc_offset(),
- base_offset as u16 + 4 * (*params as u16),
- );
+ // TODO This is a temporary fix to avoid stack underflow.
+ // We need to find a more elegant solution for this.
self.masm.increment_sp(1)?;
- // Adjust the stack to place the PC before the parameters.
- self.masm.shift_stack(*params as u8, true)?;
-
// Store parameters in memory and register the call index in the jump table.
for i in (0..*params).rev() {
tracing::trace!("Storing local at {} for function {index}", i + reserved);
@@ -94,15 +78,18 @@ impl Function {
self.masm._mstore()?;
}
- // Register the call index in the jump table.
- self.table.call(self.masm.pc_offset(), index);
+ // Register the label to jump back.
+ let return_pc = self.masm.pc() + 2;
+ self.table.label(self.masm.pc(), return_pc);
+ self.masm._jumpdest()?; // TODO: support same pc different label
- // Jump to the callee function.
+ // Register the call index in the jump table.
+ self.table.call(self.masm.pc(), index); // [PUSHN, CALL_PC]
self.masm._jump()?;
- self.masm._jumpdest()?;
// Adjust the stack pointer for the results.
- self.masm.increment_sp(*results as u8)?;
+ self.masm._jumpdest()?;
+ self.masm.increment_sp(*results as u16)?;
Ok(())
}
diff --git a/codegen/src/visitor/control.rs b/codegen/src/visitor/control.rs
index f151f1533..f278b401b 100644
--- a/codegen/src/visitor/control.rs
+++ b/codegen/src/visitor/control.rs
@@ -15,7 +15,7 @@ impl Function {
// push an `If` frame to the control stack
let frame = ControlStackFrame::new(
ControlStackFrameType::If(false),
- self.masm.pc_offset(),
+ self.masm.pc(),
self.masm.sp(),
blockty,
);
@@ -35,7 +35,7 @@ impl Function {
pub fn _block(&mut self, blockty: BlockType) -> Result<()> {
let frame = ControlStackFrame::new(
ControlStackFrameType::Block,
- self.masm.pc_offset(),
+ self.masm.pc(),
self.masm.sp(),
blockty,
);
@@ -50,7 +50,7 @@ impl Function {
pub fn _loop(&mut self, blockty: BlockType) -> Result<()> {
let frame = ControlStackFrame::new(
ControlStackFrameType::Loop,
- self.masm.pc_offset(),
+ self.masm.pc(),
self.masm.sp(),
blockty,
);
@@ -68,7 +68,7 @@ impl Function {
// push an `Else` frame to the control stack.
let frame = ControlStackFrame::new(
ControlStackFrameType::Else,
- self.masm.pc_offset(),
+ self.masm.pc(),
self.masm.sp(),
last_frame.result(),
);
@@ -78,7 +78,7 @@ impl Function {
// mark else as the jump destination of the if block.
self.table
- .label(last_frame.original_pc_offset, self.masm.pc_offset());
+ .label(last_frame.original_pc_offset, self.masm.pc());
self.masm._jumpdest()?;
Ok(())
@@ -92,7 +92,7 @@ impl Function {
tracing::trace!("select");
self.masm._iszero()?;
self.masm.increment_sp(1)?;
- self.table.offset(self.masm.pc_offset(), 4);
+ self.table.label(self.masm.pc(), self.masm.pc() + 2);
self.masm._jumpi()?;
self.masm._drop()?;
self.masm._jumpdest()?;
@@ -113,10 +113,17 @@ impl Function {
/// Conditional branch to a given label in an enclosing construct.
pub fn _br_if(&mut self, depth: u32) -> Result<()> {
let label = self.control.label_from_depth(depth)?;
- self.table.label(self.masm.pc_offset(), label);
- self.masm.asm.increment_sp(1)?;
+
+ // Register the jump target for breaking out
+ self.table.label(self.masm.pc(), label);
+ self.masm.increment_sp(1)?;
+
+ // JUMPI will check condition and jump if true
self.masm._jumpi()?;
+ // If we don't jump, we continue in the current frame
+ // No need for explicit ISZERO since JUMPI handles the condition
+
Ok(())
}
@@ -172,13 +179,12 @@ impl Function {
ControlStackFrameType::Block => self.masm._jumpdest(),
ControlStackFrameType::Loop => Ok(()),
_ => {
- self.table
- .label(frame.original_pc_offset, self.masm.pc_offset());
+ self.table.label(frame.original_pc_offset, self.masm.pc());
// TODO: Check the stack output and make decisions
// how to handle the results.
- // Emit JUMPDEST after at the end of the control flow.
+ // Emit JUMPDEST at the end of the control flow.
self.masm._jumpdest()
}
}
diff --git a/codegen/src/visitor/mod.rs b/codegen/src/visitor/mod.rs
index 6b95a1de5..41a3cf06f 100644
--- a/codegen/src/visitor/mod.rs
+++ b/codegen/src/visitor/mod.rs
@@ -188,7 +188,7 @@ macro_rules! map_wasm_operators {
};
}
-impl<'a> VisitOperator<'a> for Function {
+impl VisitOperator<'_> for Function {
type Output = Result<()>;
for_each_operator!(impl_visit_operator);
diff --git a/codegen/src/wasm/func.rs b/codegen/src/wasm/func.rs
index 12424e08b..131a844f3 100644
--- a/codegen/src/wasm/func.rs
+++ b/codegen/src/wasm/func.rs
@@ -81,7 +81,7 @@ impl<'f> Deref for Functions<'f> {
}
}
-impl<'f> DerefMut for Functions<'f> {
+impl DerefMut for Functions<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
diff --git a/codegen/src/wasm/host.rs b/codegen/src/wasm/host.rs
index 2fce8a2d4..454d7b355 100644
--- a/codegen/src/wasm/host.rs
+++ b/codegen/src/wasm/host.rs
@@ -3,7 +3,7 @@
use crate::{Error, Result};
use anyhow::anyhow;
use core::str::FromStr;
-use opcodes::{OpCode as _, ShangHai as OpCode};
+use opcodes::{Cancun as OpCode, OpCode as _};
/// EVM built-in function.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
@@ -16,8 +16,6 @@ pub enum HostFunc {
//
/// Emit ABI to the compiler.
EmitABI,
- /// check equal of two addresses
- AddressEq,
/// Push u256 max to stack
U256MAX,
/// Revert messages with length of slots
@@ -50,30 +48,42 @@ impl TryFrom<(&str, &str)> for HostFunc {
fn try_from(import: (&str, &str)) -> Result {
let (module, name) = import;
match import {
- ("asm", name) => {
- if name.starts_with("sload") {
- Ok(Self::Evm(OpCode::SLOAD))
- } else if name.starts_with("revert") {
- let count = name.trim_start_matches("revert");
-
- // TODO: use anyhow instead of Error
- Ok(Self::Revert(count.parse().map_err(|e| anyhow!("{e}"))?))
- } else {
- Ok(Self::NoOp)
- }
- }
+ ("zinkc", name) => match name {
+ "emit_abi" => Ok(Self::EmitABI),
+ "u256_add" => Ok(Self::Evm(OpCode::ADD)),
+ "u256_sub" => Ok(Self::Evm(OpCode::SUB)),
+ "u256_lt" => Ok(Self::Evm(OpCode::LT)),
+ "u256_max" => Ok(Self::U256MAX),
+ "u256_addmod" => Ok(Self::Evm(OpCode::ADDMOD)),
+ "u256_mulmod" => Ok(Self::Evm(OpCode::MULMOD)),
+ "label_reserve_mem_32" => Ok(Self::Label(CompilerLabel::ReserveMemory32)),
+ "label_reserve_mem_64" => Ok(Self::Label(CompilerLabel::ReserveMemory64)),
+ _ => 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),
- ("zinkc", "address_eq") => Ok(Self::Evm(OpCode::EQ)),
- ("zinkc", "u256_add") => Ok(Self::Evm(OpCode::ADD)),
- ("zinkc", "u256_sub") => Ok(Self::Evm(OpCode::SUB)),
- ("zinkc", "u256_lt") => Ok(Self::Evm(OpCode::LT)),
- ("zinkc", "u256_max") => Ok(Self::U256MAX),
- ("zinkc", "label_reserve_mem_32") => Ok(Self::Label(CompilerLabel::ReserveMemory32)),
- ("zinkc", "label_reserve_mem_64") => Ok(Self::Label(CompilerLabel::ReserveMemory64)),
+ ("asm", name) => match name {
+ n if n.starts_with("sload") => Ok(Self::Evm(OpCode::SLOAD)),
+ n if n.starts_with("tload") => Ok(Self::Evm(OpCode::TLOAD)),
+ n if n.starts_with("revert") => {
+ let count = n.trim_start_matches("revert");
+ Ok(Self::Revert(count.parse().map_err(|e| anyhow!("{e}"))?))
+ }
+ n if n.starts_with("mulmod") => Ok(Self::Evm(OpCode::MULMOD)),
+ n if n.starts_with("addmod") => Ok(Self::Evm(OpCode::ADDMOD)),
+ _ => Ok(Self::NoOp),
+ },
+ ("bytes", instr) => match instr {
+ push if push.starts_with("push_bytes") => Ok(Self::NoOp),
+ sload if sload.starts_with("sload_bytes") => Ok(Self::Evm(OpCode::SLOAD)),
+ eq if eq.ends_with("_eq") => Ok(Self::Evm(OpCode::EQ)),
+ _ => {
+ tracing::warn!("Failed to load host function: {import:?} from module bytes");
+ Err(Error::HostFuncNotFound(module.into(), name.into()))
+ }
+ },
_ => {
tracing::warn!("Failed to load host function: {:?}", import);
Err(Error::HostFuncNotFound(module.into(), name.into()))
@@ -88,3 +98,22 @@ pub enum CompilerLabel {
ReserveMemory32,
ReserveMemory64,
}
+
+#[cfg(test)]
+mod tests {
+ use anyhow::Ok;
+
+ use super::*;
+
+ #[test]
+ fn test_addmod_mulmod_host_functions() -> anyhow::Result<()> {
+ let addmod_func = HostFunc::try_from(("zinkc", "u256_addmod"))?;
+
+ assert_eq!(addmod_func, HostFunc::Evm(OpCode::ADDMOD));
+
+ // Test MULMOD host function conversion
+ let mulmod_func = HostFunc::try_from(("zinkc", "u256_mulmod"));
+ assert!(mulmod_func.is_ok());
+ Ok(())
+ }
+}
diff --git a/compiler/filetests/wat/br_if/as_block_last.wat b/compiler/filetests/wat/br_if/as_block_last.wat
index 5d5130a8e..e6163dabc 100644
--- a/compiler/filetests/wat/br_if/as_block_last.wat
+++ b/compiler/filetests/wat/br_if/as_block_last.wat
@@ -6,7 +6,7 @@
)
(func $internal (param i32)
(block
- (call $dummy)
+ (call $dummy)
(call $dummy)
(br_if 0 (local.get 0))
)
diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs
index 67cc50212..f95c04f0b 100644
--- a/compiler/src/compiler.rs
+++ b/compiler/src/compiler.rs
@@ -57,6 +57,7 @@ impl Compiler {
..
} = self;
+ tracing::debug!("code length: {}", buffer.len());
Ok(Artifact {
abi,
config,
diff --git a/docs/README.md b/docs/README.md
index 0da08496d..d06d9d883 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,47 +1,10 @@
# The Zink Book
+This is the book for the Zink Language, you can read it online at [docs.zink-lang.org](https://zink-lang.org/).
-## Validation
-
-e.g. How Zink Compiler helps you writing your EVM smart contracts ;)
-
-
-#### `0x35` - CALLDATALOAD
-
-1. validate the function signatures
-2. validate the stack usages
-
-
-## Optimizations
-
-
-#### StackCompressor
-
-The max limit of the defined local variables is 16 due to there is a hard limit
-of 16 slots for reaching down the expression stack of EVM.
-
-
-## Function Calls
-
-### Calling Convention
-
-There we two ways to handle the calling convention, for storing PC
-
-1. Store the PC
-
-few arguments -> store the PC on stack.
-lots of arguments -> store the PC in reserved memory.
-
-
-2. Retrieve the PC
-
-stack -> swap the parameters and the PC
-memory -> read from reserved memory
-
-
-3. Jump back to the caller
-
-stack -> swap the results and the PC
- -> dup the PC and pop in caller function
-memory -> load PC from memory
+## Contributing
+```
+cargo install mdbook
+mdbook serve
+```
diff --git a/docs/book.toml b/docs/book.toml
index d00cff8dc..a1080729b 100644
--- a/docs/book.toml
+++ b/docs/book.toml
@@ -1,5 +1,5 @@
[book]
-title = "The Zink Project"
+title = "The Zink Language"
authors = ["clearloop"]
multilingual = false
src = "."
@@ -9,7 +9,7 @@ edition = "2021"
[output.html]
cname = "docs.zink-lang.org"
-git-repository-url = "https://github.com/clearloop/zink"
+git-repository-url = "https://github.com/zink-lang/zink"
git-repository-icon = "fa-github"
default-theme = "dark"
preferred-dark-theme = "navy"
@@ -17,11 +17,11 @@ curly-quotes = true
mathjax-support = false
# If editable
-# edit-url-template = "https://github.com/clearloop/zink/edit/main/docs/{path}"
+edit-url-template = "https://github.com/zink-lang/zink/edit/main/docs/{path}"
# If enable folding chapters
-# [output.html.fold]
-# enable = true
+[output.html.fold]
+enable = true
# level = 1
[output.html.playground]
diff --git a/docs/introduction.md b/docs/introduction.md
index 383ea68da..45e7d24c0 100644
--- a/docs/introduction.md
+++ b/docs/introduction.md
@@ -11,6 +11,7 @@ contracts on Ethereum.
This guide is intended to serve a number of purposes and within you'll find:
+- [The Bounty issues][bounty]
- [The rustdocs of the zink project][rustdocs]
- [The design of the zink compiler][compiler]
- [The rust guide of zink projects][styles]
@@ -23,3 +24,4 @@ This guide is intended to serve a number of purposes and within you'll find:
[zinkc]: https://github.com/clearloop/zink/tree/main/compiler
[rustdocs]: https://docs.zink-lang.org/rustdocs
[source]: https://github.com/clearloop/zink/tree/main/docs
+[bounty]: https://zink-lang.org/budgets
diff --git a/evm/abi/src/arg.rs b/evm/abi/src/arg.rs
index d7700166e..01f9617e8 100644
--- a/evm/abi/src/arg.rs
+++ b/evm/abi/src/arg.rs
@@ -67,8 +67,9 @@ impl From<&str> for Param {
"U256" | "u256" | "uint256" => Param::UInt256,
"bool" => Param::Bool,
"address" | "Address" => Param::Address,
- "Bytes" | "Vec" => Param::Bytes,
"String" | "String32" => Param::String,
+ "Vec" => Param::Bytes,
+ bytes if bytes.starts_with("Bytes") => Param::Bytes,
_ => Param::Unknown(s.to_string()),
}
}
diff --git a/evm/opcodes/src/cancun.rs b/evm/opcodes/src/cancun.rs
new file mode 100644
index 000000000..43a078cf6
--- /dev/null
+++ b/evm/opcodes/src/cancun.rs
@@ -0,0 +1,156 @@
+//! Instructions for Cancun.
+
+use crate::{opcodes, Group, OpCode, Upgrade};
+
+opcodes! {
+ Cancun,
+ (0x00, STOP, 0, 0, 0, "Halts execution.", Frontier, StopArithmetic),
+ (0x01, ADD, 3, 2, 1, "Addition operation.", Frontier, StopArithmetic),
+ (0x02, MUL, 5, 2, 1, "Multiplication operation.", Frontier, StopArithmetic),
+ (0x03, SUB, 3, 2, 1, "Subtraction operation.", Frontier, StopArithmetic),
+ (0x04, DIV, 5, 2, 1, "Integer division operation.", Frontier, StopArithmetic),
+ (0x05, SDIV, 5, 2, 1, "Signed integer division operation (truncated).", Frontier, StopArithmetic),
+ (0x06, MOD, 5, 2, 1, "Modulo remainder operation.", Frontier, StopArithmetic),
+ (0x07, SMOD, 5, 2, 1, "Signed modulo remainder operation.", Frontier, StopArithmetic),
+ (0x08, ADDMOD, 8, 3, 1, "Modulo addition operation.", Frontier, StopArithmetic),
+ (0x09, MULMOD, 8, 3, 1, "Modulo multiplication operation.", Frontier, StopArithmetic),
+ (0x0a, EXP, 10, 2, 1, "Exponential operation.", Frontier, StopArithmetic),
+ (0x0b, SIGNEXTEND, 5, 2, 1, "Extend length of two's complement signed integer.", Frontier, StopArithmetic),
+ (0x10, LT, 3, 2, 1, "Less-than comparison.", Frontier, ComparisonBitwiseLogic),
+ (0x11, GT, 3, 2, 1, "Greater-than comparison.", Frontier, ComparisonBitwiseLogic),
+ (0x12, SLT, 3, 2, 1, "Signed less-than comparison.", Frontier, ComparisonBitwiseLogic),
+ (0x13, SGT, 3, 2, 1, "Signed greater-than comparison.", Frontier, ComparisonBitwiseLogic),
+ (0x14, EQ, 3, 2, 1, "Equality comparison.", Frontier, ComparisonBitwiseLogic),
+ (0x15, ISZERO, 3, 1, 1, "Simple not operator.", Frontier, ComparisonBitwiseLogic),
+ (0x16, AND, 3, 2, 1, "Bitwise AND operation.", Frontier, ComparisonBitwiseLogic),
+ (0x17, OR, 3, 2, 1, "Bitwise OR operation.", Frontier, ComparisonBitwiseLogic),
+ (0x18, XOR, 3, 2, 1, "Bitwise XOR operation.", Frontier, ComparisonBitwiseLogic),
+ (0x19, NOT, 3, 1, 1, "Bitwise NOT operation.", Frontier, ComparisonBitwiseLogic),
+ (0x1a, BYTE, 3, 2, 1, "Retrieve single byte from word.", Frontier, ComparisonBitwiseLogic),
+ (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, 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),
+ (0x33, CALLER, 2, 0, 1, "Get caller address.", Frontier, EnvironmentalInformation),
+ (0x34, CALLVALUE, 2, 0, 1, "Get deposited value by the instruction/transaction responsible for this execution.", Frontier, EnvironmentalInformation),
+ (0x35, CALLDATALOAD, 3, 1, 1, "Get input data of current environment.", Frontier, EnvironmentalInformation),
+ (0x36, CALLDATASIZE, 2, 0, 1, "Get size of input data in current environment.", Frontier, EnvironmentalInformation),
+ (0x37, CALLDATACOPY, 3, 3, 0, "Copy input data in current environment to memory.", Frontier, EnvironmentalInformation),
+ (0x38, CODESIZE, 2, 0, 1, "Get size of code running in current environment.", Frontier, EnvironmentalInformation),
+ (0x39, CODECOPY, 3, 3, 0, "Copy code running in current environment to memory.", Frontier, EnvironmentalInformation),
+ (0x3a, GASPRICE, 2, 0, 1, "Get price of gas in current environment", Frontier, EnvironmentalInformation),
+ (0x3b, EXTCODESIZE, 20, 1, 1, "Get size of an account's code.", Frontier, EnvironmentalInformation),
+ (0x3c, EXTCODECOPY, 20, 4, 0, "Copy an account's code to memory.", Frontier, EnvironmentalInformation),
+ (0x3d, RETURNDATASIZE, 2, 0, 1, "Get size of output data from the previous call from the current environment.", Byzantium, EnvironmentalInformation),
+ (0x3e, RETURNDATACOPY, 3, 3, 0, "Copy output data from the previous call to memory.", Byzantium, EnvironmentalInformation),
+ (0x3f, EXTCODEHASH, 100, 1, 1, "Get hash of an account’s code.", Cancun, EnvironmentalInformation),
+ (0x40, BLOCKHASH, 20, 1, 1, "Get the hash of one of the 256 most recent complete blocks.", Cancun, EnvironmentalInformation),
+ (0x41, COINBASE, 2, 0, 1, "Get the block's beneficiary address.", Frontier, BlockInformation),
+ (0x42, TIMESTAMP, 2, 0, 1, "Get the block's timestamp.", Frontier, BlockInformation),
+ (0x43, NUMBER, 2, 0, 1, "Get the block's number.", Frontier, BlockInformation),
+ (0x44, DIFFICULTY, 2, 0, 1, "Get the block's difficulty.", Frontier, BlockInformation),
+ (0x45, GASLIMIT, 2, 0, 1, "Get the block's gas limit.", Frontier, BlockInformation),
+ (0x46, CHAINID, 2, 0, 1, "Get the chain ID.", Istanbul, BlockInformation),
+ (0x47, SELFBALANCE, 5, 0, 1, "Get balance of currently executing account.", Istanbul, BlockInformation),
+ (0x48, BASEFEE, 2, 0, 1, "Get the base fee.", London, BlockInformation),
+ (0x49, BLOBHASH, 3, 1, 1, "Get versioned hash at index.", Cancun, BlockInformation),
+ (0x4a, BLOBBASEFEE, 2, 0, 1, "Get the current blob base fee.", Cancun, BlockInformation),
+ (0x50, POP, 2, 1, 0, "Remove item from stack.", Frontier, StackMemoryStorageFlow),
+ (0x51, MLOAD, 3, 1, 1, "Load word from memory.", Frontier, StackMemoryStorageFlow),
+ (0x52, MSTORE, 3, 2, 0, "Save word to memory.", Frontier, StackMemoryStorageFlow),
+ (0x53, MSTORE8, 3, 2, 0, "Save byte to memory.", Frontier, StackMemoryStorageFlow),
+ (0x54, SLOAD, 50, 1, 1, "Load word from storage.", Frontier, StackMemoryStorageFlow),
+ (0x55, SSTORE, 0, 2, 0, "Save word to storage.", Frontier, StackMemoryStorageFlow),
+ (0x56, JUMP, 8, 1, 0, "Alter the program counter.", Frontier, StackMemoryStorageFlow),
+ (0x57, JUMPI, 10, 2, 0, "Conditionally alter the program counter.", Frontier, StackMemoryStorageFlow),
+ (0x58, PC, 2, 0, 1, "Get the value of the program counter prior to the increment.", Frontier, StackMemoryStorageFlow),
+ (0x59, MSIZE, 2, 0, 1, "Get the size of active memory in bytes.", Frontier, StackMemoryStorageFlow),
+ (0x5a, GAS, 2, 0, 1, "Get the amount of available gas.", Frontier, StackMemoryStorageFlow),
+ (0x5b, JUMPDEST, 1, 0, 0, "Mark a valid destination for jumps.", Frontier, StackMemoryStorageFlow),
+ (0x5c, TLOAD, 50, 1, 1, "Load word from transient storage", Cancun, StackMemoryStorageFlow),
+ (0x5d, TSTORE, 0, 2, 0, "Save word to transient storage.", Cancun, StackMemoryStorageFlow),
+ (0x5e, MCOPY, 3,3,0, "copy memory areas", Cancun,StackMemoryStorageFlow),
+ (0x5f, PUSH0, 2, 0, 1, "Place 0 byte item on stack.", Shanghai, Push),
+ (0x60, PUSH1, 3, 0, 1, "Place 1 byte item on stack.", Frontier, Push),
+ (0x61, PUSH2, 3, 0, 1, "Place 2-byte item on stack.", Frontier, Push),
+ (0x62, PUSH3, 3, 0, 1, "Place 3-byte item on stack.", Frontier, Push),
+ (0x63, PUSH4, 3, 0, 1, "Place 4-byte item on stack.", Frontier, Push),
+ (0x64, PUSH5, 3, 0, 1, "Place 5-byte item on stack.", Frontier, Push),
+ (0x65, PUSH6, 3, 0, 1, "Place 6-byte item on stack.", Frontier, Push),
+ (0x66, PUSH7, 3, 0, 1, "Place 7-byte item on stack.", Frontier, Push),
+ (0x67, PUSH8, 3, 0, 1, "Place 8-byte item on stack.", Frontier, Push),
+ (0x68, PUSH9, 3, 0, 1, "Place 9-byte item on stack.", Frontier, Push),
+ (0x69, PUSH10, 3, 0, 1, "Place 10-byte item on stack.", Frontier, Push),
+ (0x6a, PUSH11, 3, 0, 1, "Place 11-byte item on stack.", Frontier, Push),
+ (0x6b, PUSH12, 3, 0, 1, "Place 12-byte item on stack.", Frontier, Push),
+ (0x6c, PUSH13, 3, 0, 1, "Place 13-byte item on stack.", Frontier, Push),
+ (0x6d, PUSH14, 3, 0, 1, "Place 14-byte item on stack.", Frontier, Push),
+ (0x6e, PUSH15, 3, 0, 1, "Place 15-byte item on stack.", Frontier, Push),
+ (0x6f, PUSH16, 3, 0, 1, "Place 16-byte item on stack.", Frontier, Push),
+ (0x70, PUSH17, 3, 0, 1, "Place 17-byte item on stack.", Frontier, Push),
+ (0x71, PUSH18, 3, 0, 1, "Place 18-byte item on stack.", Frontier, Push),
+ (0x72, PUSH19, 3, 0, 1, "Place 19-byte item on stack.", Frontier, Push),
+ (0x73, PUSH20, 3, 0, 1, "Place 20-byte item on stack.", Frontier, Push),
+ (0x74, PUSH21, 3, 0, 1, "Place 21-byte item on stack.", Frontier, Push),
+ (0x75, PUSH22, 3, 0, 1, "Place 22-byte item on stack.", Frontier, Push),
+ (0x76, PUSH23, 3, 0, 1, "Place 23-byte item on stack.", Frontier, Push),
+ (0x77, PUSH24, 3, 0, 1, "Place 24-byte item on stack.", Frontier, Push),
+ (0x78, PUSH25, 3, 0, 1, "Place 25-byte item on stack.", Frontier, Push),
+ (0x79, PUSH26, 3, 0, 1, "Place 26-byte item on stack.", Frontier, Push),
+ (0x7a, PUSH27, 3, 0, 1, "Place 27-byte item on stack.", Frontier, Push),
+ (0x7b, PUSH28, 3, 0, 1, "Place 28-byte item on stack.", Frontier, Push),
+ (0x7c, PUSH29, 3, 0, 1, "Place 29-byte item on stack.", Frontier, Push),
+ (0x7d, PUSH30, 3, 0, 1, "Place 30-byte item on stack.", Frontier, Push),
+ (0x7e, PUSH31, 3, 0, 1, "Place 31-byte item on stack.", Frontier, Push),
+ (0x7f, PUSH32, 3, 0, 1, "Place 32-byte (full word) item on stack.", Frontier, Push),
+ (0x80, DUP1, 3, 1, 2, "Duplicate 1st stack item.", Frontier, Duplication),
+ (0x81, DUP2, 3, 2, 3, "Duplicate 2nd stack item.", Frontier, Duplication),
+ (0x82, DUP3, 3, 3, 4, "Duplicate 3rd stack item.", Frontier, Duplication),
+ (0x83, DUP4, 3, 4, 5, "Duplicate 4th stack item.", Frontier, Duplication),
+ (0x84, DUP5, 3, 5, 6, "Duplicate 5th stack item.", Frontier, Duplication),
+ (0x85, DUP6, 3, 6, 7, "Duplicate 6th stack item.", Frontier, Duplication),
+ (0x86, DUP7, 3, 7, 8, "Duplicate 7th stack item.", Frontier, Duplication),
+ (0x87, DUP8, 3, 8, 9, "Duplicate 8th stack item.", Frontier, Duplication),
+ (0x88, DUP9, 3, 9, 10, "Duplicate 9th stack item.", Frontier, Duplication),
+ (0x89, DUP10, 3, 10, 11, "Duplicate 10th stack item.", Frontier, Duplication),
+ (0x8a, DUP11, 3, 11, 12, "Duplicate 11th stack item.", Frontier, Duplication),
+ (0x8b, DUP12, 3, 12, 13, "Duplicate 12th stack item.", Frontier, Duplication),
+ (0x8c, DUP13, 3, 13, 14, "Duplicate 13th stack item.", Frontier, Duplication),
+ (0x8d, DUP14, 3, 14, 15, "Duplicate 14th stack item.", Frontier, Duplication),
+ (0x8e, DUP15, 3, 15, 16, "Duplicate 15th stack item.", Frontier, Duplication),
+ (0x8f, DUP16, 3, 16, 17, "Duplicate 16th stack item.", Frontier, Duplication),
+ (0x90, SWAP1, 3, 2, 2, "Exchange 1st and 2nd stack items.", Frontier, Exchange),
+ (0x91, SWAP2, 3, 3, 3, "Exchange 1st and 3rd stack items.", Frontier, Exchange),
+ (0x92, SWAP3, 3, 4, 4, "Exchange 1st and 4th stack items.", Frontier, Exchange),
+ (0x93, SWAP4, 3, 5, 5, "Exchange 1st and 5th stack items.", Frontier, Exchange),
+ (0x94, SWAP5, 3, 6, 6, "Exchange 1st and 6th stack items.", Frontier, Exchange),
+ (0x95, SWAP6, 3, 7, 7, "Exchange 1st and 7th stack items.", Frontier, Exchange),
+ (0x96, SWAP7, 3, 8, 8, "Exchange 1st and 8th stack items.", Frontier, Exchange),
+ (0x97, SWAP8, 3, 9, 9, "Exchange 1st and 9th stack items.", Frontier, Exchange),
+ (0x98, SWAP9, 3, 10, 10, "Exchange 1st and 10th stack items.", Frontier, Exchange),
+ (0x99, SWAP10, 3, 11, 11, "Exchange 1st and 11th stack items.", Frontier, Exchange),
+ (0x9a, SWAP11, 3, 12, 12, "Exchange 1st and 12th stack items.", Frontier, Exchange),
+ (0x9b, SWAP12, 3, 13, 13, "Exchange 1st and 13th stack items.", Frontier, Exchange),
+ (0x9c, SWAP13, 3, 14, 14, "Exchange 1st and 14th stack items.", Frontier, Exchange),
+ (0x9d, SWAP14, 3, 15, 15, "Exchange 1st and 15th stack items.", Frontier, Exchange),
+ (0x9e, SWAP15, 3, 16, 16, "Exchange 1st and 16th stack items.", Frontier, Exchange),
+ (0x9f, SWAP16, 3, 17, 17, "Exchange 1st and 17th stack items.", Frontier, Exchange),
+ (0xa0, LOG0, 375, 2, 0, "Append log record with no topics.", Frontier, Logging),
+ (0xa1, LOG1, 750, 3, 0, "Append log record with one topic.", Frontier, Logging),
+ (0xa2, LOG2, 1125, 4, 0, "Append log record with two topics.", Frontier, Logging),
+ (0xa3, LOG3, 1500, 5, 0, "Append log record with three topics.", Frontier, Logging),
+ (0xa4, LOG4, 1875, 6, 0, "Append log record with four topics.", Frontier, Logging),
+ (0xf0, CREATE, 32000, 3, 1, "Create a new account with associated code.", Frontier, System),
+ (0xf1, CALL, 100, 7, 1, "Message-call into an account.", Frontier, System),
+ (0xf2, CALLCODE, 100, 7, 1, "Message-call into this account with alternative account's code.", Frontier, System),
+ (0xf3, RETURN, 0, 2, 0, "Halt execution returning output data.", Frontier, System),
+ (0xf4, DELEGATECALL, 40, 6, 1, "Message-call with an alternative account's code, persisting the current context.", Frontier, System),
+ (0xf5, CREATE2, 32000, 4, 1, "Create a new account with associated code at a specified address.", Constantinople, System),
+ (0xfa, STATICCALL, 40, 6, 1, "Static message-call into an account.", Byzantium, System),
+ (0xfd, REVERT, 0, 2, 0, "Stop execution and revert state changes, without consuming all gas and providing a reason.", Byzantium, System),
+ (0xfe, INVALID, 0, 0, 0, "Designated invalid instruction.", Frontier, System),
+ (0xff, SELFDESTRUCT, 5000, 1, 0, "Halt execution and register account for later deletion.", Frontier, System)
+}
diff --git a/evm/opcodes/src/lib.rs b/evm/opcodes/src/lib.rs
index d9f531a94..f8279d75f 100644
--- a/evm/opcodes/src/lib.rs
+++ b/evm/opcodes/src/lib.rs
@@ -1,8 +1,10 @@
//! Ethereum virtual machine opcode
#![deny(missing_docs)]
+mod cancun;
mod shanghai;
+pub use cancun::Cancun;
pub use shanghai::ShangHai;
/// Ethereum virtual machine opcode generator.
@@ -185,6 +187,8 @@ pub enum Upgrade {
London,
/// Shanghai
Shanghai,
+ /// Cancun
+ Cancun,
}
/// Ethereum virtual machine opcode.
diff --git a/examples/addmod.rs b/examples/addmod.rs
new file mode 100644
index 000000000..3235b3df5
--- /dev/null
+++ b/examples/addmod.rs
@@ -0,0 +1,87 @@
+//! Addmod example for i64, i32, u64, u32.
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+use zink::primitives::{numeric::Numeric, U256};
+
+#[zink::external]
+pub fn addmod_i32(a: i32, b: i32, n: i32) -> i32 {
+ a.addmod(b, n)
+}
+
+#[zink::external]
+pub fn addmod_i64(a: i64, b: i64, n: i64) -> i64 {
+ a.addmod(b, n)
+}
+
+#[zink::external]
+pub fn addmod_u32(a: u32, b: u32, n: u32) -> u32 {
+ a.addmod(b, n)
+}
+
+#[zink::external]
+pub fn addmod_u64(a: u64, b: u64, n: u64) -> u64 {
+ a.addmod(b, n)
+}
+
+#[zink::external]
+pub fn addmod_U256(a: U256, b: U256, n: U256) -> U256 {
+ a.addmod(b, n)
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
+
+#[test]
+fn test() -> anyhow::Result<()> {
+ use zint::{Bytes32 as _, Contract};
+
+ // Test for i32
+ let mut contract = Contract::search("addmod")?.compile()?;
+
+ let info_i32 = contract.execute([
+ "addmod_i32(int32,int32,int32)".as_bytes(),
+ &3i32.to_bytes32(),
+ &5i32.to_bytes32(),
+ &7i32.to_bytes32(),
+ ])?;
+ assert_eq!(info_i32.ret, 1i32.to_bytes32());
+
+ // Test for i64
+ let info_i64 = contract.execute([
+ "addmod_i64(int64,int64,int64)".as_bytes(),
+ &3i64.to_bytes32(),
+ &5i64.to_bytes32(),
+ &7i64.to_bytes32(),
+ ])?;
+ assert_eq!(info_i64.ret, 1i64.to_bytes32());
+
+ let info_u32 = contract.execute([
+ "addmod_u32(uint32,uint32,uint32)".as_bytes(),
+ &3u32.to_bytes32(),
+ &5u32.to_bytes32(),
+ &7u32.to_bytes32(),
+ ])?;
+ assert_eq!(info_u32.ret, 1u32.to_bytes32());
+
+ // Test for u64
+ let info_u64 = contract.execute([
+ "addmod_u64(uint64,uint64,uint64)".as_bytes(),
+ &3u64.to_bytes32(),
+ &5u64.to_bytes32(),
+ &7u64.to_bytes32(),
+ ])?;
+ assert_eq!(info_u64.ret, 1u64.to_bytes32());
+
+ //Test for U256
+ let info_u256 = contract.execute([
+ "addmod_U256(uint256,uint256,uint256)".as_bytes(),
+ &3i32.to_bytes32(),
+ &5i32.to_bytes32(),
+ &7i32.to_bytes32(),
+ ])?;
+ assert_eq!(info_u256.ret, 1i32.to_bytes32());
+
+ Ok(())
+}
diff --git a/examples/approval.rs b/examples/approval.rs
index 8965d7e6a..b033bb229 100644
--- a/examples/approval.rs
+++ b/examples/approval.rs
@@ -79,7 +79,7 @@ fn test_approval() -> anyhow::Result<()> {
let allowance = evm.storage(
address,
- Allowance::storage_key(Address(evm.caller), Address(spender)),
+ Allowance::storage_key(Address::from(evm.caller), Address::from(spender)),
)?;
assert_eq!(value.to_bytes32(), allowance);
@@ -105,7 +105,7 @@ fn test_approval() -> anyhow::Result<()> {
assert_eq!(info.ret, true.to_bytes32());
let allowance = evm.storage(
address,
- Allowance::storage_key(Address(evm.caller), Address(spender)),
+ Allowance::storage_key(Address::from(evm.caller), Address::from(spender)),
)?;
assert_eq!(half_value.to_bytes32(), allowance);
Ok(())
diff --git a/examples/br_balance.rs b/examples/br_balance.rs
new file mode 100644
index 000000000..33295797a
--- /dev/null
+++ b/examples/br_balance.rs
@@ -0,0 +1,60 @@
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+use zink::{storage, Storage};
+
+#[storage(i32)]
+struct Balance;
+
+#[zink::external]
+fn check_and_update(value: i32) -> bool {
+ let current = Balance::get();
+
+ // This mimics the ERC20 balance check
+ if current < value {
+ zink::revert!("Not enough balance");
+ // TODO: #287
+ // return false;
+ }
+
+ Balance::set(current - value);
+ return true;
+}
+
+// TODO: identify if the problem is caused by control flow of incorrect opcode mapping. (issue #287)
+#[test]
+fn test_balance_check() -> anyhow::Result<()> {
+ use zint::{Bytes32, Contract, EVM};
+
+ let mut evm = EVM::default().commit(true);
+ let mut contract = Contract::search("br_balance")?.compile()?;
+
+ // Initialize with balance of 42
+ let info = evm.deploy(
+ &contract
+ .construct(
+ [(
+ Balance::STORAGE_KEY.to_bytes32().into(),
+ vec![42].try_into()?,
+ )]
+ .into_iter()
+ .collect(),
+ )?
+ .bytecode()?,
+ )?;
+
+ // Try to transfer 21 (should succeed)
+ let info = evm
+ .calldata(&contract.encode(&[
+ b"check_and_update(int32)".to_vec(),
+ 21i32.to_bytes32().to_vec(),
+ ])?)
+ .call(info.address)?;
+ assert_eq!(info.ret, true.to_bytes32(), "{info:?}");
+
+ Ok(())
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
diff --git a/examples/bytes.rs b/examples/bytes.rs
new file mode 100644
index 000000000..69ec6084a
--- /dev/null
+++ b/examples/bytes.rs
@@ -0,0 +1,40 @@
+//! Storage example.
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+
+use zink::{primitives::Bytes16, Storage};
+
+/// Counter with value type `Bytes16`
+#[zink::storage(Bytes16)]
+pub struct Bytes;
+
+/// set value to the storage.
+#[zink::external]
+pub fn set(value: Bytes16) {
+ Bytes::set(value);
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
+
+#[test]
+fn value() -> anyhow::Result<()> {
+ use zink::Asm;
+ use zint::Contract;
+
+ let mut contract = Contract::search("bytes")?.compile()?;
+ let new_storage = [8u8; 16];
+ let mut evm = contract.deploy()?.commit(true);
+
+ evm.calldata(&contract.encode(&[b"set(bytes)".to_vec(), new_storage.to_vec()])?)
+ .call(contract.address)?;
+
+ assert_eq!(
+ evm.storage(contract.address, [0; 32])?,
+ Bytes16(new_storage).bytes32(),
+ );
+
+ Ok(())
+}
diff --git a/examples/erc20.rs b/examples/erc20.rs
index f24134761..3b21fcaf3 100644
--- a/examples/erc20.rs
+++ b/examples/erc20.rs
@@ -78,6 +78,7 @@ fn _update(from: Address, to: Address, value: U256) {
TotalSupply::set(TotalSupply::get().add(value));
} else {
let from_balance = Balances::get(from);
+
if from_balance.lt(value) {
zink::revert!("Insufficient balance");
}
@@ -143,6 +144,7 @@ fn deploy() -> anyhow::Result<()> {
use zint::{Bytes32, Contract, EVM};
let caller_bytes = hex::decode("be862ad9abfe6f22bcb087716c7d89a26051f74c")?;
+ let spender = [42; 20];
let mut caller = [0; 20];
caller.copy_from_slice(&caller_bytes);
@@ -150,6 +152,8 @@ fn deploy() -> anyhow::Result<()> {
let mut contract = Contract::search("erc20")?.compile()?;
let name = "The Zink Language";
let symbol = "zink";
+ let value = 42;
+ //let half_value = 21;
// 1. deploy
let info = evm.deploy(
@@ -165,6 +169,10 @@ fn deploy() -> anyhow::Result<()> {
TotalSupply::STORAGE_KEY.to_bytes32().into(),
vec![42].try_into()?,
),
+ (
+ Balances::storage_key(Address::from(caller)).into(),
+ vec![42].try_into()?,
+ ),
]
.into_iter()
.collect(),
@@ -173,58 +181,90 @@ fn deploy() -> anyhow::Result<()> {
)?;
let address = info.address;
- // 2. get name
- let info = evm
- .calldata(&contract.encode(&[b"name()".to_vec()])?)
- .call(address)?;
- assert_eq!(info.ret, name.to_bytes32());
-
- // 3. get symbol
- let info = evm
- .calldata(&contract.encode(&[b"symbol()".to_vec()])?)
- .call(address)?;
- assert_eq!(info.ret, symbol.to_bytes32());
-
- // 4. get total supply
- let info = evm
- .calldata(&contract.encode(&[b"total_supply()".to_vec()])?)
- .call(address)?;
- assert_eq!(info.ret, 42u64.to_bytes32());
-
- // 5. check decimals
- let info = evm
- .calldata(&contract.encode(&[b"decimals()".to_vec()])?)
- .call(address)?;
- assert_eq!(info.ret, 8u64.to_bytes32());
-
- // TODO: refactor offset handling (#280)
- // // 6. check approval
- // 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(), "{info:?}");
- //
- // let allowance = evm.storage(
- // address,
- // Allowance::storage_key(Address(evm.caller), Address(spender)),
- // )?;
- // assert_eq!(value.to_bytes32(), allowance);
- //
- // // 7. check approval results
- // let info = evm
- // .calldata(&contract.encode(&[
- // b"allowance(address,address)".to_vec(),
- // evm.caller.to_bytes32().to_vec(),
- // spender.to_bytes32().to_vec(),
- // ])?)
- // .call(address)?;
- // assert_eq!(info.ret, allowance);
+ // 2. get static data
+ {
+ // 2.1. get name
+ let info = evm
+ .calldata(&contract.encode(&[b"name()".to_vec()])?)
+ .call(address)?;
+ assert_eq!(info.ret, name.to_bytes32(), "{info:?}");
+
+ // 2.2. get symbol
+ let info = evm
+ .calldata(&contract.encode(&[b"symbol()".to_vec()])?)
+ .call(address)?;
+ assert_eq!(info.ret, symbol.to_bytes32(), "{info:?}");
+
+ // 2.3. get total supply
+ let info = evm
+ .calldata(&contract.encode(&[b"total_supply()".to_vec()])?)
+ .call(address)?;
+ assert_eq!(info.ret, 42u64.to_bytes32(), "{info:?}");
+
+ // 2.4. check decimals
+ let info = evm
+ .calldata(&contract.encode(&[b"decimals()".to_vec()])?)
+ .call(address)?;
+ assert_eq!(info.ret, 8u64.to_bytes32(), "{info:?}");
+
+ // 2.5. check balance of the caller
+ let balance = evm.storage(address, Balances::storage_key(Address::from(caller)))?;
+ assert_eq!(value.to_bytes32(), balance);
+ }
+
+ // 3. check approval
+ {
+ // 3.1. approve
+ 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(), "{info:?}");
+
+ let allowance = evm.storage(
+ address,
+ Allowance::storage_key(Address::from(evm.caller), Address::from(spender)),
+ )?;
+ assert_eq!(value.to_bytes32(), allowance);
+
+ // 3.2. check approval results
+ let info = evm
+ .calldata(&contract.encode(&[
+ b"allowance(address,address)".to_vec(),
+ evm.caller.to_bytes32().to_vec(),
+ spender.to_bytes32().to_vec(),
+ ])?)
+ .call(address)?;
+ assert_eq!(info.ret, allowance);
+ }
+
+ // 4. check transfer
+ {
+ // 4.1. verify balance of the caller
+ let info = evm
+ .calldata(&contract.encode(&[
+ b"balances(address)".to_vec(),
+ evm.caller.to_bytes32().to_vec(),
+ ])?)
+ .call(address)?;
+ assert_eq!(info.ret, value.to_bytes32(), "{info:?}");
+
+ // TODO: see br_balance.rs (#287)
+ // 4.2. check transfer
+ /* evm = evm.commit(false);
+ let info = evm
+ .calldata(&contract.encode(&[
+ b"transfer(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(), "{info:?}"); */
+ }
Ok(())
}
diff --git a/examples/revert.rs b/examples/revert.rs
index d0ae2d7cf..620e1197f 100644
--- a/examples/revert.rs
+++ b/examples/revert.rs
@@ -9,10 +9,16 @@ fn main() {}
/// check if the passing address is owner
#[zink::external]
-pub fn run_revert() {
+pub fn revert() {
zink::revert!("revert works")
}
+/// check if the passing address is owner
+#[zink::external]
+pub fn assert() {
+ zink::assert!(false, "assert works");
+}
+
#[test]
fn test_revert() -> anyhow::Result<()> {
use zint::Contract;
@@ -20,5 +26,8 @@ fn test_revert() -> anyhow::Result<()> {
let info = contract.execute(["revert()".as_bytes()])?;
assert_eq!(info.revert, Some("revert works".into()));
+
+ let info = contract.execute(["assert()".as_bytes()])?;
+ assert_eq!(info.revert, Some("assert works".into()));
Ok(())
}
diff --git a/examples/transient_storage.rs b/examples/transient_storage.rs
new file mode 100644
index 000000000..4dc48ab2b
--- /dev/null
+++ b/examples/transient_storage.rs
@@ -0,0 +1,39 @@
+//! Transient Storage example.
+#![cfg_attr(target_arch = "wasm32", no_std)]
+#![cfg_attr(target_arch = "wasm32", no_main)]
+
+extern crate zink;
+
+use zink::storage::TransientStorage;
+/// Temporary counter with value type `i32` that resets after each transaction
+#[zink::transient_storage(i32)]
+pub struct TempCounter;
+
+/// Set and get value via the transient storage.
+#[zink::external]
+pub fn set_and_get_temp(value: i32) -> i32 {
+ TempCounter::set(value);
+ TempCounter::get()
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {}
+
+#[test]
+fn transient_value() -> anyhow::Result<()> {
+ use zint::{Bytes32, Contract};
+
+ let mut contract = Contract::search("transient_storage")?.compile()?;
+ let value: i32 = 42;
+
+ {
+ let info = contract.execute(&[
+ b"set_and_get_temp(int32)".to_vec(),
+ value.to_bytes32().to_vec(),
+ ])?;
+ assert!(!info.ret.is_empty());
+ assert_eq!(info.ret.to_bytes32(), value.to_bytes32());
+ }
+
+ Ok(())
+}
diff --git a/tests/br_if.rs b/tests/br_if.rs
index ff83dcda3..51714e71d 100644
--- a/tests/br_if.rs
+++ b/tests/br_if.rs
@@ -1,7 +1,7 @@
//! br_if tests for the zink compiler.
use anyhow::Result;
use filetests::Test;
-use zint::Contract;
+use zint::{Contract, HaltReason};
#[test]
fn as_block_last() -> Result<()> {
@@ -12,7 +12,7 @@ fn as_block_last() -> Result<()> {
assert!(info.ret.is_empty());
let info = contract.execute(&[42])?;
- assert!(info.halt.is_none());
+ assert!(matches!(info.halt, Some(HaltReason::OutOfGas(_))));
assert!(info.ret.is_empty());
Ok(())
diff --git a/abi/Cargo.toml b/zink/abi/Cargo.toml
similarity index 100%
rename from abi/Cargo.toml
rename to zink/abi/Cargo.toml
diff --git a/abi/README.md b/zink/abi/README.md
similarity index 100%
rename from abi/README.md
rename to zink/abi/README.md
diff --git a/abi/src/abi.rs b/zink/abi/src/abi.rs
similarity index 100%
rename from abi/src/abi.rs
rename to zink/abi/src/abi.rs
diff --git a/abi/src/lib.rs b/zink/abi/src/lib.rs
similarity index 100%
rename from abi/src/lib.rs
rename to zink/abi/src/lib.rs
diff --git a/abi/src/result.rs b/zink/abi/src/result.rs
similarity index 100%
rename from abi/src/result.rs
rename to zink/abi/src/result.rs
diff --git a/abi/src/selector.rs b/zink/abi/src/selector.rs
similarity index 100%
rename from abi/src/selector.rs
rename to zink/abi/src/selector.rs
diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs
index 38b8e877d..776ddaa54 100644
--- a/zink/codegen/src/lib.rs
+++ b/zink/codegen/src/lib.rs
@@ -4,7 +4,9 @@
extern crate proc_macro;
use proc_macro::TokenStream;
-use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemStruct, LitStr};
+use proc_macro2::Span;
+use quote::ToTokens;
+use syn::{parse_macro_input, Attribute, DeriveInput, Expr, ItemFn, ItemStruct, LitStr};
mod event;
mod revert;
@@ -21,6 +23,16 @@ pub fn revert(input: TokenStream) -> TokenStream {
revert::parse(input)
}
+/// Check and expression and revert with the input message
+///
+/// This is similar with the builtin `assert!` in rust, but the revert
+/// message only support raw string.
+#[proc_macro]
+pub fn assert(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as revert::AssertInput);
+ revert::parse_assert(input)
+}
+
/// Event logging interface
///
/// ```ignore
@@ -69,6 +81,24 @@ pub fn storage(attr: TokenStream, input: TokenStream) -> TokenStream {
storage::Storage::parse(ty, input)
}
+/// Declare transient storage (cleared after each transaction)
+///
+/// ```ignore
+/// /// transient storage value
+/// #[zink::transient_storage(i32)]
+/// pub struct TempCounter;
+///
+/// /// transient storage mapping
+/// #[zink::transient_storage(i32, i32)]
+/// pub struct TempMapping;
+/// ```
+#[proc_macro_attribute]
+pub fn transient_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
+ let ty = storage::StorageType::from(attr);
+ let input = parse_macro_input!(input as ItemStruct);
+ storage::Storage::parse_transient(ty, input)
+}
+
/// Mark the function as an external entry point.
#[proc_macro_attribute]
pub fn external(_args: TokenStream, input: TokenStream) -> TokenStream {
diff --git a/zink/codegen/src/revert.rs b/zink/codegen/src/revert.rs
index e03076cdb..e8fc1305c 100644
--- a/zink/codegen/src/revert.rs
+++ b/zink/codegen/src/revert.rs
@@ -3,7 +3,10 @@
use proc_macro::TokenStream;
use proc_macro2::{Literal, Span};
use quote::{quote, ToTokens};
-use syn::{Ident, LitStr};
+use syn::{
+ parse::{Parse, ParseStream},
+ parse2, Expr, Ident, LitStr, Token,
+};
/// Revert with message
pub fn parse(input: LitStr) -> TokenStream {
@@ -34,3 +37,41 @@ pub fn parse(input: LitStr) -> TokenStream {
}
.into()
}
+
+/// Parse assert macro
+pub fn parse_assert(input: AssertInput) -> TokenStream {
+ let cond = input.cond;
+ let revert: Expr = syn::parse2(
+ parse(
+ input
+ .message
+ .unwrap_or(LitStr::new("unknown error", Span::call_site())),
+ )
+ .into(),
+ )
+ .expect("Invalid revert message");
+
+ quote! {
+ if !#cond {
+ #revert
+ }
+ }
+ .into()
+}
+
+/// Assert input
+pub struct AssertInput {
+ pub cond: Expr,
+ pub comma: Token![,],
+ pub message: Option,
+}
+
+impl Parse for AssertInput {
+ fn parse(input: ParseStream) -> syn::Result {
+ Ok(AssertInput {
+ cond: input.parse()?,
+ comma: input.parse()?,
+ message: input.parse()?,
+ })
+ }
+}
diff --git a/zink/codegen/src/storage.rs b/zink/codegen/src/storage.rs
index 58b33a1ac..7f73ca863 100644
--- a/zink/codegen/src/storage.rs
+++ b/zink/codegen/src/storage.rs
@@ -12,10 +12,20 @@ use syn::{
thread_local! {
static STORAGE_REGISTRY: RefCell> = RefCell::new(HashSet::new());
+ static TRANSIENT_STORAGE_REGISTRY: RefCell> = RefCell::new(HashSet::new());
+}
+
+/// Storage type (persistent or transient)
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StorageKind {
+ Persistent,
+ Transient,
}
/// Storage attributes parser
pub struct Storage {
+ /// Storage kind (persistent or transient)
+ kind: StorageKind,
/// kind of the storage
ty: StorageType,
/// The source and the target storage struct
@@ -25,12 +35,48 @@ pub struct Storage {
}
impl Storage {
- /// Parse from proc_macro attribute
+ /// Parse from proc_macro attribute for persistent storage
pub fn parse(ty: StorageType, target: ItemStruct) -> TokenStream {
- let storage = Self::from((ty, target));
+ let storage = Self::from_parts(StorageKind::Persistent, ty, target);
+ storage.expand()
+ }
+
+ /// Parse from proc_macro attribute for transient storage
+ pub fn parse_transient(ty: StorageType, target: ItemStruct) -> TokenStream {
+ let storage = Self::from_parts(StorageKind::Transient, ty, target);
storage.expand()
}
+ fn from_parts(kind: StorageKind, ty: StorageType, target: ItemStruct) -> Self {
+ let mut this = Self {
+ kind,
+ ty,
+ target,
+ getter: None,
+ };
+
+ let mut attrs: Vec = Default::default();
+ for attr in this.target.attrs.iter().cloned() {
+ if !attr.path().is_ident("getter") {
+ attrs.push(attr);
+ continue;
+ }
+
+ let Ok(list) = attr.meta.require_list().clone() else {
+ panic!("Invalid getter arguments");
+ };
+
+ let Some(TokenTree::Ident(getter)) = list.tokens.clone().into_iter().nth(0) else {
+ panic!("Invalid getter function name");
+ };
+
+ this.getter = Some(getter);
+ }
+
+ this.target.attrs = attrs;
+ this
+ }
+
fn expand(mut self) -> TokenStream {
match &self.ty {
StorageType::Value(value) => self.expand_value(value.clone()),
@@ -45,14 +91,19 @@ impl Storage {
fn expand_value(&mut self, value: Ident) -> TokenStream {
let is = &self.target;
let name = self.target.ident.clone();
- let slot = storage_slot(name.to_string());
+ let slot = self.get_storage_slot(name.to_string());
let key = slot.to_bytes32();
let keyl = Literal::byte_string(&key);
+ let trait_path = match self.kind {
+ StorageKind::Persistent => quote!(zink::storage::Storage),
+ StorageKind::Transient => quote!(zink::storage::TransientStorage),
+ };
+
let mut expanded = quote! {
#is
- impl zink::storage::Storage for #name {
+ impl #trait_path for #name {
#[cfg(not(target_family = "wasm"))]
const STORAGE_KEY: [u8; 32] = *#keyl;
const STORAGE_SLOT: i32 = #slot;
@@ -62,7 +113,6 @@ impl Storage {
};
if let Some(getter) = self.getter() {
- // TODO: generate docs from the storage doc
let gs: proc_macro2::TokenStream = parse_quote! {
#[allow(missing_docs)]
#[zink::external]
@@ -79,12 +129,17 @@ impl Storage {
fn expand_mapping(&mut self, key: Ident, value: Ident) -> TokenStream {
let is = &self.target;
let name = self.target.ident.clone();
- let slot = storage_slot(name.to_string());
+ let slot = self.get_storage_slot(name.to_string());
+
+ let trait_path = match self.kind {
+ StorageKind::Persistent => quote!(zink::storage::Mapping),
+ StorageKind::Transient => quote!(zink::transient_storage::TransientMapping),
+ };
let mut expanded = quote! {
#is
- impl zink::storage::Mapping for #name {
+ impl #trait_path for #name {
const STORAGE_SLOT: i32 = #slot;
type Key = #key;
@@ -103,7 +158,6 @@ impl Storage {
};
if let Some(getter) = self.getter() {
- // TODO: generate docs from the storage doc
let gs: proc_macro2::TokenStream = parse_quote! {
#[allow(missing_docs)]
#[zink::external]
@@ -120,12 +174,17 @@ impl Storage {
fn expand_dk_mapping(&mut self, key1: Ident, key2: Ident, value: Ident) -> TokenStream {
let is = &self.target;
let name = self.target.ident.clone();
- let slot = storage_slot(name.to_string());
+ let slot = self.get_storage_slot(name.to_string());
+
+ let trait_path = match self.kind {
+ StorageKind::Persistent => quote!(zink::storage::DoubleKeyMapping),
+ StorageKind::Transient => quote!(zink::transient_storage::DoubleKeyTransientMapping),
+ };
let mut expanded = quote! {
#is
- impl zink::DoubleKeyMapping for #name {
+ impl #trait_path for #name {
const STORAGE_SLOT: i32 = #slot;
type Key1 = #key1;
@@ -148,7 +207,6 @@ impl Storage {
};
if let Some(getter) = self.getter() {
- // TODO: generate docs from the storage doc
let gs: proc_macro2::TokenStream = parse_quote! {
#[allow(missing_docs)]
#[zink::external]
@@ -162,6 +220,25 @@ impl Storage {
expanded.into()
}
+ fn get_storage_slot(&self, name: String) -> i32 {
+ match self.kind {
+ StorageKind::Persistent => STORAGE_REGISTRY.with_borrow_mut(|r| {
+ let key = r.len();
+ if !r.insert(name.clone()) {
+ panic!("Storage {name} has already been declared");
+ }
+ key
+ }) as i32,
+ StorageKind::Transient => TRANSIENT_STORAGE_REGISTRY.with_borrow_mut(|r| {
+ let key = r.len();
+ if !r.insert(name.clone()) {
+ panic!("Transient storage {name} has already been declared");
+ }
+ key
+ }) as i32,
+ }
+ }
+
/// Get the getter of this storage
fn getter(&mut self) -> Option {
let mut getter = if matches!(self.target.vis, Visibility::Public(_)) {
@@ -178,37 +255,6 @@ impl Storage {
}
}
-impl From<(StorageType, ItemStruct)> for Storage {
- fn from(patts: (StorageType, ItemStruct)) -> Self {
- let mut this = Self {
- ty: patts.0,
- target: patts.1,
- getter: None,
- };
-
- let mut attrs: Vec = Default::default();
- for attr in this.target.attrs.iter().cloned() {
- if !attr.path().is_ident("getter") {
- attrs.push(attr);
- continue;
- }
-
- let Ok(list) = attr.meta.require_list().clone() else {
- panic!("Invali getter arguments");
- };
-
- let Some(TokenTree::Ident(getter)) = list.tokens.clone().into_iter().nth(0) else {
- panic!("Invalid getter function name");
- };
-
- this.getter = Some(getter);
- }
-
- this.target.attrs = attrs;
- this
- }
-}
-
/// Zink storage type parser
#[derive(Default, Debug)]
pub enum StorageType {
@@ -246,14 +292,3 @@ impl From for StorageType {
}
}
}
-
-fn storage_slot(name: String) -> i32 {
- STORAGE_REGISTRY.with_borrow_mut(|r| {
- let key = r.len();
- if !r.insert(name.clone()) {
- panic!("Storage {name} has already been declared");
- }
-
- key
- }) as i32
-}
diff --git a/elko/Cargo.toml b/zink/elko/Cargo.toml
similarity index 100%
rename from elko/Cargo.toml
rename to zink/elko/Cargo.toml
diff --git a/elko/README.md b/zink/elko/README.md
similarity index 100%
rename from elko/README.md
rename to zink/elko/README.md
diff --git a/elko/src/bin/elko.rs b/zink/elko/src/bin/elko.rs
similarity index 100%
rename from elko/src/bin/elko.rs
rename to zink/elko/src/bin/elko.rs
diff --git a/elko/src/build.rs b/zink/elko/src/build.rs
similarity index 100%
rename from elko/src/build.rs
rename to zink/elko/src/build.rs
diff --git a/elko/src/lib.rs b/zink/elko/src/lib.rs
similarity index 100%
rename from elko/src/lib.rs
rename to zink/elko/src/lib.rs
diff --git a/elko/src/new.rs b/zink/elko/src/new.rs
similarity index 100%
rename from elko/src/new.rs
rename to zink/elko/src/new.rs
diff --git a/elko/src/utils/manifest.rs b/zink/elko/src/utils/manifest.rs
similarity index 100%
rename from elko/src/utils/manifest.rs
rename to zink/elko/src/utils/manifest.rs
diff --git a/elko/src/utils/mod.rs b/zink/elko/src/utils/mod.rs
similarity index 100%
rename from elko/src/utils/mod.rs
rename to zink/elko/src/utils/mod.rs
diff --git a/elko/src/utils/result.rs b/zink/elko/src/utils/result.rs
similarity index 100%
rename from elko/src/utils/result.rs
rename to zink/elko/src/utils/result.rs
diff --git a/elko/src/utils/wasm.rs b/zink/elko/src/utils/wasm.rs
similarity index 100%
rename from elko/src/utils/wasm.rs
rename to zink/elko/src/utils/wasm.rs
diff --git a/zink/src/ffi/asm.rs b/zink/src/ffi/asm.rs
index b308531a0..16870b707 100644
--- a/zink/src/ffi/asm.rs
+++ b/zink/src/ffi/asm.rs
@@ -1,7 +1,5 @@
//! Assembly FFI.
-use crate::primitives::{Address, U256};
-
#[link(wasm_import_module = "asm")]
#[allow(improper_ctypes)]
extern "C" {
@@ -29,11 +27,45 @@ 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);
-
- /// Push u256 to stack
- pub fn push_u256(u256: U256);
+ /// Emit opcode ADDMOD
+ pub fn addmod_i8(a: i8, b: i8, n: i8) -> i8;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_i8(a: i8, b: i8, n: i8) -> i8;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_i16(a: i16, b: i16, n: i16) -> i16;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_i16(a: i16, b: i16, n: i16) -> i16;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_i32(a: i32, b: i32, n: i32) -> i32;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_i32(a: i32, b: i32, n: i32) -> i32;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_i64(a: i64, b: i64, n: i64) -> i64;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_i64(a: i64, b: i64, n: i64) -> i64;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_u8(a: u8, b: u8, n: u8) -> u8;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_u8(a: u8, b: u8, n: u8) -> u8;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_u16(a: u16, b: u16, n: u16) -> u16;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_u16(a: u16, b: u16, n: u16) -> u16;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_u32(a: u32, b: u32, n: u32) -> u32;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_u32(a: u32, b: u32, n: u32) -> u32;
+
+ /// Emit opcode ADDMOD
+ pub fn addmod_u64(a: u64, b: u64, n: u64) -> u64;
+ /// Emit opcode ADDMOD
+ pub fn mulmod_u64(a: u64, b: u64, n: u64) -> u64;
/// Revert with message in 32 bytes
pub fn revert1(message: &'static str);
@@ -71,9 +103,51 @@ 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;
+ /// Load a 8-bit signed integer from the transient storage.
+ pub fn tload_i8() -> i8;
+
+ /// Load a 8-bit unsigned integer from the transient storage.
+ pub fn tload_u8() -> u8;
+
+ /// Load a 16-bit signed integer from the transient storage.
+ pub fn tload_i16() -> i16;
+
+ /// Load a 16-bit unsigned integer from the transient storage.
+ pub fn tload_u16() -> u16;
+
+ /// Load a 32-bit signed integer from the transient storage.
+ pub fn tload_i32() -> i32;
+
+ /// Load a 32-bit unsigned integer from the transient storage.
+ pub fn tload_u32() -> u32;
+
+ /// Load a 64-bit signed integer from the transient storage.
+ pub fn tload_i64() -> i64;
+
+ /// Load a 64-bit unsigned integer from the transient storage.
+ pub fn tload_u64() -> u64;
+
+ /// Store a 8-bit signed integer to the transient storage.
+ pub fn tstore_i8(val: i8);
+
+ /// Store a 8-bit unsigned integer to the transient storage.
+ pub fn tstore_u8(val: u8);
+
+ /// Store a 16-bit signed integer to the transient storage.
+ pub fn tstore_i16(val: i16);
+
+ /// Store a 16-bit unsigned integer to the transient storage.
+ pub fn tstore_u16(val: u16);
+
+ /// Store a 32-bit signed integer to the transient storage.
+ pub fn tstore_i32(val: i32);
+
+ /// Store a 32-bit unsigned integer to the transient storage.
+ pub fn tstore_u32(val: u32);
+
+ /// Store a 64-bit signed integer to the transient storage.
+ pub fn tstore_i64(val: i64);
- /// Load address from storage
- pub fn sload_u256() -> U256;
+ /// Store a 64-bit unsigned integer to the transient storage.
+ pub fn tstore_u64(val: u64);
}
diff --git a/zink/src/ffi/bytes.rs b/zink/src/ffi/bytes.rs
new file mode 100644
index 000000000..83dbb3eb4
--- /dev/null
+++ b/zink/src/ffi/bytes.rs
@@ -0,0 +1,32 @@
+//! Bytes based instructions
+
+use crate::primitives::*;
+
+macro_rules! impl_bytes {
+ ($($count:expr),*) => {
+ #[link(wasm_import_module = "bytes")]
+ #[allow(improper_ctypes)]
+ extern "C" {
+ paste::paste! {
+ $(
+ #[doc = concat!("Push ", stringify!($count), " bytes to stack")]
+ pub fn [< push_bytes $count >] (bytes: [< Bytes $count >]);
+
+ #[doc = concat!("Load ", stringify!($count), " bytes from storage")]
+ pub fn [< sload_bytes $count >] () -> [< Bytes $count >];
+
+ #[doc = concat!("TLoad ", stringify!($count), " bytes from transient storage")]
+ pub fn [< tload_bytes $count >] () -> [< Bytes $count >];
+
+ #[doc = concat!("Check equal for bytes", stringify!($count))]
+ pub fn [< bytes $count _eq >] (this: [< Bytes $count >], other: [< Bytes $count >]) -> bool;
+ )*
+ }
+ }
+ };
+}
+
+impl_bytes!(
+ 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
+);
diff --git a/zink/src/ffi/evm.rs b/zink/src/ffi/evm.rs
index c3b00e297..fede71d7b 100644
--- a/zink/src/ffi/evm.rs
+++ b/zink/src/ffi/evm.rs
@@ -110,6 +110,12 @@ extern "C" {
/// Load a value from the storage
pub fn sload();
+ /// Store a value in the transient storage
+ pub fn tstore();
+
+ /// Load a value from the transient storage
+ pub fn tload();
+
/// Save word to memory
pub fn mstore();
@@ -119,12 +125,21 @@ extern "C" {
/// Load word from memory
pub fn mload();
+ /// Copy memory to memory
+ pub fn mcopy();
+
/// Compute Keccak-256 hash
pub fn keccak256();
/// Get the current message sender
pub fn caller() -> Address;
+ /// Get the current blob hash at index
+ pub fn blobhash();
+
+ /// Get the current blob base fee
+ pub fn blobbasefee();
+
/// Append log record with no topics
pub fn log0(name: &'static [u8]);
diff --git a/zink/src/ffi/mod.rs b/zink/src/ffi/mod.rs
index 9ffad9ef9..1b7e3b328 100644
--- a/zink/src/ffi/mod.rs
+++ b/zink/src/ffi/mod.rs
@@ -1,8 +1,7 @@
//! Zink FFI.
-
use crate::primitives::{Address, Bytes32, U256};
-
pub mod asm;
+pub mod bytes;
pub mod evm;
#[link(wasm_import_module = "zinkc")]
@@ -11,9 +10,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;
-
/// Equal operation for addresses
pub fn u256_add(this: U256, other: U256) -> U256;
@@ -25,10 +21,14 @@ extern "C" {
/// Equal operation for addresses
pub fn u256_max() -> U256;
-
+
/// Cast U256 to bytes32
pub fn cast_bytes32(value: U256) -> Bytes32;
+ /// Addmod operation for addresses
+ pub fn u256_addmod(this: U256, other: U256, modulus: U256) -> U256;
+ /// Equal operation for addresses
+ pub fn u256_mulmod(this: U256, other: U256, modulus: U256) -> U256;
/// Set up a label for reserving 32 bytes in memory
pub fn label_reserve_mem_32();
diff --git a/zink/src/lib.rs b/zink/src/lib.rs
index 3aeee8205..870538e3a 100644
--- a/zink/src/lib.rs
+++ b/zink/src/lib.rs
@@ -10,10 +10,9 @@ mod event;
pub mod ffi;
pub mod primitives;
pub mod storage;
-
pub use self::{asm::Asm, event::Event};
-pub use storage::{DoubleKeyMapping, Mapping, Storage};
-pub use zink_codegen::{external, revert, storage, Event};
+pub use storage::{DoubleKeyMapping, Mapping, Storage, TransientStorage};
+pub use zink_codegen::{assert, external, revert, storage, transient_storage, Event};
/// Generate a keccak hash of the input (sha3)
#[cfg(not(target_family = "wasm"))]
diff --git a/zink/src/primitives/bytes.rs b/zink/src/primitives/bytes.rs
new file mode 100644
index 000000000..8e4533b3a
--- /dev/null
+++ b/zink/src/primitives/bytes.rs
@@ -0,0 +1,65 @@
+//! Fixed bytes
+use crate::{ffi, storage::StorageValue, Asm};
+use paste::paste;
+
+macro_rules! impl_bytes {
+ ($count:expr) => {
+ paste! {
+ #[derive(Debug, Clone, Copy)]
+ pub struct [] (
+ #[allow(unused)]
+ #[cfg(target_family = "wasm")] i32,
+ #[cfg(not(target_family = "wasm"))] pub [u8; $count],
+ );
+
+ impl [] {
+ /// Returns empty bytes
+ #[cfg(target_family = "wasm")]
+ pub const fn empty() -> Self {
+ [](0)
+ }
+
+ #[cfg(not(target_family = "wasm"))]
+ pub const fn empty() -> Self {
+ []([0; $count])
+ }
+
+ /// if self equal to another
+ #[allow(clippy::should_implement_trait)]
+ #[inline(always)]
+ pub fn eq(self, other: Self) -> bool {
+ paste::paste! {
+ unsafe { ffi::bytes::[< bytes $count _eq >](self, other) }
+ }
+ }
+ }
+
+ impl Asm for [] {
+ fn push(self) {
+ unsafe { ffi::bytes::[](self) }
+ }
+
+ #[cfg(not(target_family = "wasm"))]
+ fn bytes32(&self) -> [u8; 32] {
+ let mut output = [0; 32];
+ output[(32-$count)..].copy_from_slice(&self.0);
+ output
+ }
+ }
+
+ impl StorageValue for [] {
+ fn sload() -> Self {
+ unsafe { ffi::bytes::[]() }
+ }
+ }
+ }
+ };
+ ($($count:expr),+) => {
+ $(impl_bytes!($count);)+
+ };
+}
+
+impl_bytes! {
+ 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
+}
diff --git a/zink/src/primitives/mod.rs b/zink/src/primitives/mod.rs
index 67b4af318..f703ad767 100644
--- a/zink/src/primitives/mod.rs
+++ b/zink/src/primitives/mod.rs
@@ -1,11 +1,12 @@
//! Zink primitive types
mod address;
+pub mod bytes;
+pub mod numeric;
mod u256;
-pub use address::Address;
-pub use u256::U256;
+pub use {address::Address, bytes::*, u256::U256};
-pub type Bytes20 = Address;
-pub type Bytes32 = U256;
+// pub type Address = Bytes20;
+// pub type Bytes32 = U256;
pub type String32 = U256;
diff --git a/zink/src/primitives/numeric.rs b/zink/src/primitives/numeric.rs
new file mode 100644
index 000000000..b819c6200
--- /dev/null
+++ b/zink/src/primitives/numeric.rs
@@ -0,0 +1,35 @@
+use crate::ffi;
+
+/// A trait for modular arithmetic operations on numeric types.
+pub trait Numeric: Copy {
+ fn addmod(self, other: Self, n: Self) -> Self;
+ fn mulmod(self, other: Self, n: Self) -> Self;
+}
+
+macro_rules! impl_numeric {
+ ($($t:ty, $addmod_fn:ident, $mulmod_fn:ident);* $(;)?) => {
+ $(
+ impl Numeric for $t {
+ #[inline(always)]
+ fn addmod(self, other: Self, n: Self) -> Self {
+ unsafe { ffi::asm::$addmod_fn(n, other, self) }
+ }
+ #[inline(always)]
+ fn mulmod(self, other: Self, n: Self) -> Self {
+ unsafe { ffi::asm::$mulmod_fn(n, other, self) }
+ }
+ }
+ )*
+ };
+}
+
+impl_numeric! {
+ i8, addmod_i8, mulmod_i8;
+ u8, addmod_u8, mulmod_u8;
+ i16, addmod_i16, mulmod_i16;
+ u16, addmod_u16, mulmod_u16;
+ i32, addmod_i32, mulmod_i32;
+ u32, addmod_u32, mulmod_u32;
+ i64, addmod_i64, mulmod_i64;
+ u64, addmod_u64, mulmod_u64;
+}
diff --git a/zink/src/storage/dkmapping.rs b/zink/src/storage/dkmapping.rs
index d2c26c2c5..003f9a818 100644
--- a/zink/src/storage/dkmapping.rs
+++ b/zink/src/storage/dkmapping.rs
@@ -1,6 +1,10 @@
//! Double key mapping
-use crate::{ffi, storage::StorageValue, Asm};
+use crate::{
+ ffi,
+ storage::{StorageValue, TransientStorageValue},
+ Asm,
+};
/// Storage mapping interface
pub trait DoubleKeyMapping {
@@ -31,6 +35,35 @@ pub trait DoubleKeyMapping {
}
}
+/// Transient storage mapping interface
+pub trait DoubleKeyTransientMapping {
+ const STORAGE_SLOT: i32;
+
+ type Key1: Asm;
+ type Key2: Asm;
+ type Value: TransientStorageValue;
+
+ #[cfg(not(target_family = "wasm"))]
+ fn storage_key(key1: Self::Key1, key2: Self::Key2) -> [u8; 32];
+
+ /// Get value from transient storage key.
+ #[inline(always)]
+ fn get(key1: Self::Key1, key2: Self::Key2) -> Self::Value {
+ load_double_key(key1, key2, Self::STORAGE_SLOT);
+ Self::Value::tload()
+ }
+
+ /// Set key and value in transient storage
+ #[inline(always)]
+ fn set(key1: Self::Key1, key2: Self::Key2, value: Self::Value) {
+ value.push();
+ load_double_key(key1, key2, Self::STORAGE_SLOT);
+ unsafe {
+ ffi::evm::tstore();
+ }
+ }
+}
+
/// Load storage key to stack
#[inline(always)]
fn load_double_key(key1: impl Asm, key2: impl Asm, index: i32) {
diff --git a/zink/src/storage/mapping.rs b/zink/src/storage/mapping.rs
index 669c8f836..d38b1c201 100644
--- a/zink/src/storage/mapping.rs
+++ b/zink/src/storage/mapping.rs
@@ -1,6 +1,10 @@
//! Storage Mapping
-use crate::{ffi, storage::StorageValue, Asm};
+use crate::{
+ ffi,
+ storage::{StorageValue, TransientStorageValue},
+ Asm,
+};
/// Storage mapping interface
pub trait Mapping {
@@ -28,6 +32,32 @@ pub trait Mapping {
}
}
+/// Transient storage mapping interface
+pub trait TransientMapping {
+ const STORAGE_SLOT: i32;
+
+ type Key: Asm;
+ type Value: TransientStorageValue;
+
+ #[cfg(not(target_family = "wasm"))]
+ fn storage_key(key: Self::Key) -> [u8; 32];
+
+ /// Get value from transient storage key.
+ fn get(key: Self::Key) -> Self::Value {
+ load_key(key, Self::STORAGE_SLOT);
+ Self::Value::tload()
+ }
+
+ /// Set key and value in transient storage
+ fn set(key: Self::Key, value: Self::Value) {
+ value.push();
+ load_key(key, Self::STORAGE_SLOT);
+ unsafe {
+ ffi::evm::tstore();
+ }
+ }
+}
+
/// Load storage key to stack
fn load_key(key: impl Asm, index: i32) {
unsafe {
diff --git a/zink/src/storage/mod.rs b/zink/src/storage/mod.rs
index d7ecc018e..3d16da345 100644
--- a/zink/src/storage/mod.rs
+++ b/zink/src/storage/mod.rs
@@ -1,7 +1,11 @@
//! Zink storage implementation.
use crate::{ffi, Asm};
-pub use {dkmapping::DoubleKeyMapping, mapping::Mapping, value::Storage};
+pub use {
+ dkmapping::{DoubleKeyMapping, DoubleKeyTransientMapping},
+ mapping::{Mapping, TransientMapping},
+ value::{Storage, TransientStorage},
+};
mod dkmapping;
mod mapping;
@@ -13,6 +17,12 @@ pub trait StorageValue: Asm {
fn sload() -> Self;
}
+/// Interface for the value of kv based transient storage
+pub trait TransientStorageValue: Asm {
+ /// Load from transient storage
+ fn tload() -> Self;
+}
+
impl StorageValue for i32 {
fn sload() -> Self {
unsafe { ffi::asm::sload_i32() }
@@ -24,3 +34,15 @@ impl StorageValue for u32 {
unsafe { ffi::asm::sload_u32() }
}
}
+
+impl TransientStorageValue for i32 {
+ fn tload() -> Self {
+ unsafe { ffi::asm::tload_i32() }
+ }
+}
+
+impl TransientStorageValue for u32 {
+ fn tload() -> Self {
+ unsafe { ffi::asm::tload_u32() }
+ }
+}
diff --git a/zink/src/storage/value.rs b/zink/src/storage/value.rs
index d3c5caa3d..20d817fee 100644
--- a/zink/src/storage/value.rs
+++ b/zink/src/storage/value.rs
@@ -1,5 +1,9 @@
//! Key-Value storage
-use crate::{ffi, storage::StorageValue, Asm};
+use crate::{
+ ffi,
+ storage::{StorageValue, TransientStorageValue},
+ Asm,
+};
/// Storage trait. Currently not for public use
pub trait Storage {
@@ -24,3 +28,27 @@ pub trait Storage {
}
}
}
+
+/// Transient storage trait. Currently not for public use
+pub trait TransientStorage {
+ #[cfg(not(target_family = "wasm"))]
+ const STORAGE_KEY: [u8; 32];
+ const STORAGE_SLOT: i32;
+
+ type Value: TransientStorageValue + Asm;
+
+ /// Get value from transient storage.
+ fn get() -> Self::Value {
+ Asm::push(Self::STORAGE_SLOT);
+ Self::Value::tload()
+ }
+
+ /// Set value to transient storage.
+ fn set(value: Self::Value) {
+ value.push();
+ Asm::push(Self::STORAGE_SLOT);
+ unsafe {
+ ffi::evm::tstore();
+ }
+ }
+}
diff --git a/zint/Cargo.toml b/zink/zint/Cargo.toml
similarity index 100%
rename from zint/Cargo.toml
rename to zink/zint/Cargo.toml
diff --git a/zint/README.md b/zink/zint/README.md
similarity index 100%
rename from zint/README.md
rename to zink/zint/README.md
diff --git a/zint/src/bytes.rs b/zink/zint/src/bytes.rs
similarity index 100%
rename from zint/src/bytes.rs
rename to zink/zint/src/bytes.rs
diff --git a/zint/src/contract.rs b/zink/zint/src/contract.rs
similarity index 100%
rename from zint/src/contract.rs
rename to zink/zint/src/contract.rs
diff --git a/zint/src/evm.rs b/zink/zint/src/evm.rs
similarity index 99%
rename from zint/src/evm.rs
rename to zink/zint/src/evm.rs
index b12316282..93342c6b2 100644
--- a/zint/src/evm.rs
+++ b/zink/zint/src/evm.rs
@@ -43,7 +43,7 @@ impl<'e> Default for EVM<'e> {
}
}
-impl<'e> EVM<'e> {
+impl EVM<'_> {
/// Interpret runtime bytecode with provided arguments
pub fn interp(runtime_bytecode: &[u8], input: &[u8]) -> Result {
Self::default()
diff --git a/zint/src/lib.rs b/zink/zint/src/lib.rs
similarity index 100%
rename from zint/src/lib.rs
rename to zink/zint/src/lib.rs
diff --git a/zint/src/lookup.rs b/zink/zint/src/lookup.rs
similarity index 100%
rename from zint/src/lookup.rs
rename to zink/zint/src/lookup.rs
diff --git a/zint/tests/addition.rs b/zink/zint/tests/addition.rs
similarity index 100%
rename from zint/tests/addition.rs
rename to zink/zint/tests/addition.rs