StarkNet 主网仍处于Alpha 阶段,核心的代码(Prover, StarkNet OS, L1 core Contract)尚未开源。
![](https://img.haomeiwen.com/i7973505/4458fc765b3e6dbe.png)
基本概念
区块结构
区块头的结构为:
![](https://img.haomeiwen.com/i7973505/748b9014862355c3.png)
交易结构
StarkNet 支持两种交易类型:
- invoke 交易: 主要用来调用合约函数;
- declare 交易: 主要用来引入新的classes;
deploy 交易目前从StarkNet移除,部署合约可以采用syscall
调用。
invoke 交易的结构为:
![](https://img.haomeiwen.com/i7973505/7ba39095d4594cbc.png)
declare 交易的结构为:
![](https://img.haomeiwen.com/i7973505/146c0483ea0b0650.png)
合约
合约ABI
合约ABI 为JSON 格式:
starknet-compile contract.cairo \
--output contract_compiled.json \
--abi contract_abi.json
合约地址
采用pedersen
hash计算:
contract_address := pedersen(
“STARKNET_CONTRACT_ADDRESS”,
caller_address,
salt,
pedersen(contract_code),
pedersen(constructor_calldata))
合约类(Classes)
类似于面向对象编程,StarkNet 将合约的实现分为类(Class)和 实例(Instance).
合约类由Cairo 字节码定义,通过类hash识别; 合约实例对应着合约类,有相应的存储空间,能被交易和其它合约调用。
合约类并非特别需要部署合约实例;
合约类通过declare
交易添加; 合约实例通过deploy
系统调用实现。
可以使用library_call
调用合约类代码,类似于以太坊的deletgatecall
.
合约存储
合约有个存储slots, 初始值为0;
存储值的读取:
let (value) = storage_read(key)
存储什的写入:
storage_write(key, value);
系统调用
合约调用的方法为:
func call_contract{syscall_ptr : felt*}(
contract_address : felt, function_selector : felt, calldata_size : felt, calldata : felt*
) -> (retdata_size : felt, retdata : felt*)
合约部署的语法为:
func deploy{syscall_ptr : felt*}(
class_hash : felt, // 合约类的Hash
contract_address_salt : felt,
constructor_calldata_size : felt,
constructor_calldata : felt*,
) -> (contract_address : felt)
抛出事件为:
func emit_event{syscall_ptr : felt*}(keys_len : felt, keys : felt*, data_len : felt, data : felt*)
获取区块号:
func get_block_number{syscall_ptr : felt*}() -> (block_number : felt)
获取区块的时间戳
func get_block_timestamp{syscall_ptr : felt*}() -> (block_timestamp : felt)
获取调用者的地址:
func get_caller_address{syscall_ptr : felt*}() -> (caller_address : felt)
获取合约的地址:
func get_contract_address{syscall_ptr : felt*}() -> (contract_address : felt)
获取sequencer的地址
func get_sequencer_address{syscall_ptr : felt*}() -> (sequencer_address : felt)
获取原始交易的信息
func get_tx_info{syscall_ptr : felt*}() -> (tx_info : TxInfo*)
调用合约类方法,类似于delegatecall 方法:
func library_call{syscall_ptr : felt*}(
class_hash : felt, function_selector : felt, calldata_size : felt, calldata : felt*
) -> (retdata_size : felt, retdata : felt*)
调用L1 handler
func library_call_l1_handler{syscall_ptr : felt*}(
class_hash : felt, function_selector : felt, calldata_size : felt, calldata : felt*
) -> (retdata_size : felt, retdata : felt*)
向L1 发送消息
func send_message_to_l1{syscall_ptr : felt*}(
to_address : felt, payload_size : felt, payload : felt*
)
读取合约存储的值
func storage_read{syscall_ptr : felt*}(address : felt) -> (value : felt)
向合约存储写入值
func storage_write{syscall_ptr : felt*}(address : felt, value : felt)
Account abstraction
抽象账户可以实现:
- signature abstract: 更改验证逻辑;
- fee abstraction: 可以用不同的token 支付手续费;
- Nonce abstraction: 支持并行广播交易。
StarkNet 的抽象账户必须包含以下两个函数:
-
__validate__
: 保证Sequencer 只打包有效的交易。 -
__execute__
: 主要执行交易。
关于账户Nonce, 目前顺序递增的模式,以后将升级到Nonce abstraction.
目前对于无效的交易,Sequencer 并不打包,以后将对失败交易也将打包,并扣手续费。
交易的手续费采用ETH 支付。
链上数据
目前StarkNet 仍然是ZK-Rollup模式,将state diff
以calldata 形式存储上链。
state diff
包含包更新 合约存储以及部署合约的信息,采用uint256[]
格式,包含以下数据:
-
部署合约的个数:
-
对于每个部署的合约,包含:
-
contract_address
: 部署合约的地址; -
contract_hash
: 部署合约的hash;
-
-
更新的合约的个数:
-
对于每个更新合约:
-
contract_address
: 合约的地址;
-
-
num_of_storage_updates
: 存储更新新的个数;-
nonce, num_of_storage_updates
: 更新的nonce 和 存储更新的个数;
-
-
对于每个存储更新:
-
key
: 在合约存储的位置;-
value
: 新的值
-
-
Hash 函数
所有的Hash 输出映射到 , 其中
StarkNet 主要使用两种Hash函数:
- sn_keccak:
;
-
pedersen
:
sn_keccak
根据 Keccak256 派生。
Pedersen hash 采用STARK 友好的椭圆曲线:
其中:
-
;
对于 ,可以将其分解为
, Pedersen hash 的定义为:
其中 是常量。
事件
StarkNet 可以抛出事件,包含以下字段:
-
from_address
: 发出事件的合约地址; -
keys
: 用来检索事件的keys; -
data
: 日志的值
日志的定义:
@event
func message_received(a : felt, b: felt):
end
日志的发出:
message_received.emit(1, 2);
手续费
手续费由ETH支付,主要有两部分:
- 计算复杂度: 交易涉及计算越复杂,费用越高;
-
链上数据
: 主要是L1 calldata 和 L2->L1的消息。
手续费的计算公式为:
消息传递机制
L2上的合约可以和L1上的合约交互,通过 send_message_to_L1
系统调用。
当状态在L1上更新之后,消息存储在L1上的 StarkNet Core Contract 上,并发出 LogMessageToL1
事件。
随后,接收者通过 consumeMessageFromL2
消费传递的消息。
![](https://img.haomeiwen.com/i7973505/cf13db8995bb1f85.png)
L2->L1的消息包含 FromAddress, EthereumAddress, Payload
.
对于L1->L2消息,包含以下几个阶段:
- 用户调用
sendMessageToL2
发起L2合约的调用,L1合约记录支付的手续费; - 消息解码为StarkNet 交易,也称为L1 handler transactions.
- Sequencer 在看到L1交易确认后,发起L2交易;
- L2交易会调用l1_handler.
- L1 Handler transaction 被提交到L1合约,并实现状态更新。
![](https://img.haomeiwen.com/i7973505/a6546e51ff8eb8b2.png)
L1 -> L2 消息包含以下字段:
- L1 发送者地址
- 接收者合约地址
- 函数 selector
- Calldata
- Message Nonce
对于L1->L2失败的交易,用户可以在L1调用 startL11ToL2MessageCancelldation
和 cancelL1toL2Message
进行取消.
L1 handler transaction 包含的字段为:
- Version
- ContractAddress
- Selector
- Calldata
- Nonce
StarkGate
StarkGate 主要实现ETH和ERC20在L1和L2之间发跨链。
**L1->L2 Transfer (Deposit) **
用户在L1发起 deposit
操作,发出deposit 事件,并向L2 发送一个消息。当交易在L1确认后,由Sequencer在L2 触发L1 handler handle_deposit
操作。当L1 handler 交易被打包进区块并提交到L1上时,完成整个操作。
L2 -> L1 Transfer (Withdraw)
用户首先在L2 发起initiate_withdraw
函数 ,并向L1 发送消息。当交易打包到区块并在L1 确认后,用户即可在L1 发起取款操作。
StarkNet State
StarkNet 的状态包含:
- 合约实例: 为地址到合约状态的映射
- 合约类: 为合约类Hash到合约 类定义的映射;
合约的状态由以下组成:
- 类hash;
- contract 存储;
- contract nonce;
状态承诺为:
h(h(h(class_hash, storage_root), nonce),0)
整个状态采用MPT 树数据结构。
账户创建
通过StarkNet CLI 工具, 初始化一个账户:
starknet new_account
初始化完成之后,还需要部署账户:
starknet deploy_account
StarkNet 合约示例
// Declare this file as a StarkNet contract.
%lang starknet
from starkware.cairo.common.cairo_builtins import HashBuiltin
// Define a storage variable.
@storage_var
func balance() -> (res: felt) {
}
// Increases the balance by the given amount.
@external
func increase_balance{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
}(amount: felt) {
let (res) = balance.read();
balance.write(res + amount);
return ();
}
// Returns the current balance.
@view
func get_balance{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
}() -> (res: felt) {
let (res) = balance.read();
return (res=res);
}
合约编译:
starknet-compile contract.cairo \
--output contract_compiled.json \
--abi contract_abi.json
合约声明:
starknet declare --contract contract_compiled.json
Declare transaction was sent.
Contract class hash: 0x1e2208b571b2cb68908f37a196ed5e391c8933a6db23bb3939acedee40d9b8a
Transaction hash: 0x762e166dd3326b2e263eb5bcfdccd225dc88e067fdf7c92cf8ce5e4ea01f9f1
合约部署:
starknet deploy --class_hash $CLASS_HASH
Invoke transaction for contract deployment was sent.
Contract address: 0x039564c4f6d9f45a963a6dc8cf32737f0d51a08e446304626173fd838bd70e1c
Transaction hash: 0x125e4bc5251af8ee2664ea0d1495b36c593f25f78f1a78f637a3f7aafa9e22
合约调用:
starknet invoke \
--address ${CONTRACT_ADDRESS} \
--abi contract_abi.json \
--function increase_balance \
--inputs 1234
合约查询:
starknet call \
--address ${CONTRACT_ADDRESS} \
--abi contract_abi.json \
--function get_balance
参考
https://starkware.co/starknet/
https://docs.starknet.io/documentation/
https://medium.com/starkware/papyrus-an-open-source-starknet-full-node-396f7cd90202
https://github.com/eqlabs/pathfinder
https://eprint.iacr.org/2021/1063.pdf
https://github.com/starkware-libs/cairo-lang
https://github.com/starkware-libs/papyrus
https://github.com/OpenZeppelin/cairo-contracts
https://github.com/starknet-io/starkgate-contracts
https://starknet.io/docs/hello_cairo/index.html#hello-cairo
https://github.com/NethermindEth/juno
网友评论