ERC-20 token 支付手续费源码解析

2023-02-14 10:52:38 浏览数 (2)

本文是对 GSN 代码的解析。

1 ERC-20 token 支付手续费流程

流程:

ERC-20 token 支付手续费ERC-20 token 支付手续费

1)client 向 relay service 发送签名后的请求,不需要用 ETH 支付手续费。

2)relay service 将请求放到 tx 中,为用户用 ETH 垫付手续费,将 tx 发送到区块链网络中,目标是 RelayHub 合约。

3-5)RelayHub 合约请求 TokenPaymaster 合约将最大可能的手续费换算成 ERC-20 token,并从用户账户扣除。

6-8)RelayHub 合约请求 Forwarder 调用目标合约。Forwarder 会先验证签名和 nonce,再将请求转发给目标合约。

9-11)RelayHub 合约请求 TokenPaymaster 合约将多向用户收取的 ERC-20 token 退还给用户。

12-13)RelayHub 合约收到 TokenPaymaster 合约将 ERC-20 token 换成的 ETH 后,为 relay service 报销其为用户垫付的 ETH 手续费。

GSN 组件:

  • provider:供 client 使用,会为 client 拼接 relay request,并发送到 relayer。当发送完 relay request 后,还会发送审计请求,作为后续惩罚 relayer 的依据。
  • relay service:支付 gas 的第三方服务。将用户的 tx 发送到区块链,并垫付手续费。是去中心化的,志愿者可以自己启动一个 relay service。relay service 需要在 RelayHub 合约上质押一定数量的 token,如果被发现作恶,则会被惩罚。
  • 合约:
    • RelayHub:是 GSN 中的主要合约,连接了 client、relayer 和 paymaster。会包含如下合约的地址:
      • RelayRegistrar:管理 relayer 的注册信息。
      • StakeManager:管理 relayer 的质押信息。
      • Penalizer:惩罚 relayer 的规则。
    • Paymaster:决定是否接受 tx,并退还 relayer 垫付的手续费。需要开发者自己实现,不过提供了一些示例。
    • Forwarder:验证签名,将用户的地址添加到 call data 中,将请求转发给 target 合约。
    • ERC2771Recipient:target 合约需要继承该合约,兼容 EIP-2771 标准,以从 call data 中获取用户地址。

2 Provider

代码 使用示例 demo

@opengsn/provider是 web3 provider 的封装,供 client 调用,为 client 拼接 relay request,并发送给 relayer。

发送 tx 过程:

provider 发送 txprovider 发送 tx

3 Relay Service

代码 使用示例

是支付 gas 的第三方 relayer。

meta tx 不直接发送到区块链,而是发送元交易到第三方 relayer,该第三方支付 gas。

  • 志愿者可以自己启动一个 relayer,但须提前在 RelayHub 注册并抵押 ETH。正确完成 relay tx 会收到来自 RelayHub 的奖励,若恶意攻击则会被没收抵押。
  • relay manager 需确保有足够 ETH 来代替 DApp 使用者支付 gas,若 ETH 不足则会变成非活跃状态,不会被 Client 选择。

需要在 RelayHub 注册 relayer,目的是:

  • 为 relayer 质押 ETH 或 ERC-20 token,以免 relayer 提交无效 tx。
  • 为 relayer 提供初始资金以便发送 tx,默认是 2 ETH。
  • 将 relayer 添加到链上 relayers 列表,供 client 使用。

可自行启动 relayer 后,在该页面进行注册:https://relays.opengsn.org/

3.1 config

定义 默认值

参数

必填

描述

示例

默认值

relayHubAddress

RelayHub 合约地址

0xbF06d99FDE1dc4e4C24F4191Fad82F8f5524Ce62

managerStakeTokenAddress

relayer 用于质押的 ERC-20 token 地址

0x7e282733bbca1994fa0d63848b40a1df2ebb5623

ownerAddress

owner 账户,被 relayer-register 使用

0x8C1FD2DE219c98f5F88620422e36a8A32f83324E

ethereumNodeUrl

node 地址

http://host.docker.internal:8545

url

relay server url

http://localhost:8090

workdir

缓存 tx 数据的目录

./app/data

gasPriceFactor

gas 乘数

1.1

1

environmentName

环境配置名称

ethereumMainnet

ganacheLocal

runPaymasterReputations

是否运行 Paymaster Reputations

true

true

runPenalizer

是否运行 Penalizer

true

true

workerMinBalance

relay worker 账户的最小 eth 数量

0.1e18

