一文了解 zkSync 中的原生 Account AbstractionIntroQuick Look at the zkSync AA ContractAccount ContractFee Model and Paymaster in zkSync EraSummary & Comparison

原文作者:ChiHaoLu

原文来源:medium

本文主要介绍 Account Abstraction(AA、抽象账户)在 zkSync 这个 Layer2 Solution 的发展与相关内容。而重点会放在三个部分:

1. 账户合约:账户型态,账户合约的重要 Entry Point 和相关重点

2. 交易:AA 交易的验证方式和执行方式、流程

3. 手续费:交易手续费、Paymaster

一文了解 zkSync 中的原生 Account Abstraction

Image Source: zkSync Tweets

Author: ChiHaoLu(chihaolu.eth) @ imToken Labs

Special thanks to NIC Lin & Cyan Ho for reviewing this post.

本文同步发表于 imToken Mirror Publication

Table of Contents

· Intro

· Quick Look at the zkSync AA Contract

· Fee Model and Paymaster in zkSync Era

· Summary & Comparison

· Closing

Intro

Background

1. 熟悉智能合约钱包与其常见的 features

2. 大致了解 Ethereum 的交易如何运作

3. 大致了解 EIP-4337 运作模式

4. 大致了解 ZK(Validity)Rollup 运作模式

Quick Look at the zkSync

这边为了帮助大家在不需要深入理解 zkSync 的情况下阅读,快速的 recap 一下 zkSync 的基本信息,zkSync 主要有分两个版本,1.0(zkSync Lite)与 2.0(zkSync Era)。

zkSync 1.0 仅能使用 EOA 且不支援智能合约(只支援 token transfer 和 swap),而 zkSync 2.0 也就是 zkSync Era,属于 Native AA(原生账户型态都是合约,没有 EOA,也就是没有 Ethereum 中的 EOA 和 Contract Account 的差别),同时为 EVM-Compatible,支援使用 Rust、Yul、Vyper、Solidity 开发智能合约。

下文提到的 zkSync 若无特别指称,都指的是 zkSync 2.0,也就是 zkSync Era。

在 zkSync Era 中还有多个 System Contract,可以理解成是他们把 zkSync 一些重要 OS 功能实作在智能合约中。这些 System Contract 都是 precompiled contract,从未被部署(直接跑在节点里),但它们都具有一个正式地址。

执行 AA Protocol 时 zkSync 会透过一些 System Contract 来做逻辑运算和判断,例如验证 nonce 时是由 NonceHolder 来判断,而执行抽象账户的机制和收取手续费是由 bootloader 来判断,下文会一一介绍他们。

Recap Account Abstraction

Account Abstraction 的核心概念可以总结为两个关键点:Signature Abstraction(签章抽象)和 Payment Abstraction(支付抽象)。

Signature Abstraction 的目标是使各种账户合约能够使用不同的验证方案。这意味着使用者不受限于只能使用特定曲线的数位签章算法,而可以选择任何他们喜好的验证机制。

而 Payment Abstraction 旨在为使用者提供多种交易支付选项。例如,可以使用 ERC-20 代币进行支付,而不是使用原生代币,或者可以由第三方赞助交易,甚至是其他更特别的 payment model。

zkSync 2.0 中的账户可以发起交易,就像 EOA 一样,但也可以在其中利用可编程性实现任意逻辑,如 Contract Account。这就是我们所说的 Account Abstraction,它融合 Ethereum 中两种账户型态的优势,使 AA 账户的使用者体验更加灵活,进而达到上述的两种目标:Signature Abstraction 和 Payment Abstraction。

AA Mechanism in zkSync Era

zkSync AA 中最重要的角色分别为 bootloader,他是一个 System Contract,他主要用来处理交易以及执行 AA 的机制,对应着 4337 的 EntryPoint Contract。bootloader 无法被使用者呼叫(仅能被 Operator 触发),也从未被部署(直接跑在节点里),但它具有一个正式地址(可以用来收款)。

Operator 是 ZK rollup 中的一位重要角色,是中心化的 Off-Chain Server,与大家可能会看过的 Sequencer 同义,负责从外部触发 bootloader 等 System Contract。

原生的 Account Abstraction Protocol(e.g. StarkNet、zkSync)基本上都是参考 EIP-4337 进行设计,zkSync 的实现上用户会将交易送给 Operator,Operator 就会开始把交易送给 bootloader,并开始一系列的处理。

区块的角度看

当 bootloader 接收到来自 Operator 的输入,bootloader 会先替该区块定义一些环境变量(e.g. gas price、 block.number、 block.timestamp等)。之后 bootloader 会依序读取交易列表,先询问该账户合约是否同意该交易(也就是 AA 机制中的呼叫 validate function),再把他们放进区块中。

每一笔交易验证通过之后 Operator 会验证该区块是否足够大以便发送给证明者(或是否已超时)。如果够大或超时 Operator 就会关闭该区块,停止向 bootloader 添加新的交易,并完成交易执行。

