# Hello, Zinc!
### Intro
Zinc 是一套程式語言用來開發
1. Smart contract on zkSync.
2. General-purpose zero-knowledge proof circuits.
有鑒於現存的 ZKP frameworks 缺乏支持 smart contract 的特定功能。以及近期的新興 smart contract 語言,例如 Simplicity or Libra's Move,其設計上比起開發可讀性,更重視 safety 以及程式本身的正規可驗證性。
>The goal of Zinc is to make writing safe zero-knowledge circuits and ZKP-based smart contracts easy.
### Zinc 特點
- Type safety
* 字串跟整數無法交互運算
- Type inference
* 根據數值直接決定型別
- Immutability
- Movable resources as a first-class citizen
- Module definition and import
- Expressive syntax
- Industrial-grade compiler optimizations
- Flat learning curve for Rust/JS/Solidity/C++ developers
- **Turing incompleteness: no recursion or unbounded looping**
### 語法使用
以下使用的版本為 zinc 0.2.3
1. const and variable
```
let x = 5566;
x = 2266; // compile error!
let mut y = 5566;
y = 2266; // ok!
```
2. types
- Unit `()`
* 類似 void
* 沒辦法做運算也不能被 cast
```
fn check(value: bool) {
// several statements
};
let y = check(true); // y is ()
```
- Boolean: true/false, 0/1
* 基於 type safety,不能 cast 為 integer 做運算
- Integer
* u8 .. u248: unsigned integers
* 8 的倍數
* i8 .. i248: signed integers
* 8 的倍數
* field: the native field integer.
* It represents an unsigned integer of bitlength equal to the field modulus length (e.g. for BN256 the field modulus length is 254 bit).
* decimal: 0, 1, 122, 574839572494237242
* hexadecimal: 0x0, 0xfa, 0x0001, 0x1fffDEADffffffffffBEEFffff
* Casting: only between integer <-> field. If the value does not fit into the target type, it is truncated.
* If the literal type is not specified, the minimal possible bitlength is inferred.
```
let a = 0; // u8
let a: i24 = 0; // i24
let b = 256; // u16
let c = -1; // i8
let c = -129; // i16
let d = 0xff as field; // field
let e: field = 0; // field
```
- Array
```
let mut fibbonaci = [0, 1, 1, 2, 3, 5, 8, 13];
let element = fibbonaci[3];
fibbonaci[2] = 1;
```
* bit array:
```
let preimage_bits: [bool; 156]
```
- Tuple
```
let mut tuple: (u8, field) = (0xff, 0 as field);
tuple.0 = 42;
dbg!("{}", tuple.1);
```
- Struct
```
struct Arithmetic {
x: field,
y: field,
}
impl Arithmetic {
pub fn add(self) -> field {
self.a + self.b
}
}
fn main() {
let arithmetic = Arithmetic { a: 10 as field, b: 5 as field };
}
```
- Enum
```
enum Prize {
Gold = 1,
Silver = 2,
}
impl Prize {
pub fn first() -> Self {
Self::Gold
}
pub fn second() -> Self {
Self::Silver
}
}
fn main(score: field) -> field {
let x = Prize::Gold;
List::first() as field * score
}
```
- String
* 使用非常受限,目前只能用在 debug or require message
```
dbg!("{}", 42); // format string
require(true != false, "a very obvious fact"); // optional error message
```
- Map
```
use std::collections::MTreeMap;
contract Test {
data: MTreeMap<u8, field>;
pub fn new(owner: u160) -> Self {
Self {
data: MTreeMap,
}
}
pub fn example(mut self) {
let (old1, existed1) = self.data.insert(42, 9 as field);
let (value, exists1) = self.data.get(42);
let exists2 = self.data.contains(42);
let (old2, existed2) = self.data.remove(42);
}
}
```
- Type alias
```
type ComplexType = [(u8, [bool; 8], field); 16];
```
3. Fucntion
- public function: with `pub` keyword
```
impl Data {
pub fn sum(self) -> u8 {
self.a + self.b + self.c + self.d
}
}
```
- const function
* Called at compile-time, thus they may only accept and return constant expressions.
* Useful when you need to use a lot of similar parameterized values.
```
const fn cube(x: u64) -> u64 { x * x * x }
fn main() {
let cubed_ten = cube(10 as u64); // 1000
let cubed_twenty = cube(20 as u64); // 8000
}
```
4. Conditions
- If
```
let x = 1;
let c = if x == 1 {
let a = 5;
a
} else if x == 2 {
let b = 10;
b
};
```
- match
* constant (e.g. 42)
* path (e.g. MyEnum::ValueOne)
* variable binding (e.g. value)
* wildcard (_)
```
const SCRUTINEE: u8 = 42;
const MATCH: u8 = match SCRUTINEE {
0 => 10,
1 => 20,
42 => 100,
VALUE => 255 - VALUE,
};
```
5. Control
- for-while loop
```
for {identifier} in {range} [while {expression}] {
...
}
```
```
let x = 7;
for i in 0..10 while i % x != 2 {
// do something
};
```
6. Debugging
Use special function `dbg!`
```
// a = 5, b = 3
fn print_sum(a: u8, b: u8) {
dbg!("{} + {} = {}", a, b, a + b); // prints '5 + 3 = 8'
}
```
7. Testing
- Unit tests are just simple functions marked with the `#[test]`
- `#[should_panic]` such test must fail in order to succeed, e.g. by passing a false value to the require function or causing an overflow.
- `#[ignore]` such test is just ignored.
```
#[test]
fn ordinar() {
require(2 + 2 == 4, "The laws of the Universe have been broken");
}
#[test]
#[should_panic]
fn panicking() {
require(2 + 2 == 5, "And it's okay");
}
#[test]
#[ignore]
fn ignored() {
require(2 + 2 > 4, "So we'll just ignore it");
}
```
8. Smart contract
- Implicit storage fields
* contract address: u160, ex: 0x224a0b11b36156c7fa4c4d784a8b85c93978e424
* the contract balances: Map<u160, u248>, <address, amount>
* Ref: https://rinkeby.zkscan.io/explorer/accounts/0x224a0b11b36156c7fa4c4d784a8b85c93978e424
- Explicit storage fields
* 每一個 smart contract 的 storage 都會被 Zinc Zandbox server 寫入 persistent databases.
```
contract Example {
pub tokens: (u8, u64);
data: [u8; 1000];
//...
}
```
- Constructor
```
contract Example {
pub value: u64;
pub fn new(_value: u64) -> Self {
Self {
value: _value,
}
}
}
```
- Public methods
* The contract must have at least one public function.
```
contract Example {
//...
pub fn deposit(mut self, amount: u64) -> bool { ... }
}
```
- Private methods
```
contract Example {
//...
fn get_balance(address: u160) -> bool { ... }
}
```
- Builtin methods
* transfer
* fetch
* dbg
* require
- Global variables
* zksync::msg
```
zksync::msg:sender //u160
zksync::msg:recipient //u160
zksync::msg:token_address //u160
zksync::msg:amount //u160
```
- Constants
```
contract Example {
//...
pub const VERSION: u8 = 1; // public constant
const LIMIT: u8 = 255; // private constant
}
```
### Hello, World!
#### Install zargo
下載對應的版本,解壓縮之後會有三個 bin: zargo, znc, zvm
實際上開發只需要 zargo
https://github.com/matter-labs/zinc/releases/tag/0.2.3
> macos 第一次執行會被 Security&Privacy 擋住,需要先 approve
#### Create project
```
$ zargo new --type contract constant_price
```
```
//!
//! The 'constant_price' contract entry.
//!
contract ConstantPrice {
pub value: u64;
pub fn new(value: u64) -> Self {
Self {
value: value,
}
}
}
```
#### Try to code something
- removing the redundant value auto-generated field
- adding the fee parameter
- adding two mutable methods for making exchanges and deposits
- adding an immutable method for getting the contract fee value
- declaring the Address and Balance type aliases
- declaring the TokenAddress enumeration type with zkSync token addresses
Ref: https://zinc.zksync.io/07-smart-contracts/02-minimal-example.html
#### Compile
```
$ zargo build
```
會產生
- private_key: 用來部署 smart contract
- ./data/input.json: 部署 contract 的初始化參數
#### 部署到 Zinc testnet
1. Create zkSync account
zkSync on rinkeby: https://rinkeby.zksync.io/
Mint tokens on rinkeby: https://mint.zksync.dev/mint
> NOTE: unlock account by transfering at first time
2. Publish contract to rinkeby
```
$ zargo publish --network rinkeby --instance default
```
Response:
```
Compiling hello_swap v0.1.0
Finished release [optimized] target
Uploading the instance `default` of `hello_swap v0.1.0` to network `rinkeby`
Address 0x9035a2b7cf9365e879ec26415d97a954cd68955f
Account ID 190788
```
#### Query contract storage
```
$ zargo query --network rinkeby --address 0x9035a2b7cf9365e879ec26415d97a954cd68955f
```
Response:
```
{
"address": "0x9035a2b7cf9365e879ec26415d97a954cd68955f",
"balances": [
{
"key": "0x0",
"value": "64000000000000"
}
],
"fee": "100"
}
```
#### Calling a non-mutable contract method
```
$ zargo query --network rinkeby --address 0x9035a2b7cf9365e879ec26415d97a954cd68955f --method get_fee
```
Response:
```
{
"output": "100"
}
```
#### Calling a mutable contract method
修改 ./data/input.json
```
{
"msg": {
"sender": "0x009a0256ca6b16455f5b3a46b7aee448f929a45b",
"recipient": "0x9035a2b7cf9365e879ec26415d97a954cd68955f",
"token_address": "0xeb8f08a975ab53e34d8a0330e0d34de942c95926", // USDC
"amount": "1_E6"
}
}
```
Request:
```
$ zargo call --network rinkeby --address 0x9035a2b7cf9365e879ec26415d97a954cd68955f --method deposit
```
Response:
```
{
"output": {
"result": null,
"root_hash": "0x0"
}
}
```
Query storage again:
```
{
"address": "0x9035a2b7cf9365e879ec26415d97a954cd68955f",
"balances": [
{
"key": "0x0",
"value": "64000000000000"
},
{
"key": "0xeb8f08a975ab53e34d8a0330e0d34de942c95926",
"value": "1000000"
}
],
"fee": "100"
}
```
#### Calling a `exchange` method
```
$ zargo call --network rinkeby --address 0x9035a2b7cf9365e879ec26415d97a954cd68955f --method exchange
```
實際上會產生出 2 筆 tx
Ref: https://rinkeby.zkscan.io/explorer/accounts/0x009a0256ca6b16455f5b3a46b7aee448f929a45b
### Discussion
1. 如何做合約升級? proxy contract?
2. 無限 fetch 另一個 contract?
### Reference
- https://github.com/matter-labs/zinc
- zkSync wallet: https://wallet.zksync.io/
- mint Token: https://mint.zksync.dev/
- zinc v0.2.3: https://zinc.zksync.io/index.html
- zinc v0.1.5: https://zinc.matterlabs.dev/09-zargo-circuit-manager/00-overview.html
- zkSync 1.x rinkeby: https://rinkeby.zkscan.io/
- zkSync 2.0 rinkeby: https://zksync2-alpha.zkscan.io/explorer/
- EthCC: ZKSYNC — FIRST EVM-COMPATIBLE ZKROLLUP, Alex Gluchowski @ July 21st 11:00 am
* https://ethcc.io/agenda
- 更多期待 https://medium.com/matter-labs/zksync-2-0-hello-ethereum-ca48588de179