0.1e18

workerTargetBalance

relay worker 账户的目标 eth 数量

0.3e18

0.3e18

managerMinBalance

relay manager 账户的最小 eth 数量

0.1e18

0.1e18

managerTargetBalance

relay manager 账户的目标 eth 数量

0.3e18

0.3e18

checkInterval

relayer 周期性检查间隔

10000

10000

3.2 relay service 初始化

  1. 解析配置文件。
  2. 根据配置文件中的 RelayHub 地址创建 RelayHub。从 RelayHub 获得 StakeManager,Penalizer 和 RelayRegistrar 的地址并进行创建。
  3. 根据配置文件决定是否创建 paymaster reputation manager。
  4. 根据配置文件决定是否初始化 PenalizerService。PenalizerService 会周期性地查看内存中是否新增非法 tx 信息(例如,tx 中调用的不是 IRelayHub.relayCall() 函数,tx 的 nonce 重复),如果有则调用 penalizer 合约中的惩罚函数,取消 relayer 的注册,并将其质押奖励给提交非法 tx 者。为了防止股权绑架(stake kidnapping),relayer 的质押一半被 burn,一半用于奖励。
  5. 初始化 relayer,会初始化 transaction manager 和 registration manager。
  • transaction manager 会监听 TransactionBroadcast 事件并发送交易。
  • registration manager 会检查并更新 relay manager 的质押信息,如下图所示。
registrationregistration

3.3 relay service 启动

启动 http server,会启动 relayer。relayer 会周期性地执行如下步骤:

relay service 启动relay service 启动

3.4 relayer 监听请求

  • /getaddr,返回合约地址信息。如果运行 paymaster reputation,还会返回 paymaster reputation 相关信息。
  • /stats,返回状态信息,供该页面展示:https://relays.opengsn.org/
  • /relay,relay worker 会发送 tx 并垫付手续费
  1. 进行一系列检查
  • 如果运行 PaymasterReputation,还会检查 request 中 paymaster 的名誉
  • 请求消耗的 gas 不能超过 paymaster.getGasAndDataLimits()
  • 请求中 paymaster 的 RelayHub 的余额不能过小
  • 检查 relay worker 余额是否够发送 tx
  1. 发送交易,调用 relayHub.RelayCall(),并将 tx 保存到本地。
  2. 发送交易后,检查 relay worker 的余额,必要时进行补充:如果 relay manager 的余额小于配置,则从 RelayHub 中撤回一部分;如果 relay worker 的余额小于配置,则将 relay manager 的余额转给 relay worker 一部分。
  • /audit,如果配置文件中运行 penalizer,则监听该方法。如果发现 nonce 重复或没有调用 IRelayHub.relayCall() 函数的 tx,则将非法 tx 信息更新到内存,并发送交易,调用 penalizer.commit() 声明惩罚。

3.5 惩罚 relayer

@opengsn/provider 向 relayer 发送 tx 后,还会请求 /audit 审计 tx。penalizer service 会周期性地根据审计结果对 relayer 实施惩罚:

惩罚 relayer惩罚 relayer

根据配置决定是否运行 Penalizer Service。

3.6 计算 paymaster 名誉

  • 根据配置决定是否运行 Paymaster Reputation Manager。
  • 周期性检查,如果发现 RelayHub 中发生了被 paymaster 拒绝或接受的 event,则更新 paymaster 的名誉。拒绝会导致 paymaster 名誉分值减一,接受会加一。
  • 如果 relayer 发现请求中的 paymaster 名誉低于配置中的阈值,则拒绝 relay tx。

4 合约

4.1 RelayHub

合约代码

是 GSN 中的主要合约。连接了 client、relayer 和 paymaster,这样它们就可以互相不需要了解或信任对方。

Dapp 开发人员不需要了解或信任 RelayHub,就可以集成 GSN 。

RelayHub 可以自行部署,但自行部署的 RelayHub 无法共享已存在的 relayer。

RelayHub 中会保存合约 StakeManager、Penalizer 和 RelayRegistrar 的地址。