从交易的角度看,bootloader 被 Operator 触发后会依序对每一笔交易:

1. 确认用户 Account Contract Address 对应的 nonce 是否合法

2. 呼叫用户 Account Contract 上的 validate function 进行验证

3. 验证通过之后 Account Contract 会把 gas fee 汇进 bootloader 的地址(或透过 Paymaster,下文会介绍),bootloader 会检查自己是否有收到足够的款项。

4. 呼叫用户 Account Contract 上的 execute function 执行交易

以上的前三步就是对应着 4337 的 Verification Loop,第四步则对应着 4337 的 Execution Loop。

这边主要做一个 overview 的介绍,每一步细节跟角色都会在之后详细说明。

Quick Look at the zkSync AA Contract

Nonce

zkSync 的账户 nonce 是记录在一个 System Contract — NonceHolder 里面,以 mapping 记住一组又一组的 (account_address, nonce) pair 是否有使用过来判断 nonce 是否合法。

从上文我们知道 bootloader 被 Operator 触发后第一步是检查 nonce,所以在每笔交易开始之前,NonceHolder 会被用来确认当前使用的这组 nonce 是否合法(目前只检查有没有用过)。如果 nonce 合法,则进入到 Verification Phase,此时 nonce 就已经会被标记为 used;不合法则交易(验证)失败

关于 zkSync 当前的 nonce 的重点:

· 虽然当前使用者可以同时送出多笔不同 nonce 的交易到 Account 执行,但因为 zkSync 不支援平行化处理,所以不同 nonce 的交易还是会依序被处理。

· 理论上用户可以使用任何 256-bit 的非 0 整数作为 nonce,但 zkSync 还是推荐使用incrementNonceIfEquals 来作为管理 nonce 的方式,以确认他是依序递增的(当前 zkSync 的 AA 机制并不会确认依序递增,只确认没用过,但官方文件表示未来会会要求依序递增)。

Account Contract

在 zkSync 中的 Account Contract 有以下四个必要的 Entry Point,分别为:

· validateTransaction 在 Verification Phase 被呼叫,以确认这次操作是被账户的拥有者授权的,用户可以在这边客制自己的验证逻辑(e.g. 各种签章算法、多签等)。

· payForTransaction,当交易手续费由这个账户支付(而非使用 paymaster)时,Operator 会呼叫这个函式来向 bootloader address 支付至少tx.gasprice * tx.gaslimit 的 ETH

· prepareForPaymaster 当交易手续费会由 Paymaster 支付,Operator 会呼叫这个函式来完成一些与 paymaster 互动的前置动作。zkSync 提供的范例是 approve Paymaster 的 ERC-20 token。

· executeTransaction,在 Verification Phase 成功通过且成功收取手续费之后,此函式会用来完成用户想要达到的 operation(e.g. 跟合约互动、汇款等行为)。

关于 Paymaster、手续费数量(tx.gasprice * tx.gaslimit)等会在后面的章节解释。

在 zkSync 的 Account 中还有一个非必要(optional)的保险函式 executeTransactionFromOutside,当没有办法执行函式时(例如 sequencer 没有反应或发现 zkSync 有监管风险时),可以有一个「逃跑机制」来提取资金到 L1。这个部分与 AA Protocol 没有太大的关系所以不耗费篇幅叙述,有兴趣的人可以查看官方文件与 zkSync 的 spec.

Keynotes and Limitations in Validate Function

在 validateTransaction 中可以实作各种客制化逻辑,例如如果 Account 有实作 1271 的话,可以直接把 1271 里的验证逻辑也套用在 validateTransaction 中,或参考 zkSync 官方文件的实作 MultiSig Account Contract

同时,在 4337 中的 Verification Phase 为了避免 DoS 威胁有一些限制(不能触及外部的 Opcodes 还有 Limited Depth 等),在 zkSync 也有同样的限制,例如:

1. 合约逻辑只可以碰到自己的 slots(Account Contract 的地址若为地址 A):

· 属于地址 A 的 Slots

· 任何其他地址的 Slots A

· 任何其他地址的 Slot keccak256(A||X):同意直接使用地址作为 mapping 的 key(例如mapping(address=>value)),等同于同意碰触 slot keccak256(A||X),以此方式进行扩增。例如 ERC-20 上的 token balance。

2. 合约逻辑不可以使用到 Global 变量,例如block.number

Keynotes and Limitations in Execute Function

executeTransaction 需要注意的是如果要执行 System Call,需要确认有 isSystem flag。因为这些 System Contract 对账户系统的影响非常大,例如增加 nonce 的唯一方式是与 NonceHolder 互动,要部署合约必须与 ContractDeployer 互动,藉由这个isSystem flag 可以确保账户开发者是有意识地要与 System Contract 互动。

然而实作上建议可以引用 SystemContractsCaller 这个 zkSync 提供的 Library 以避免自己处理isSystem flag,并使用其中的 systemCallWithPropagatedRevert 完成 System Call。

