Skip to content

Commit fde4211

Browse files
authored
chore(2024): migrate previous articles about zink from the personal blog (#4)
Migrate previous blogs of clearloop in 2024 to the zink official
1 parent acb6def commit fde4211

File tree

4 files changed

+448
-1
lines changed

4 files changed

+448
-1
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
author: "cydonia"
3+
date: "2024-02-03"
4+
labels: ["cydonia", "rust"]
5+
description: "Thoughts about the storage implementation in zink."
6+
title: "Thoughts of ERC-20 in Zink"
7+
---
8+
9+
Have been stuck in the development of zink for around 2 months, feeling my life is worthless though these days since
10+
I haven't done anything that I'm feeling proud of.
11+
12+
The ERC20 implementation in zink is a big picture which leads zink to a real programming language of EVM when it gets
13+
completed, I'm too scared to see that something **solidity** can do but **zink** can not that I could not even push one
14+
more commit in zink these days.
15+
16+
## Splitting Problems
17+
18+
Another night without sleeping, tired of video games again finally, get up and check the code base of zink, I found that
19+
I have already cleaned the code structure of the code generation module last time which makes it easier to catch up my
20+
previous work this time.
21+
22+
What if I just start my work on ERC20, splitting the problems out into issues like always, the layout of [ERC20][erc20] is
23+
pretty simple, ERCs in zink will be implemented in rust traits without doubts.
24+
25+
```solidity
26+
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
27+
mapping(address account => uint256) private _balances;
28+
29+
// ...
30+
31+
/**
32+
* @dev Sets the values for {name} and {symbol}.
33+
*
34+
* All two of these values are immutable: they can only be set once during
35+
* construction.
36+
*/
37+
constructor(string memory name_, string memory symbol_) {
38+
_name = name_;
39+
_symbol = symbol_;
40+
}
41+
}
42+
```
43+
44+
45+
## Storage
46+
47+
Mapping storage is missing in zink for now bcz I haven't got a perfect idea for passing bytes from rust to evm yet.
48+
49+
```solidity
50+
mapping(address account => uint256) private _balances;
51+
```
52+
53+
But for doing it in hard way, I have already got an idea from the design of my old friend [ink][ink], for the [Mapping][ink-mapping]
54+
storage implementation in **ink**, they are mapping pairs into storage slots, mb it doesn't look clever but it seems that this is
55+
the best solution anyway.
56+
57+
```rust
58+
pub struct Mapping<K, V: Packed, KeyType: StorageKey = AutoKey> {
59+
#[allow(clippy::type_complexity)]
60+
_marker: PhantomData<fn() -> (K, V, KeyType)>,
61+
}
62+
```
63+
64+
I was actually struggling with how to use rust's **BTreeMap** in zink before, using **BTreeMap** is friendly to
65+
rust developers because we are familiar with it, however there are two big problems in it:
66+
67+
- The methods of **BTreeMap** or any **Iterator** requires allocation from memory which will embed tons of code
68+
to support it.
69+
- Even if we are okay with the tons of allocation code, store a encoded **BtreeMap** in evm takes a lot anyway,
70+
bcz we need to handle encoding/decoding stuffs which burns gas a lot.
71+
72+
In conclusion, due to the two points above, it is very unfortunate that using Rust's **BtreeMap** in zink is not
73+
a proper solution.
74+
75+
### Store `bytes` in zink!
76+
77+
Zink currently only support `i32` in storage, bcz I haven't got a solution passing bytes elegantly yet, but for moving forward
78+
to the goal of ERC20, I compromise to implement it anyway, the current solution requires an `Abi` trait _(I hate the naming `Abi`,
79+
because it can describe too many interfaces in zink xd)_.
80+
81+
```rust
82+
pub trait Abi {
83+
fn write(&self) -> Result<()>;
84+
}
85+
86+
// ... impl Abi for number types
87+
impl Abi for i32 {}
88+
89+
// ... impl Abi for bytes
90+
impl Abi for [u8; 0] {}
91+
impl Abi for [u8; 1] {}
92+
// ...
93+
impl Abi for [u8; 32] {}
94+
```
95+
96+
`sstore` requires two stack inputs as key and value, for simplifying the problem, for the types which implements `Abi`, we only need to
97+
make sure that they can be transformed into bytes under the length 32 at the moment.
98+
99+
```yul
100+
PUSH0 // key
101+
PUSH0 // value
102+
SSTORE
103+
```
104+
105+
Thus we need generate matched FFI for these stuffs as well, which is closed to the assembly implementation,
106+
107+
```rust
108+
mod ffi {
109+
fn store_key_i32_value_i32(key: i32, value: i32) {}
110+
fn store_key_i32_value_bytes(key: i32, ptr: i32, length: i32) {}
111+
// ... everything
112+
}
113+
```
114+
115+
I love writing macros in rust but not the generated code like above looks really ugly, but seems I have to do it now anyway.
116+
117+
118+
### Store maps in zink
119+
120+
I don't like the naming `mapping` because it is too long, so if I can choose, I'll use `map` because it is shorter, typescript is using `Map`
121+
or `Record` as well, so I don't understand why solidity is using the keyword `mapping`, mb because they have token `=>` in the storage declaration,
122+
and they want to express **ing**.
123+
124+
So for `Map` in zink, it will follow the well-designed `Mapping` in `ink`, provided `Key`, `Value`, and `Prefix`, the problem will be concatenating
125+
the storage key in zink.
126+
127+
And the solution is using macro, again:
128+
129+
```rust
130+
mod ffi {
131+
fn store_map_key_i32_value_i32(prefix: i32, key: i32, value: i32) {}
132+
fn store_map_key_i32_value_bytes(prefix: i32, key: i32, ptr: i32, length: i32) {}
133+
}
134+
```
135+
However, we can fix `i32` as prefix this time, because the storage keys of a contract may never reach the max limit of `i32`.
136+
137+
## Interfaces
138+
139+
After solving the storage problem, the next one is the design of `interface` in zink, like mentioned above, we can use trait without doubts, but the
140+
problem is that we need to export the methods provided by traits to WASM as well, hmm, this problem leads us to a derive macro,
141+
142+
```rust
143+
144+
#[derive(Erc20)]
145+
struct MyContract {}
146+
147+
// Which generates
148+
149+
#[no_mangle]
150+
extern "C" fn total_supply() {}
151+
```
152+
153+
Looks weird, but it works, should zink ask users to use `MyContract` to define the namespace is a problem as well, maybe we can provide different
154+
solutions for this first, but as the experience from apple, we'd better only give users the best solution finally at this kind of points.
155+
156+
## Errors and Events
157+
158+
Errors and events are about to be refactored as well since now we have a solution for passing bytes in zink now, even it is ugly, but it is best
159+
solution for now ))
160+
161+
[ink]: https://github.com/paritytech/ink
162+
[ink-mapping]: https://docs.rs/ink/latest/ink/storage/struct.Mapping.html
163+
[erc20]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
author: "tianyi"
3+
date: "2024-09-14"
4+
labels: ["zink", "evm"]
5+
description: "Do we really need constructor?"
6+
title: "Constructor JIT in Zink?"
7+
---
8+
9+
The constructor implementation of zink was following solidity at the beginning till I found that I actually
10+
missed the tests of it, and the result is, my implementation does not work at all... So I have to back to
11+
fix the constructor in zink to continue the ERC20 implementation.
12+
13+
While doing this dirty work, I realized that the constructor implementation in solidity is actually overkilled
14+
for zink since it is a bit too verbose for adapting corner cases.
15+
16+
## Constructor Layout in Solidity
17+
18+
According to [Layout of Call Data][sol-calldata], solidity compiler needs to process `codecopy`, `mload`, etc.
19+
for loading parameters from the end of the bytecode and passing them to the constructor function, for example:
20+
21+
```yul
22+
// @program: set storage in constructor and get it in the deployed contract.
23+
24+
// init code
25+
//
26+
// 1. Copy parameters to memory
27+
// 2. Load parameter to stack
28+
// 3. store parameter in storage
29+
PUSH1, 0x01, PUSH1, 0x17, PUSH1, 0x1f, CODECOPY, PUSH0, MLOAD, PUSH0, SSTORE
30+
31+
// return logic - calculate the position of runtime bytecode and return it
32+
PUSH1, 0x02, PUSH1, 0x15, PUSH0, CODECOPY, PUSH1, 0x02, PUSH0, RETURN
33+
34+
// runtime bytecode - Load the parameter set in constructor from storage
35+
PUSH0, MLOAD
36+
37+
// parameters - 0x42
38+
PUSH1, 0x42
39+
```
40+
41+
The instructions could also be like below if we compile the init code just in time
42+
43+
```yul
44+
// init code - just push the parameter
45+
PUSH1, 0x42, PUSH0, SSTORE
46+
47+
// return logic - same
48+
PUSH1, 0x02, PUSH1, 0x0d, PUSH0, CODECOPY, PUSH1, 0x02, PUSH0, RETURN
49+
50+
// runtime bytecode - same
51+
PUSH0, MLOAD
52+
53+
// parameters - none
54+
```
55+
56+
But if so, why solidity takes so much for loading constructor parameters instead of compiles them on creation? If
57+
I'm not mistaken, they may just want to adapt the case that deploying contracts based on on-chain data, block number,
58+
timestamp, etc. or the state of deployed contracts.
59+
60+
## No Constructor but `Constructor` in Zink
61+
62+
Considering about the constructor is only used for presetting storage most of the time, pro-macro `constructor` in
63+
zink is finally removed in [#229][#229], and `Constructor` is introduced instead for wrapping runtime bytecode on
64+
demand, as a code snippet:
65+
66+
```rust
67+
use zinkc::Constructor;
68+
69+
fn to_bytecode(runtime_bytecode: &[u8]) -> Result<Vec<u8>> {
70+
let mut constructor = Constructor::default();
71+
let bytecode = constructor
72+
.storage(&[(b"storage_key", b"storage_value")].collect())?
73+
.finished(runtime_bytecode);
74+
}
75+
```
76+
77+
There will be no constructor but `Constructor` in zink...
78+
79+
80+
## Future Plans
81+
82+
I'm now pushing the ERC20 implementation straightforwardly since it has been the next milestone since last year... I
83+
have to admit that I failed this dreaming project in the past days of 2024, 3 months, let's see what we'll archive in 2024.
84+
85+
86+
[#229]: https://github.com/zink-lang/zink/pull/229
87+
[sol-calldata]: https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html#layout-of-call-data

0 commit comments

Comments
 (0)