支付手续费流程相关的方法:

  • relayCall:relay 一个 tx。
    1. 验证 msg.sender 是否为注册过的 relayer。
    2. 验证 relay manager 的质押 token 数量是否够:通过 relayer 查找到 relay manager,调用 stakeManager.getStakeInfo() 获得 relay manager 的质押信息,检查其否质押了最小额度的 token。
    3. 验证 paymaster 存放在 RelayHub 的 ETH 数量是否够支付最大可能的手续费,msg.data 是否过长。
    4. 验证编码后的请求是否被正确打包,没有任何额外字节。
    5. 原子调用 paymaster.preRelayedCall(),forwarder.execute() 和 paymaster.postRelayedCall()。
    6. 更新 paymaster 和 relay manager 的 ETH 余额:从 paymaster 中扣除实际消耗的 gas 和 fee;为 relay manager 报销垫付的 gas 和 fee。
  • depositFor:Paymaster 会调用该方法存 ETH 到 RelayHub,以便 RelayHub 为 tx 支付手续费。 function depositFor(address target) external payable;

Relayer 相关的方法:

  • function addRelayWorkers(address[] calldata newRelayWorkers) external:被 relay manager 调用,添加被其控制的 worker。
  • function onRelayServerRegistered(address relayManager) external:被 RelayRegistrar 回调,通知 RelayHub 该 relay manager 已经更新了注册信息。
  • function setMinimumStakes(IERC20[] memory token, uint256[] memory minimumStake) external:设置或更改给定 token 的最新额度,是 relay manager 质押到 RelayHub 的最小额度。零表示该 token 不允许被质押。

Penalizer 相关的方法:

  • function penalize(address relayWorker, address payable beneficiary) external:如果 Penalizer 发现 relay worker 违反了 Penalizer 合约中的某些规定,如 relay worker 所 relay 的 tx 不是调用 RelayHub 的 relayCall() 方法,会调用此方法进行惩罚。RelayHub 会根据给定的 relay worker 查找到 relay manager,并调用 StakeManager 合约进行惩罚。

4.2 Token Paymaster

合约代码

用来为 tx 支付手续费。Paymaster 会保存合约 RelayHub 和 Forwarder 的地址。

没有经过审计,只是一个示例。

Dapp 开发者需要实现 Paymaster 合约,只要继承 BasePaymaster 即可。该合约需要在 RelayHub 存一定数量的 ETH,并在 tx 执行后被 RelayHub 收费。

Dapp 开发者需要实现两个被 RelayHub 回调的函数:preRelayedCallpostRelayedCall

在 BasePaymaster 中:

  • preRelayedCall 会做一系列检查,继承 BasePaymaster 的合约需要实现 _preRelayedCall 即可。
  • postRelayedCall 同理。

在给出的 TokenPaymaster(继承 BasePaymaster )示例中:

  • 构造函数中会添加支持的 uniswap 和 token,并执行 token.approve(),以允许 uniswap 转移 token:
  • _preRelayedCall 为 tx 预付最大可能的 token 费用:
    1. 验证 token 是否支持 uniswap,即 caller 是否可以用 ERC20 token 进行支付。
    2. 计算最大可能的 token 费用。会调用 relayHub.calculateCharge() 计算出 ETH 费用后,再换算成 token。
    3. 调用 token.transferFrom() 向用户收取 token 费用。
  • _postRelayedCall 向用 caller 退还未使用的 gas。
    1. 调用 relayHub.calculateCharge() 计算出实际需要支付的 ETH 费用后,换算成 token。
    2. 预付的 token 减去实际需要支付的 token,得到需要退还的多余 token,并调用 token.transfer() 退还给 payer。
    3. 将收取的 token 通过 uniswap 换成 ETH 后,调用 relayHub.depositFor() 存到 RelayHub,以便 RelayHub 为 tx 支付手续费。

4.3 Forwarder

合约代码

  • 接收 ForwardRequest 并验证是否合法,验证 sender 的签名和 nonce。用户合约仅依赖 forwarder 保证安全性。
  • 将 20 字节的 sender 地址添加到 ForwardRequest 的 data 字段,并调用 ForwardRequest 中 to 字段所代表的目标合约。

4.4 Target Contract

开发者编写的合约,获取原始 sender 并执行原始 tx。

需要兼容 ERC-2771 标准

  • 继承 ERC2771Recipient.sol
  • [将 msg.sender 替换为 _msgSender,以获取原始 sender。因为 target contract 只接收到 Forwarder 的请求,所以 msg.sender 是 Forwarder 的地址,而非用户的地址。

5 CLI

代码 使用示例

CLI 用来部署 GSN 合约,启动 relayer 等。

gsn start: 在本地测试环境中运行 GSN。

1. 部署 GSN 合约:

部署 GSN 合约部署 GSN 合约

2. 启动一个 relay service。

参考

Ethereum Gas Station Network (GSN)

Paying for your user's meta-transaction

Creating a Paymaster

0 人点赞