function _executeTransaction(Transaction calldata _transaction) internal {

    address to = address(uint160(_transaction.to));

    uint128 value = Utils.safeCastToU128(_transaction.value);

    bytes memory data = _transaction.data;

    if (to == address(DEPLOYER_SYSTEM_CONTRACT)) {

        uint32 gas = Utils.safeCastToU32(gasleft());

        // Note, that the deployer contract can only be called

        // with a “systemCall” flag.

        SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data);

    } else {

        bool success;

        assembly {

            success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0)

        }

        require(success);

    }

}

上面程序码的例子是与 DEPLOYER_SYSTEM_CONTRACT 互动,账户开发者最常碰到 System Contract 的情况就是我们要用账户去部署一个合约,此时必须要跟 ContractDeployer 这个 System Contract 互动。

Fee Model and Paymaster in zkSync Era

Fee and Gas Limit

zkSync 的费用模型与以太坊非常类似,Fee Token 也为 ETH,然而 zkSync 除了基本运算和写入 slot 的成本之外,和其他的 Layer2(Arbitrum、Optimism)一样需要考量发布到 L1 的额外成本(安全费用)。由于发布资料到 L1 上的 Gas Price 非常不稳定,所以在每个区块开启(开始收录交易)时,zkSync 的 Operator 会定义以下动态参数:

· gasPrice:以 gwei 为单位的 gas 价格,也就是前文提到的 transaction object 中的 tx.gasprice

· gasPerPubdata:在以太坊上发布一个 byte 的资料所需的 gas 数量

此外,zkSync 不像 4337 一样需要定义三种 Gas Limits: verificationGasexecutionGas 和 preVerificationGas,只单由一个gasLimit 就包含以上所有的 fee cost,所以用户需要自行确认gasLimit 足以涵盖 Verification Phase、Execution Phase 还有上传资料到 L1 的安全费用等全部 fee cost。这个就是前文提到的 transaction object 中的 tx.gaslimit

将两者相乘(tx.gasprice * tx.gaslimit)就能得到这笔交易所需要支付给 bootloader 的手续费数量。

Paymaster

Paymaster 主要在用户交易的支付手续费阶段时,代替 User 的 Account Contract 支付 ETH 给 bootloader,用户可以选择他们想要的 Paymaster 以及不同的 Payment Model 进行支付手续费,例如(但不限于):

1. 在交易发起前或交易执行后支付 ERC-20 给 Paymaster

2. 使用 Credit Card 储值进入 Paymaster Contract

3. Paymaster 会不断为 User 免费支付部分或全额手续费

用户要如何与 Paymaster 互动需要根据不同的协议而定,可以是中心化也可以是去中心化;可以在交易前,也可以在交易后;可以使用 ERC-20 也可以使用法币,甚至可以免费。

zkSync 的 Paymaster Contract 主要由两个函式构成,分别为 validateAndPayForPaymasterTransaction(required) 与 postTransaction(optional),两者都只能被 bootloader 呼叫:

· validateAndPayForPaymasterTransaction 是整个 Paymaster Contract 中唯一必须实作的 function,当 Operator 收到的交易有附带 paymaster params 时,代表手续费不由 User 的 Account Contract 支付,而是由 Paymaster 支付。此时 Operator 就会呼叫validateAndPayForPaymasterTransaction 来判断这个 Paymaster 是否愿意支付这笔交易。如果 Paymaster 愿意,这个函式会 send 至少tx.gasprice * tx.gaslimit 的 ETH 给 bootloader。

· postTransaction 是一个 optional 的函式,通常用于 refund(将未使用完的 gas 退还给发送者),但当前 zkSync 还不支援此操作。

zkSync 中的 Paymaster 有实作 postTransaction 才会执行postTransaction,这个部分有别于 4337,4337 如果在 validatePaymasterUserOp 没有回传 context 的话就不会呼叫postOp,反之则会。

综合以上,举例来说用户现在想要发送一笔手续费由 Paymaster 支付的交易,那流程如下:

1. 藉由 NonceHolder 确认 nonce 是否合法

2. 呼叫用户 Account Contract 上的 validateTransaction 进行验证,确认交易由账户拥有者授权

3. 呼叫用户 Account Contract 上的 prepareForPaymaster,里面可能会执行例如 approve 一定数量的 ERC-20 Token 给 Paymaster 或是不做任何事

4. 呼叫 Paymaster Contract 上的 validateAndPayForPaymasterTransaction 确认 Paymaster 愿意支付并且收取手续费,同时 Paymaster 向用户收取一定数量的 ERC-20(前面 approve 的)

5. 确认 bootloader 收到正确数量(至少tx.gasprice * tx.gaslimit)的 ETH 手续费

6. 呼叫用户 Account Contract 上的 executeTransaction 执行用户想要的交易

7. 如果 Paymaster Contract 有实作 postTransaction 且 gas 仍然足够(没有 out of gas error),那就执行postTransaction

