本文档描述了一种通用的Token Bridge应用,旨在利用Wormhole消息传递协议在不同链之间转移符合标准的Token,创建唯一的Wrapped Asset表示。
[TOC]
使用 Wormhole 消息传递协议在不同的连接链之间转移代币。
去中心化金融生态系统正朝着一个方向发展,即具有不同优势的不同链成为各种协议的所在地。然而,一个代币通常只在一条链上铸造,因此与其它链上的生态系统和协议断开连接。
每个链通常都有一个事实上的代币发行标准,比如以太坊上的 ERC-20 和 Solana 上的 SPL。这些标准虽然不完全相同,但都实现了一个类似的接口,具有相同的概念,如拥有、铸造、转移和销毁代币。
为了连接链,一个代币理想情况下应该有一个原生铸币(native mint)——即最初在其上创建的链上的原始代币——以及在其他链上的一个包装版本,代表原生代币的所有权。
虽然 Wormhole 消息传递协议提供了一种在链之间证明和传递消息的方式,理论上可以用于实现单个代币的桥接,但这将需要为每个代币进行手动工程工作,并创建不兼容的协议,导致糟糕的 UX。
我们希望使用 Wormhole 消息传递协议实现一个通用的代币桥,能够桥接任何符合标准的代币在链之间,从而按需在每个连接的链上创建唯一的包装表示。
在代币桥网络的每个链上,都将有一个代币桥端点程序。
这些程序将管理 payload 的授权(发射器过滤)、外链代币的包装表示(“包装资产”)和托管锁定的代币。
对于出站转移,合约将有一个锁定方法,要么锁定原生代币并产生一个相应的 Transfer 消息,该消息被发布到 Wormhole,要么销毁一个包装代币并产生/发布所述消息。
对于入站转移,它们可以消费、验证和处理包含代币桥 payload 的 Wormhole 消息。
将有五种不同的 payload:
Transfer
- 将触发释放锁定的代币或铸造包装代币。TransferWithPayload
- 将触发释放锁定的代币或铸造包装代币,并带有额外的特定于领域的 payload。AssetMeta
- 证明资产元数据(首次转移前需要)。RegisterChain
- 注册外链的代币桥合约(发射器地址)。UpgradeContract
- 升级合约。由于任何人都可以使用 Wormhole 发布与代币桥 payload 格式匹配的消息,因此需要实现一个授权 payload。这可以使用 (emitter_chain, emitter_address)
元组来完成。代币桥的每个端点都需要知道其他链上相应其他端点的地址。代币桥端点的这种注册通过 RegisterChain
实现,其中可以注册一个 (chain_id, emitter_address)
元组。每个链只能注册一个端点。端点是不可变的。只有当发射器是硬编码的治理合约时,此 payload 才会被接受。
为了将资产转移到另一个链,用户需要调用桥接合约的 transfer
(或 transferWithPayload
)方法,提供接收者详细信息和他们愿意支付的相应费用。合约要么将代币保存在托管账户中(如果是原生代币),要么销毁包装资产。包装资产可以被销毁,因为一旦代币被转回,它们就可以被自由铸造,这样总供应量就可以表明当前在此链上持有的代币总数。锁定后,合约将向 Wormhole 发布一个 Transfer
(或 TransferWithPayload
)消息。一旦消息被守护者签名,它就可以被发布到转移的目标链。在赎回时(见下面的 completeTransfer
),目标链将释放托管中的原生代币或铸造一个包装资产,这取决于它是否是那里的原生代币。
代币桥保证对于每个非原生代币,每个链上都会有一个唯一的包装资产。换句话说,将原生代币从链 A 转移到链 C 将导致与从 A 先转移到 B,然后再从 B 转移到 C 相同的包装代币,并且不会发生双重包装。
该程序将跟踪已消费的消息摘要(包括一个 nonce)以防止重放。
要在目标链上赎回交易,必须将 VAA 发布到目标代币桥。由于 VAA 包括来自守护者的签名,因此通常是谁将其提交到目标链并不重要,因为 VAA 无法被欺骗,并且 VAA 包括代币完成时将被发送到的目标地址。
TransferWithPayload
是一个例外,它必须由目标地址赎回,因为它包含必须由接收者处理的附加 payload (例如,代币交换指令)。
completeTransfer
方法将接受一个费用接收者。如果设置了该字段,指定的费用金额将发送给费用接收者,剩余的金额将发送给转移的预期接收者。
这允许转移由独立的 relayers 完成,以改善用户的 UX,用户只需要发送一个交易,只要费用足够并且代币被任何充当 relayer 的人接受。
为了保持 Transfer
消息的小型化,它们不携带代币的所有元数据。然而,这意味着在代币首次转移到新链之前,需要桥接元数据并创建包装资产。在这种情况下,元数据包括小数位数,这是实例化代币的核心要求。
代币的元数据可以通过在其各自的原生链上调用 attestToken
来证明,这将产生一个 AssetMeta
Wormhole 消息。此消息可用于在使用详细信息证明状态和初始化 Wormhole 网络中的任何链上的 WrappedAsset。代币由元组 (chain_id, chain_address)
标识,元数据应映射到此标识符。对于给定的标识符,一个包装资产可能只被创建一次,并且不会被更新。
由于某些受支持链的约束,通过代币桥传递的所有代币数量都被截断为最多 8 位小数。
任何链的实现都必须确保任何代币在任何给定时间(所有目标链加起来)只有最多 MaxUint64 个单位(后移位)被桥接到 Wormhole 网络中,即使该槽是 32 字节长(理论上适合 uint256)。
由于存款期间的截断而无法转移的代币“粉尘”需要退还给用户。
Examples: (例子:)
1000000000000000000
, over the wormhole it is passed as: 100000000
. (数量为“1”的 18 位小数的以太坊代币最初表示为:1000000000000000000
,通过 wormhole 传递时表示为:100000000
。)20000
and is passed over the wormhole without a decimal shift. (数量为“2”的 4 位小数的代币表示为 20000
,并通过 wormhole 传递,没有小数位移。)Handling on the target Chains: (在目标链上的处理:)
Implementations on target chains can handle the decimal shift in one of the following ways: (目标链上的实现可以通过以下方式之一处理小数位移:)
AssetMeta
) it can do a decimal shift back to the original decimal amount. This allows for out-of-the-box interoperability of DeFi protocols across for example different EVM environments. (如果链支持原始小数位数(从 AssetMeta
中得知),它可以将小数位移回原始小数位数。这允许 DeFi 协议开箱即用地跨不同 EVM 环境进行互操作。)Proposed bridge interface: (提议的桥接接口:)
attestToken(address token)
- Produce a AssetMeta
message for a given token (为给定的代币生成一个 AssetMeta
消息)
transfer(address token, uint64-uint256 amount (size depending on chains standards), uint16 recipient_chain, bytes32 recipient, uint256 fee)
- Initiate
a Transfer
. Amount in the tokens native decimals. (发起一个 Transfer
。金额为代币的原始小数位数。)
transferWithPayload(address token, uint64-uint256 amount (size depending on chains standards), uint16 recipient_chain, bytes32 recipient, bytes payload)
- Initiate
a TransferWithPayload
. Amount in the tokens native decimals. payload
is an arbitrary binary blob. (发起一个 TransferWithPayload
。金额为代币的原始小数位数。 payload
是一个任意的二进制 blob。)
createWrapped(Message assetMeta)
- Creates a wrapped asset using AssetMeta
(使用 AssetMeta
创建一个包装资产)
completeTransfer(Message transfer)
- Execute a Transfer
message (执行一个 Transfer
消息)
completeTransferWithPayload(Message transfer)
- Execute a TransferWithPayload
message (执行一个 TransferWithPayload
消息)
registerChain(Message registerChain)
- Execute a RegisterChain
governance message (执行一个 RegisterChain
治理消息)
upgrade(Message upgrade)
- Execute a UpgradeContract
governance message (执行一个 UpgradeContract
治理消息)
Payloads: (payload:)
Transfer: (转移:)
PayloadID uint8 = 1
// Amount being transferred (big-endian uint256) (正在转移的金额(大端 uint256))
Amount uint256
// Address of the token. Left-zero-padded if shorter than 32 bytes (代币的地址。如果短于 32 字节,则左侧补零)
TokenAddress bytes32
// Chain ID of the token (代币的链 ID)
TokenChain uint16
// Address of the recipient. Left-zero-padded if shorter than 32 bytes (接收者的地址。如果短于 32 字节,则左侧补零)
To bytes32
// Chain ID of the recipient (接收者的链 ID)
ToChain uint16
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount. (用户愿意支付的代币数量(大端 uint256)作为 relayer 费用。必须 <= 金额。)
Fee uint256
TransferWithPayload: (带有 payload 的转移:)
PayloadID uint8 = 3
// Amount being transferred (big-endian uint256) (正在转移的金额(大端 uint256))
Amount uint256
// Address of the token. Left-zero-padded if shorter than 32 bytes (代币的地址。如果短于 32 字节,则左侧补零)
TokenAddress bytes32
// Chain ID of the token (代币的链 ID)
TokenChain uint16
// Address of the recipient. Left-zero-padded if shorter than 32 bytes (接收者的地址。如果短于 32 字节,则左侧补零)
To bytes32
// Chain ID of the recipient (接收者的链 ID)
ToChain uint16
// The address of the message sender on the source chain (源链上消息发送者的地址)
FromAddress bytes32
// Arbitrary payload (任意 payload)
Payload bytes
AssetMeta: (资产元数据:)
PayloadID uint8 = 2
// Address of the token. Left-zero-padded if shorter than 32 bytes (代币的地址。如果短于 32 字节,则左侧补零)
TokenAddress [32]uint8
// Chain ID of the token (代币的链 ID)
TokenChain uint16
// Number of decimals of the token
// (the native decimals, not truncated to 8) (代币的小数位数)
Decimals uint8
// Symbol of the token (UTF-8) (代币的符号 (UTF-8))
Symbol [32]uint8
// Name of the token (UTF-8) (代币的名称 (UTF-8))
Name [32]uint8
RegisterChain: (注册链:)
// Gov Header (治理头)
// Module Identifier ("TokenBridge" left-padded) (模块标识符(“TokenBridge”左侧补零))
Module [32]byte
// Governance Action ID (1 for RegisterChain) (治理操作 ID(RegisterChain 为 1))
Action uint8 = 1
// Target Chain (Where the governance action should be applied) (目标链(治理操作应应用的地方))
// (0 is a valid value for all chains) ((0 对于所有链都是有效值))
ChainId uint16
// Packet (数据包)
// Emitter Chain ID (发射器链 ID)
EmitterChainID uint16
// Emitter address. Left-zero-padded if shorter than 32 bytes (发射器地址。如果短于 32 字节,则左侧补零)
EmitterAddress [32]uint8
UpgradeContract: (升级合约:)
// Header (头)
// Module Identifier ("TokenBridge" left-padded) (模块标识符(“TokenBridge”左侧补零))
Module [32]byte
// Governance Action ID (2 for UpgradeContract) (治理操作 ID(UpgradeContract 为 2))
Action uint8 = 2
// Target Chain (Where the governance action should be applied) (目标链(治理操作应应用的地方))
ChainId uint16
// Packet (数据包)
// Address of the new contract (新合约的地址)
NewContract [32]uint8
A user who initiated a transfer should call completeTransfer
within 24 hours. Guardian Sets are guaranteed to be valid for at least 24 hours. If the user waits longer, it could be that the Guardian Set has changed between the time where the transfer was initiated and the the time the user attempts to redeem the VAA. Let's call the Guardian Set at the time of signing setA
and the Guardian Set at the time of redeeming on the target chain setB
. (发起转移的用户应在 24 小时内调用 completeTransfer
。守护者集合保证至少 24 小时有效。如果用户等待时间过长,则守护者集合可能在发起转移的时间和用户尝试赎回 VAA 的时间之间发生了变化。让我们将签名时的守护者集合称为 setA
,将目标链上赎回时的守护者集合称为 setB
。)
If setA != setB
and more than 24 hours have passed, there are multiple options for still redeeming the VAA on the target chain: (如果 setA != setB
且超过 24 小时,则仍有多个选项可在目标链上赎回 VAA:)
setB
. In this case, the VAA merely needs to be modified to have the new Guardian Set Index along with any setA
only guardian signatures removed to make a valid VAA. The updated VAA can then be be redeemed. The typescript sdk includes a repairVaa()
function to perform this automatically. (签署 VAA 的守护者法定人数可能仍然是 setB
的一部分。在这种情况下,只需修改 VAA,使其具有新的守护者集合索引,并删除任何仅 setA
守护者签名,即可生成有效的 VAA。然后可以赎回更新后的 VAA。typescript sdk 包含一个 repairVaa()
函数来自动执行此操作。)setA
and setB
is greater than 2/3 of setB
, but not all signatures of the VAA are from Guardians in setB
. Then it may be possible to gather signatures from the other Guardians from other sources. E.g. Wormholescan provides an API under (/api/v1/observations/:chain/:emitter/:sequence)[https://docs.wormholescan.io/#/Wormscan/find-observations-by-sequence]. (setA
和 setB
之间的交集大于 setB
的 2/3,但 VAA 的并非所有签名都来自 setB
中的守护者。那么可能可以从其他来源收集其他守护者的签名。例如,Wormholescan 在 (/api/v1/observations/:chain/:emitter/:sequence)[https://docs.wormholescan.io/#/Wormscan/find-observations-by-sequence] 下提供了一个 API。)send-observation-request
admin command. A new valid VAA with an updated Guardian Set Index is generated once enough Guardians have re-observed the old message. Note that this is only possible if a quorum of Guardians is running archive nodes that still include this transaction. (守护者可以使用 send-observation-request
管理命令向网络发送签名的重新观察请求。一旦有足够的守护者重新观察旧消息,就会生成一个带有更新的守护者集合索引的新有效 VAA。请注意,只有当守护者法定人数正在运行仍包含此交易的存档节点时,这才有可能。)Since there is no way for a token bridge endpoint to know which other chain already has wrapped assets set up for the native asset on its chain, there may be transfers initiated for assets that don't have wrapped assets set up yet on the target chain. However, the transfer will become executable once the wrapped asset is set up (which can be done any time). (由于代币桥端点无法知道哪个其他链已经为其链上的原生资产设置了包装资产,因此可能会为尚未在目标链上设置包装资产的资产发起转移。但是,一旦设置了包装资产(可以随时完成),转移将变为可执行。)
- 原文链接: github.com/wormhole-foun...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!