本文档介绍了 OpenZeppelin Gas Station Network (GSN) 库,它提供了一组合约,允许合约通过 GSN 被调用,实现 gasless 交易。
你当前阅读的不是此文档的最新版本。5.x 是当前版本。
更好的阅读方式:https://docs.openzeppelin.com/contracts/api/gsn |
这组合约提供了通过 Gas Station Network 使合约可调用的所有必要工具。
如果你是 GSN 的新手,请前往我们的系统概述和创建支持 GSN 的合约的基本指南。 |
接收者必须继承的核心合约是 GSNRecipient
:它包括所有必要的接口,以及一些辅助方法,使与 GSN 的交互更容易。
GSNRecipient
中提供了使编写 GSN 策略 变得简单的实用程序,或者你可以简单地使用我们预先构建的策略之一:
GSNRecipientERC20Fee
以应用特定的 ERC20 token 向最终用户收取 gas 费用
GSNRecipientSignature
接受所有已由受信任的第三方(例如,后端中的私钥)签名的中继调用
你还可以查看构成 GSN 协议的两个合约接口:IRelayRecipient
和 IRelayHub
,但你不需要直接使用它们。
GSNRecipient
基础 GSN 接收者合约:包括 IRelayRecipient
接口,并在继承树中的所有合约上启用 GSN 支持。
这个合约是抽象的。函数 IRelayRecipient.acceptRelayedCall ,<br> _preRelayedCall , 和 _postRelayedCall 没有实现,必须由派生合约提供。有关如何使用预构建的 GSNRecipientSignature 和<br>GSNRecipientERC20Fee ,或如何编写你自己的,请参阅<br>GSN 策略。 |
函数
IRelayRecipient
事件
getHubAddr() → address
public返回此接收者的 IRelayHub
合约的地址。
_upgradeRelayHub(address newRelayHub)
internal切换到新的 IRelayHub
实例。添加此方法是为了面向未来:没有理由不使用默认实例。
升级后,GSNRecipient 将无法再从旧的<br>IRelayHub 实例接收中继调用。此外,所有资金应事先通过 _withdrawDeposits 提取。 |
relayHubVersion() → string
public返回构建此接收者实现的 IRelayHub
的版本字符串。如果使用了 _upgradeRelayHub
,则新的 IRelayHub
实例应与此版本兼容。
_withdrawDeposits(uint256 amount, address payable payee)
internal提取接收者在 RelayHub
中的存款。
派生合约应在具有适当访问控制的外部接口中公开此功能。
_msgSender() → address payable
internal替换 msg.sender。返回交易的实际发送者:常规交易的 msg.sender,GSN 中继调用的最终用户(其中 msg.sender 实际上是 RelayHub
)。
从 GSNRecipient 派生的合约永远不应使用 msg.sender ,而应使用 _msgSender 代替。 |
_msgData() → bytes
internal替换 msg.data。返回交易的实际 calldata:常规交易的 msg.data,GSN 中继调用的简化版本(其中 msg.data 包含附加信息)。
从 GSNRecipient 派生的合约永远不应使用 msg.data ,而应使用 _msgData 代替。 |
preRelayedCall(bytes context) → bytes32
public参见 IRelayRecipient.preRelayedCall
。
不应直接覆盖此函数,而应使用 _preRelayedCall
代替。
要求:
调用者必须是 RelayHub
合约。
_preRelayedCall(bytes context) → bytes32
internal参见 IRelayRecipient.preRelayedCall
。
由 GSNRecipient.preRelayedCall
调用,后者断言调用者是 RelayHub
合约。派生合约必须实现此函数,并执行它们可能希望执行的任何中继调用预处理。
postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal)
public参见 IRelayRecipient.postRelayedCall
。
不应直接覆盖此函数,而应使用 _postRelayedCall
代替。
要求:
调用者必须是 RelayHub
合约。
_postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal)
internal参见 IRelayRecipient.postRelayedCall
。
由 GSNRecipient.postRelayedCall
调用,后者断言调用者是 RelayHub
合约。派生合约必须实现此函数,并执行它们可能希望执行的任何中继调用后处理。
_approveRelayedCall() → uint256, bytes
internal在 acceptRelayedCall 中返回此值以继续执行中继调用。请注意,此合约将被 RelayHub 收取费用
_approveRelayedCall(bytes context) → uint256, bytes
internal参见 GSNRecipient._approveRelayedCall
。
此重载将 context
转发到 _preRelayedCall 和 _postRelayedCall。
_rejectRelayedCall(uint256 errorCode) → uint256, bytes
internal在 acceptRelayedCall 中返回此值以阻止执行中继调用。不会收取任何费用。
_computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) → uint256
internalRelayHubChanged(address oldRelayHub, address newRelayHub)
event当合约将其 IRelayHub
合约更改为新合约时发出。
GSNRecipientSignature
当 GSN 策略 随附受信任签名者的签名时,允许通过中继交易。目的是让此签名由在链下执行验证的服务器生成。请注意,在此方案中不会向用户收取任何费用。因此,服务器应确保在其经济和威胁模型中考虑这一点。
函数
GSNRecipient
事件
GSNRecipient
constructor(address trustedSigner)
public设置将生成签名以批准中继调用的受信任签名者。
acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256) → uint256, bytes
public确保只有具有受信任签名的交易才能通过 GSN 中继。
_preRelayedCall(bytes) → bytes32
internal
_postRelayedCall(bytes, bool, uint256, bytes32)
internal
GSNRecipientERC20Fee
GSN 策略 以特殊用途的 ERC20 token 收取交易费用,我们将其称为 gas 支付 token。收取的金额恰好是向接收者收取的以太币金额。这意味着 token 本质上与以太币的价值Hook。
gas 支付 token 向用户分发的策略不是由本合约定义的。它是一个可铸造的 token,其唯一的铸币者是接收者,因此必须在派生合约中实现该策略,并利用内部 _mint
函数。
函数
GSNRecipient
事件
GSNRecipient
constructor(string name, string symbol)
public构造函数的参数是 gas 支付 token 将具有的详细信息:name
和 symbol
。decimals
硬编码为 18。
token() → contract __unstable__ERC20Owned
public返回 gas 支付 token。
_mint(address account, uint256 amount)
internal铸造 gas 支付 token 的内部函数。派生合约应在其公共 API 中公开此函数,并具有适当的访问控制机制。
acceptRelayedCall(address, address from, bytes, uint256 transactionFee, uint256 gasPrice, uint256, uint256, bytes, uint256 maxPossibleCharge) → uint256, bytes
public确保只有具有足够 gas 支付 token 余额的用户才能通过 GSN 中继交易。
_preRelayedCall(bytes context) → bytes32
internal实现对用户的预收费。最大可能的费用(取决于 gas 限制、gas 价格和费用)将从 gas 支付 token 的用户余额中扣除。请注意,这是对实际费用的高估,这是必要的,因为我们无法预测执行实际需要多少 gas。剩余部分将在 _postRelayedCall
中返回给用户。
_postRelayedCall(bytes context, bool, uint256 actualCharge, bytes32)
internal一旦知道实际执行成本,就会将先前收取的额外金额退还给用户。
IRelayRecipient
将通过来自 IRelayHub
的 GSN 调用的合约的基本接口。
你不需要自己编写实现!改为从 GSNRecipient 继承。 |
函数
getHubAddr() → address
external返回此接收者与之交互的 IRelayHub
实例的地址。
acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256 maxPossibleCharge) → uint256, bytes
external由 IRelayHub
调用以验证此接收者是否接受为中继调用付费。请注意,无论中继调用的执行结果如何(即,如果它恢复与否),都将向接收者收费。
中继请求由 from
发起,并将由 relay
提供服务。encodedFunction
是中继调用的 calldata,因此它的前四个字节是函数选择器。中继调用将转发 gasLimit
gas,并且交易将以至少 gasPrice
的 gas 价格执行。relay
的费用是 transactionFee
,接收者将被收取最多 maxPossibleCharge
(以 wei 为单位)。nonce
是发件人(from
)的 nonce,用于在 IRelayHub
中防止重放攻击,approvalData
是一个可选参数,可用于保存对所有或部分先前值的签名。
返回一个元组,其中第一个值用于指示批准 (0) 或拒绝(自定义非零错误代码,保留值 1 到 10),第二个值是要传递给其他 IRelayRecipient
函数的数据。
acceptRelayedCall
被调用时有 50k gas:如果执行期间耗尽,则该请求将被视为拒绝。常规恢复也会触发拒绝。
preRelayedCall(bytes context) → bytes32
external由 IRelayHub
在批准的中继调用请求上调用,在中继调用执行之前。这允许例如预先向交易发送者收费。
context
是 acceptRelayedCall
的元组返回的第二个值。
返回要传递给 postRelayedCall
的值。
preRelayedCall
被调用时有 100k gas:如果执行期间耗尽或以其他方式恢复,则不会执行中继调用,但接收者仍将被收取交易成本。
postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal)
external由 IRelayHub
在批准的中继调用请求上调用,在中继调用执行之后。这允许例如向用户收取中继调用成本,从 preRelayedCall
返回任何多收费用,或执行特定于合约的记账。
context
是 acceptRelayedCall
的元组返回的第二个值。success
是中继调用的执行状态。actualCharge
是对接收者将为交易支付多少费用的估计,不包括 postRelayedCall
本身使用的任何 gas。preRetVal
是 preRelayedCall
的返回值。
postRelayedCall
被调用时有 100k gas:如果执行期间耗尽或以其他方式恢复,则中继调用和对 preRelayedCall
的调用将被追溯恢复,但接收者仍将被收取交易成本。
IRelayHub
RelayHub
的接口,GSN 的核心合约。用户不应需要直接与此合约交互。
有关如何在本地测试网络上部署 RelayHub
实例的更多信息,请参阅 OpenZeppelin GSN helpers。
函数
maxPossibleCharge(relayedCallStipend, gasPrice, transactionFee)
penalizeRepeatedNonce(unsignedTx1, signature1, unsignedTx2, signature2)
事件
[RelayAdded(relay, owner, transactionFee, stake, unstakeDelay, url)
](https://docs.openzeppelin.com/contracts/3.x/api/gsn- CanRelayFailed(relay, from, to, selector, reason)
TransactionRelayed(relay, from, to, selector, status, charge)
stake(address relayaddr, uint256 unstakeDelay)
external向一个 relay 添加 stake 并设置它的 unstakeDelay
。如果该 relay 不存在,则创建它,并且此函数的调用者成为其所有者。如果该 relay 已经存在,则只有所有者可以调用此函数。一个 relay 不能是它自己的所有者。
此函数调用中的所有 Ether 都将被添加到 relay 的 stake 中。
它的 unstake delay 将被分配给 unstakeDelay
,但新值必须大于或等于当前值。
发出一个 Staked
事件。
registerRelay(uint256 transactionFee, string url)
external将调用者注册为一个 relay。 该 relay 必须已经 stake,并且不能是合约(即,此函数必须直接从一个 EOA 调用)。
此函数可以被多次调用,发出新的 RelayAdded
事件。请注意,接收到的 transactionFee
不会被 relayCall
强制执行。
发出一个 RelayAdded
事件。
removeRelayByOwner(address relay)
external移除(注销)一个 relay。未注册(但已 stake)的 relay 也可以被移除。
只能由该 relay 的所有者调用。在该 relay 的 unstakeDelay
经过后,可以调用 unstake
。
发出一个 RelayRemoved
事件。
unstake(address relay)
externalgetRelay(address relay) → uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, enum IRelayHub.RelayState state
external返回一个 relay 的状态。请注意,relay 可以在 unstake 或受到惩罚时被删除,导致此函数返回一个空条目。
depositFor(address target)
external为一个合约存入 Ether,以便它可以接收(并支付)中继的交易。
未使用的余额只能由合约本身通过调用 withdraw
来提取。
发出一个 Deposited
事件。
balanceOf(address target) → uint256
external返回一个账户的存款。这些可以是合约的资金,也可以是 relay 所有者的收入。
withdraw(uint256 amount, address payable dest)
externalcanRelay(address relay, address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData) → uint256 status, bytes recipientContext
external检查 RelayHub
是否会接受一个中继的操作。
要发生这种情况,必须满足多个条件:
- 所有参数必须由发送者(from
)签名
- 发送者的 nonce 必须是当前的 nonce
- 接收者必须接受此交易(通过 acceptRelayedCall
)
返回一个 PreconditionCheck
值(当交易可以被中继时返回 OK
),或者,如果它在 acceptRelayedCall
中返回一个接收者特定的错误代码。
relayCall(address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData)
external中继一个交易。
为了使其成功,必须满足多个条件:
- canRelay
必须 return PreconditionCheck.OK
- 发送者必须是一个已注册的 relay
- 交易的 gas 价格必须大于或等于发送者所请求的价格
- 交易必须有足够的 gas,以防止所有内部交易(对接收者的调用)使用所有可用 gas 时耗尽 gas
- 接收者必须有足够的余额来支付 relay 最坏情况下的费用(即,当所有 gas 都被消耗时)
如果满足所有条件,则调用将被中继,并且接收者将被收费。 preRelayedCall
、编码的函数和 postRelayedCall
将按该顺序被调用。
参数:
- from
:发起请求的客户端
- to
:目标 IRelayRecipient
合约
- encodedFunction
:要中继的函数调用,包括数据
- transactionFee
:relay 收取的超过实际 gas 成本的费用(%)
- gasPrice
:客户端愿意支付的 gas 价格
- gasLimit
:调用编码函数时转发的 gas
- nonce
:客户端的 nonce
- signature
:客户端对所有先前参数以及 relay 和 RelayHub 地址的签名
- approvalData
:dapp 特定的数据,转发到 acceptRelayedCall
。此值未被 RelayHub
验证,但它仍然可以用于例如签名。
发出一个 TransactionRelayed
事件。
requiredGas(uint256 relayedCallStipend) → uint256
external返回应该转发给 relayCall
调用的 gas 数量,以便中继一个将花费最多 relayedCallStipend
gas 的交易。
maxPossibleCharge(uint256 relayedCallStipend, uint256 gasPrice, uint256 transactionFee) → uint256
external返回最大接收者费用,给定转发的 gas 数量、gas 价格和 relay 费用。
penalizeRepeatedNonce(bytes unsignedTx1, bytes signature1, bytes unsignedTx2, bytes signature2)
external惩罚一个使用相同 nonce(仅使第一个有效)和不同数据(gas 价格、gas 限制等可能不同)签署两个交易的 relay。
必须提供两个交易的(未签名)交易数据和签名。
penalizeIllegalTransaction(bytes unsignedTx, bytes signature)
external惩罚一个发送未以 RelayHub
的 registerRelay
或 relayCall
为目标的交易的 relay。
getNonce(address from) → uint256
external返回 RelayHub
中一个账户的 nonce。
Staked(address relay, uint256 stake, uint256 unstakeDelay)
event当一个 relay 的 stake 或 unstakeDelay 增加时发出
RelayAdded(address relay, address owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url)
event当一个 relay 被注册或重新注册时发出。查看这些事件(并过滤掉 RelayRemoved
事件)可以让客户端发现可用的 relay 列表。
RelayRemoved(address relay, uint256 unstakeTime)
event当一个 relay 被移除(注销)时发出。unstakeTime
是可以调用 unstake 的时间。
Unstaked(address relay, uint256 stake)
event当一个 relay 被解除 stake 时发出,包括返回的 stake。
Deposited(address recipient, address from, uint256 amount)
event当 depositFor
被调用时发出,包括被资助的金额和账户。
Withdrawn(address account, address dest, uint256 amount)
event当一个账户从 RelayHub
提取资金时发出。
CanRelayFailed(address relay, address from, address to, bytes4 selector, uint256 reason)
event当试图中继一个调用失败时发出。
这可能是由于不正确的 relayCall
参数,或者接收者不接受该中继的调用。实际的中继调用没有被执行,并且接收者没有被收费。
reason
参数包含一个错误代码:值 1-10 对应于 PreconditionCheck
条目,并且大于 10 的值是从 acceptRelayedCall
返回的自定义接收者错误代码。
TransactionRelayed(address relay, address from, address to, bytes4 selector, enum IRelayHub.RelayCallStatus status, uint256 charge)
event当一个交易被中继时发出。 在监视 relay 的操作和对合约的中继调用时很有用
请注意,实际的编码函数可能会被回滚:这在 status
参数中指示。
charge
是从接收者余额中扣除的 Ether 值,支付给 relay 的所有者。
Penalized(address relay, address sender, uint256 amount)
event当一个 relay 受到惩罚时发出。
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!