最后一步即便 out of gas error 导致不能执行postTransaction,这笔 AA 交易也算是成功,只是省略掉呼叫postTransaction 的动作而已

更深入探究 zkSync 的 Paymaster 会发现它的 Verification Rules 和 4337 稍有不同(zkSync Paymaster 可以踩任何其他合约的 slot)、同时也有各种不同的 type(e.g. Approval-based),这部分由于比较细节所以有兴趣深入的人可以参考官方文件或我之前的实作

Summary & Comparison

Recap the AA transaction in zkSync Era

经过上文的解释我们已经知道一个账户合约有哪些重点 Entry Point,以及他们的作用、相关限制,同时也知道 System Contract 是什么,接下来我们重新梳理一次一笔 AA 交易在 zkSync 中怎么从建置到完成,同时我也会附上更细节的 ref 让有兴趣深入了解的人可以参考:

1. 用户在本地端使用 SDK 或钱包建置 Transaction Object(e.g. from, to, data, value…)

2. 用户对这个 Transaction 签章,这里的签章不一定真的是一个 EIP-712 格式且 ECDSA 曲线的签章,zkSync 也支援 2718 和 1559,且选择何种曲线或验证方式的重点是要通过 Account Contract 中的 validate function

3. 将这个已签的 Transaction 透过RPC API 送去给 Operator,这时会进入 Pending 状态,Operator 把这笔交易送给 bootloader(呼叫 bootloader contract 上的 processL2Tx)执行一连串的 AA Protocol

4. booteloader 会检查 Nonce 是否合法(利用 NonceHolder)

5. booteloader 会呼叫 Account Contract 上的 validateTransaction 确认此笔交易是经过账户的拥有者授权的

6. booteloader 获取手续费的方式有两种,藉由何种方式来 collect fee 依照交易参数(组建 transaction object 时是否有附带上 paymaster params)而定:

7. 「呼叫payForTransaction 来跟 Account Contract 收取手续费」或者「呼叫prepareForPaymaster 和 validateAndPayForPaymasterTransaction 来跟 Paymaster Contract 收取手续费」

8. 检查 bootloader 有收到至少tx.gasprice * tx.gaslimit 数量的交易手续费

9. booteloader 会呼叫 Account Contract 上的 executeTransaction 来执行交易

10. (optional)如果利用 Paymaster 来支付手续费,booteloader 会呼叫postTransaction,如果 Paymaster 没有实作 postTransaction 或 gas 已经耗尽就不会有这一步

以上的 4.~7. 步为 Verification Phase(定义在 bootloader 的 l2TxValidation),第 8.~9. 步 Execution Phase(定义在 bootloader 的 l2TxExecution)。

Comparison between EIP-4337, StarkNet and zkSync Era

基本上这三者的 AA 机制流程都相仿,皆为 Verification Phase → 手续费机制(由 Account Contract 支付或者 Paymaster)→ Execution Phase,主要差别有:

1. 执行 AA 机制的角色是谁:在 zkSync Era 中最主要与其他两者 AA 的差别在于 Operator 需要和 bootloader(System Contract)一起配合,例如 bootloader 会开启一个新区块并且定义该区块的相关参数,接收 Operator 送来的交易们并且进行验证。在 4337 中这部分由 Bundler 与 EntryPoint 协作,而在 StarkNet 中这部分全部都由 Sequencer 负责。

2. Gas Cost 是否需要考量到 L1 安全费用:L2 的 AA 都需要考虑这个上传资料到 L1 的费用,不只是这边提到的 ZK(Validity)Rollups Native AA,在 Optimistic Rollups 实作 4337 时也需要算入 L1 安全费用(算在 preVerificationGas 中,细节可见Alchemy 相关文件)。

3. 是否可以在 Account Contract 部署前送出交易:在 StarkNet 和 zkSync Era 中都没有像 4337 的 EntryPoint 有 initCode 这个 field 能让其替用户部署 Account Contract,所以都不可以在部署账户前送出交易。

Comparison

 

由于 StarkNet 尚无已实现的 Paymaster 机制、zkSync 也尚未完成 gas refund 机制的设计,所以一些比较细节的比较在这里就没有列出。

此外,目前的 4337 bundler 们并未完成 P2P mempool,且 zkRollups 的 Sequencer 和 Operator 也都还是唯一的官方 server,所以都有一定中心化的成分存在。

在开发流程上 zkSync 由于没有与各家 bundler 串接的问题(只需要与 Operator API 互动),所以使用起来相较 4337 容易,开发账户合约(SDK)的体验也会更好;同时 zkSync 可以使用 Solidity 作为合约开发语言,所以也不像在 StarkNet 开发需要跨过 Cairo 的门槛。

 

转载声明:本文 由CoinON抓取收录,观点仅代表作者本人,不代表CoinON资讯立场,CoinON不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。若以此作为投资依据,请自行承担全部责任。

声明:图文来源于网络,如有侵权请联系删除

风险提示:投资有风险,入市需谨慎。本资讯不作为投资理财建议。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 2023年8月23日 下午3:34
下一篇 2023年8月23日 下午8:34

相关推荐

一文了解 zkSync 中的原生 Account AbstractionIntroQuick Look at the zkSync AA ContractAccount ContractFee Model and Paymaster in zkSync EraSummary & Comparison

星期三 2023-08-23 20:34:28

本文主要介绍 Account Abstraction(AA、抽象账户)在 zkSync 这个 Layer2 Solution 的发展与相关内容。而重点会放在三个部分:

1. 账户合约:账户型态,账户合约的重要 Entry Point 和相关重点

2. 交易:AA 交易的验证方式和执行方式、流程

3. 手续费:交易手续费、Paymaster

一文了解 zkSync 中的原生 Account Abstraction

Image Source: zkSync Tweets

Author: ChiHaoLu(chihaolu.eth) @ imToken Labs

Special thanks to NIC Lin & Cyan Ho for reviewing this post.

本文同步发表于 imToken Mirror Publication

Table of Contents

· Intro

· Quick Look at the zkSync AA Contract

· Fee Model and Paymaster in zkSync Era

· Summary & Comparison

· Closing

Intro

Background

1. 熟悉智能合约钱包与其常见的 features

2. 大致了解 Ethereum 的交易如何运作

3. 大致了解 EIP-4337 运作模式

4. 大致了解 ZK(Validity)Rollup 运作模式

Quick Look at the zkSync

这边为了帮助大家在不需要深入理解 zkSync 的情况下阅读,快速的 recap 一下 zkSync 的基本信息,zkSync 主要有分两个版本,1.0(zkSync Lite)与 2.0(zkSync Era)。

zkSync 1.0 仅能使用 EOA 且不支援智能合约(只支援 token transfer 和 swap),而 zkSync 2.0 也就是 zkSync Era,属于 Native AA(原生账户型态都是合约,没有 EOA,也就是没有 Ethereum 中的 EOA 和 Contract Account 的差别),同时为 EVM-Compatible,支援使用 Rust、Yul、Vyper、Solidity 开发智能合约。

下文提到的 zkSync 若无特别指称,都指的是 zkSync 2.0,也就是 zkSync Era。

在 zkSync Era 中还有多个 System Contract,可以理解成是他们把 zkSync 一些重要 OS 功能实作在智能合约中。这些 System Contract 都是 precompiled contract,从未被部署(直接跑在节点里),但它们都具有一个正式地址。

执行 AA Protocol 时 zkSync 会透过一些 System Contract 来做逻辑运算和判断,例如验证 nonce 时是由 NonceHolder 来判断,而执行抽象账户的机制和收取手续费是由 bootloader 来判断,下文会一一介绍他们。

Recap Account Abstraction

Account Abstraction 的核心概念可以总结为两个关键点:Signature Abstraction(签章抽象)和 Payment Abstraction(支付抽象)。

Signature Abstraction 的目标是使各种账户合约能够使用不同的验证方案。这意味着使用者不受限于只能使用特定曲线的数位签章算法,而可以选择任何他们喜好的验证机制。

而 Payment Abstraction 旨在为使用者提供多种交易支付选项。例如,可以使用 ERC-20 代币进行支付,而不是使用原生代币,或者可以由第三方赞助交易,甚至是其他更特别的 payment model。

zkSync 2.0 中的账户可以发起交易,就像 EOA 一样,但也可以在其中利用可编程性实现任意逻辑,如 Contract Account。这就是我们所说的 Account Abstraction,它融合 Ethereum 中两种账户型态的优势,使 AA 账户的使用者体验更加灵活,进而达到上述的两种目标:Signature Abstraction 和 Payment Abstraction。

AA Mechanism in zkSync Era

zkSync AA 中最重要的角色分别为 bootloader,他是一个 System Contract,他主要用来处理交易以及执行 AA 的机制,对应着 4337 的 EntryPoint Contract。bootloader 无法被使用者呼叫(仅能被 Operator 触发),也从未被部署(直接跑在节点里),但它具有一个正式地址(可以用来收款)。

Operator 是 ZK rollup 中的一位重要角色,是中心化的 Off-Chain Server,与大家可能会看过的 Sequencer 同义,负责从外部触发 bootloader 等 System Contract。

原生的 Account Abstraction Protocol(e.g. StarkNet、zkSync)基本上都是参考 EIP-4337 进行设计,zkSync 的实现上用户会将交易送给 Operator,Operator 就会开始把交易送给 bootloader,并开始一系列的处理。

区块的角度看

当 bootloader 接收到来自 Operator 的输入,bootloader 会先替该区块定义一些环境变量(e.g. gas price、 block.number、 block.timestamp等)。之后 bootloader 会依序读取交易列表,先询问该账户合约是否同意该交易(也就是 AA 机制中的呼叫 validate function),再把他们放进区块中。

每一笔交易验证通过之后 Operator 会验证该区块是否足够大以便发送给证明者(或是否已超时)。如果够大或超时 Operator 就会关闭该区块,停止向 bootloader 添加新的交易,并完成交易执行。

从交易的角度看,bootloader 被 Operator 触发后会依序对每一笔交易:

1. 确认用户 Account Contract Address 对应的 nonce 是否合法

2. 呼叫用户 Account Contract 上的 validate function 进行验证

3. 验证通过之后 Account Contract 会把 gas fee 汇进 bootloader 的地址(或透过 Paymaster,下文会介绍),bootloader 会检查自己是否有收到足够的款项。

4. 呼叫用户 Account Contract 上的 execute function 执行交易

以上的前三步就是对应着 4337 的 Verification Loop,第四步则对应着 4337 的 Execution Loop。

这边主要做一个 overview 的介绍,每一步细节跟角色都会在之后详细说明。

Quick Look at the zkSync AA Contract

Nonce

zkSync 的账户 nonce 是记录在一个 System Contract — NonceHolder 里面,以 mapping 记住一组又一组的 (account_address, nonce) pair 是否有使用过来判断 nonce 是否合法。

从上文我们知道 bootloader 被 Operator 触发后第一步是检查 nonce,所以在每笔交易开始之前,NonceHolder 会被用来确认当前使用的这组 nonce 是否合法(目前只检查有没有用过)。如果 nonce 合法,则进入到 Verification Phase,此时 nonce 就已经会被标记为 used;不合法则交易(验证)失败

关于 zkSync 当前的 nonce 的重点:

· 虽然当前使用者可以同时送出多笔不同 nonce 的交易到 Account 执行,但因为 zkSync 不支援平行化处理,所以不同 nonce 的交易还是会依序被处理。

· 理论上用户可以使用任何 256-bit 的非 0 整数作为 nonce,但 zkSync 还是推荐使用incrementNonceIfEquals 来作为管理 nonce 的方式,以确认他是依序递增的(当前 zkSync 的 AA 机制并不会确认依序递增,只确认没用过,但官方文件表示未来会会要求依序递增)。

Account Contract

在 zkSync 中的 Account Contract 有以下四个必要的 Entry Point,分别为:

· validateTransaction 在 Verification Phase 被呼叫,以确认这次操作是被账户的拥有者授权的,用户可以在这边客制自己的验证逻辑(e.g. 各种签章算法、多签等)。

· payForTransaction,当交易手续费由这个账户支付(而非使用 paymaster)时,Operator 会呼叫这个函式来向 bootloader address 支付至少tx.gasprice * tx.gaslimit 的 ETH

· prepareForPaymaster 当交易手续费会由 Paymaster 支付,Operator 会呼叫这个函式来完成一些与 paymaster 互动的前置动作。zkSync 提供的范例是 approve Paymaster 的 ERC-20 token。

· executeTransaction,在 Verification Phase 成功通过且成功收取手续费之后,此函式会用来完成用户想要达到的 operation(e.g. 跟合约互动、汇款等行为)。

关于 Paymaster、手续费数量(tx.gasprice * tx.gaslimit)等会在后面的章节解释。

在 zkSync 的 Account 中还有一个非必要(optional)的保险函式 executeTransactionFromOutside,当没有办法执行函式时(例如 sequencer 没有反应或发现 zkSync 有监管风险时),可以有一个「逃跑机制」来提取资金到 L1。这个部分与 AA Protocol 没有太大的关系所以不耗费篇幅叙述,有兴趣的人可以查看官方文件与 zkSync 的 spec.

Keynotes and Limitations in Validate Function

在 validateTransaction 中可以实作各种客制化逻辑,例如如果 Account 有实作 1271 的话,可以直接把 1271 里的验证逻辑也套用在 validateTransaction 中,或参考 zkSync 官方文件的实作 MultiSig Account Contract

同时,在 4337 中的 Verification Phase 为了避免 DoS 威胁有一些限制(不能触及外部的 Opcodes 还有 Limited Depth 等),在 zkSync 也有同样的限制,例如:

1. 合约逻辑只可以碰到自己的 slots(Account Contract 的地址若为地址 A):

· 属于地址 A 的 Slots

· 任何其他地址的 Slots A

· 任何其他地址的 Slot keccak256(A||X):同意直接使用地址作为 mapping 的 key(例如mapping(address=>value)),等同于同意碰触 slot keccak256(A||X),以此方式进行扩增。例如 ERC-20 上的 token balance。

2. 合约逻辑不可以使用到 Global 变量,例如block.number

Keynotes and Limitations in Execute Function

executeTransaction 需要注意的是如果要执行 System Call,需要确认有 isSystem flag。因为这些 System Contract 对账户系统的影响非常大,例如增加 nonce 的唯一方式是与 NonceHolder 互动,要部署合约必须与 ContractDeployer 互动,藉由这个isSystem flag 可以确保账户开发者是有意识地要与 System Contract 互动。

然而实作上建议可以引用 SystemContractsCaller 这个 zkSync 提供的 Library 以避免自己处理isSystem flag,并使用其中的 systemCallWithPropagatedRevert 完成 System Call。

function _executeTransaction(Transaction calldata _transaction) internal {

    address to = address(uint160(_transaction.to));

    uint128 value = Utils.safeCastToU128(_transaction.value);

    bytes memory data = _transaction.data;

    if (to == address(DEPLOYER_SYSTEM_CONTRACT)) {

        uint32 gas = Utils.safeCastToU32(gasleft());

        // Note, that the deployer contract can only be called

        // with a “systemCall” flag.

        SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data);

    } else {

        bool success;

        assembly {

            success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0)

        }

        require(success);

    }

}

上面程序码的例子是与 DEPLOYER_SYSTEM_CONTRACT 互动,账户开发者最常碰到 System Contract 的情况就是我们要用账户去部署一个合约,此时必须要跟 ContractDeployer 这个 System Contract 互动。

Fee Model and Paymaster in zkSync Era

Fee and Gas Limit

zkSync 的费用模型与以太坊非常类似,Fee Token 也为 ETH,然而 zkSync 除了基本运算和写入 slot 的成本之外,和其他的 Layer2(Arbitrum、Optimism)一样需要考量发布到 L1 的额外成本(安全费用)。由于发布资料到 L1 上的 Gas Price 非常不稳定,所以在每个区块开启(开始收录交易)时,zkSync 的 Operator 会定义以下动态参数:

· gasPrice:以 gwei 为单位的 gas 价格,也就是前文提到的 transaction object 中的 tx.gasprice

· gasPerPubdata:在以太坊上发布一个 byte 的资料所需的 gas 数量

此外,zkSync 不像 4337 一样需要定义三种 Gas Limits: verificationGasexecutionGas 和 preVerificationGas,只单由一个gasLimit 就包含以上所有的 fee cost,所以用户需要自行确认gasLimit 足以涵盖 Verification Phase、Execution Phase 还有上传资料到 L1 的安全费用等全部 fee cost。这个就是前文提到的 transaction object 中的 tx.gaslimit

将两者相乘(tx.gasprice * tx.gaslimit)就能得到这笔交易所需要支付给 bootloader 的手续费数量。

Paymaster

Paymaster 主要在用户交易的支付手续费阶段时,代替 User 的 Account Contract 支付 ETH 给 bootloader,用户可以选择他们想要的 Paymaster 以及不同的 Payment Model 进行支付手续费,例如(但不限于):

1. 在交易发起前或交易执行后支付 ERC-20 给 Paymaster

2. 使用 Credit Card 储值进入 Paymaster Contract

3. Paymaster 会不断为 User 免费支付部分或全额手续费

用户要如何与 Paymaster 互动需要根据不同的协议而定,可以是中心化也可以是去中心化;可以在交易前,也可以在交易后;可以使用 ERC-20 也可以使用法币,甚至可以免费。

zkSync 的 Paymaster Contract 主要由两个函式构成,分别为 validateAndPayForPaymasterTransaction(required) 与 postTransaction(optional),两者都只能被 bootloader 呼叫:

· validateAndPayForPaymasterTransaction 是整个 Paymaster Contract 中唯一必须实作的 function,当 Operator 收到的交易有附带 paymaster params 时,代表手续费不由 User 的 Account Contract 支付,而是由 Paymaster 支付。此时 Operator 就会呼叫validateAndPayForPaymasterTransaction 来判断这个 Paymaster 是否愿意支付这笔交易。如果 Paymaster 愿意,这个函式会 send 至少tx.gasprice * tx.gaslimit 的 ETH 给 bootloader。

· postTransaction 是一个 optional 的函式,通常用于 refund(将未使用完的 gas 退还给发送者),但当前 zkSync 还不支援此操作。

zkSync 中的 Paymaster 有实作 postTransaction 才会执行postTransaction,这个部分有别于 4337,4337 如果在 validatePaymasterUserOp 没有回传 context 的话就不会呼叫postOp,反之则会。

综合以上,举例来说用户现在想要发送一笔手续费由 Paymaster 支付的交易,那流程如下:

1. 藉由 NonceHolder 确认 nonce 是否合法

2. 呼叫用户 Account Contract 上的 validateTransaction 进行验证,确认交易由账户拥有者授权

3. 呼叫用户 Account Contract 上的 prepareForPaymaster,里面可能会执行例如 approve 一定数量的 ERC-20 Token 给 Paymaster 或是不做任何事

4. 呼叫 Paymaster Contract 上的 validateAndPayForPaymasterTransaction 确认 Paymaster 愿意支付并且收取手续费,同时 Paymaster 向用户收取一定数量的 ERC-20(前面 approve 的)

5. 确认 bootloader 收到正确数量(至少tx.gasprice * tx.gaslimit)的 ETH 手续费

6. 呼叫用户 Account Contract 上的 executeTransaction 执行用户想要的交易

7. 如果 Paymaster Contract 有实作 postTransaction 且 gas 仍然足够(没有 out of gas error),那就执行postTransaction

最后一步即便 out of gas error 导致不能执行postTransaction,这笔 AA 交易也算是成功,只是省略掉呼叫postTransaction 的动作而已

更深入探究 zkSync 的 Paymaster 会发现它的 Verification Rules 和 4337 稍有不同(zkSync Paymaster 可以踩任何其他合约的 slot)、同时也有各种不同的 type(e.g. Approval-based),这部分由于比较细节所以有兴趣深入的人可以参考官方文件或我之前的实作

Summary & Comparison

Recap the AA transaction in zkSync Era

经过上文的解释我们已经知道一个账户合约有哪些重点 Entry Point,以及他们的作用、相关限制,同时也知道 System Contract 是什么,接下来我们重新梳理一次一笔 AA 交易在 zkSync 中怎么从建置到完成,同时我也会附上更细节的 ref 让有兴趣深入了解的人可以参考:

1. 用户在本地端使用 SDK 或钱包建置 Transaction Object(e.g. from, to, data, value…)

2. 用户对这个 Transaction 签章,这里的签章不一定真的是一个 EIP-712 格式且 ECDSA 曲线的签章,zkSync 也支援 2718 和 1559,且选择何种曲线或验证方式的重点是要通过 Account Contract 中的 validate function

3. 将这个已签的 Transaction 透过RPC API 送去给 Operator,这时会进入 Pending 状态,Operator 把这笔交易送给 bootloader(呼叫 bootloader contract 上的 processL2Tx)执行一连串的 AA Protocol

4. booteloader 会检查 Nonce 是否合法(利用 NonceHolder)

5. booteloader 会呼叫 Account Contract 上的 validateTransaction 确认此笔交易是经过账户的拥有者授权的

6. booteloader 获取手续费的方式有两种,藉由何种方式来 collect fee 依照交易参数(组建 transaction object 时是否有附带上 paymaster params)而定:

7. 「呼叫payForTransaction 来跟 Account Contract 收取手续费」或者「呼叫prepareForPaymaster 和 validateAndPayForPaymasterTransaction 来跟 Paymaster Contract 收取手续费」

8. 检查 bootloader 有收到至少tx.gasprice * tx.gaslimit 数量的交易手续费

9. booteloader 会呼叫 Account Contract 上的 executeTransaction 来执行交易

10. (optional)如果利用 Paymaster 来支付手续费,booteloader 会呼叫postTransaction,如果 Paymaster 没有实作 postTransaction 或 gas 已经耗尽就不会有这一步

以上的 4.~7. 步为 Verification Phase(定义在 bootloader 的 l2TxValidation),第 8.~9. 步 Execution Phase(定义在 bootloader 的 l2TxExecution)。

Comparison between EIP-4337, StarkNet and zkSync Era

基本上这三者的 AA 机制流程都相仿,皆为 Verification Phase → 手续费机制(由 Account Contract 支付或者 Paymaster)→ Execution Phase,主要差别有:

1. 执行 AA 机制的角色是谁:在 zkSync Era 中最主要与其他两者 AA 的差别在于 Operator 需要和 bootloader(System Contract)一起配合,例如 bootloader 会开启一个新区块并且定义该区块的相关参数,接收 Operator 送来的交易们并且进行验证。在 4337 中这部分由 Bundler 与 EntryPoint 协作,而在 StarkNet 中这部分全部都由 Sequencer 负责。

2. Gas Cost 是否需要考量到 L1 安全费用:L2 的 AA 都需要考虑这个上传资料到 L1 的费用,不只是这边提到的 ZK(Validity)Rollups Native AA,在 Optimistic Rollups 实作 4337 时也需要算入 L1 安全费用(算在 preVerificationGas 中,细节可见Alchemy 相关文件)。

3. 是否可以在 Account Contract 部署前送出交易:在 StarkNet 和 zkSync Era 中都没有像 4337 的 EntryPoint 有 initCode 这个 field 能让其替用户部署 Account Contract,所以都不可以在部署账户前送出交易。

Comparison

 

由于 StarkNet 尚无已实现的 Paymaster 机制、zkSync 也尚未完成 gas refund 机制的设计,所以一些比较细节的比较在这里就没有列出。

此外,目前的 4337 bundler 们并未完成 P2P mempool,且 zkRollups 的 Sequencer 和 Operator 也都还是唯一的官方 server,所以都有一定中心化的成分存在。

在开发流程上 zkSync 由于没有与各家 bundler 串接的问题(只需要与 Operator API 互动),所以使用起来相较 4337 容易,开发账户合约(SDK)的体验也会更好;同时 zkSync 可以使用 Solidity 作为合约开发语言,所以也不像在 StarkNet 开发需要跨过 Cairo 的门槛。