Wormhole资产跨链项目代码解析
Posted mutourend
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Wormhole资产跨链项目代码解析相关的知识,希望对你有一定的参考价值。
1. 引言
Wormhole支持基于Solana与多个链进行资产转移,开源代码为:
当前已上线V1,已支持:
未来V2将支持:
2. 关键代码解析
支持的chain_id命名及支持的action类型有:
ActionGuardianSetUpdate Action = 0x01
ActionContractUpgrade Action = 0x02
ActionTransfer Action = 0x10
// ChainIDSolana is the ChainID of Solana
ChainIDSolana = 1
// ChainIDEthereum is the ChainID of Ethereum
ChainIDEthereum = 2
// ChainIDTerra is the ChainID of Terra
ChainIDTerra = 3
以 0x22557d54dfc8adb1a888478258f89abb4a11a7069c983e6db1bc14de6d6e7946 lockAssets交易为例:
会将相应的token发送到black hole: 0x0000000000000000000000000000000000000000 中 burn掉:
if (isWrappedAsset[asset]) {
WrappedAsset(asset).burn(msg.sender, amount);
asset_chain = WrappedAsset(asset).assetChain();
asset_address = WrappedAsset(asset).assetAddress();
}
Solana端Wrapped Ether (Wormhole) token地址为:FeGn77dhg1KXRRFeSwwMiykZnZPw5JXW6naf2aQgZDQf
Solana端Wormhole合约中的mint交易为:https://solscan.io/tx/wwwFsn2cx69HPEg64iRErNHaUx4GbLt48vGSgFguAfgDBZtiYjFsXc6eH7CzEJuMv8RQ1UD41JZ1iMy5CYW5Hvr
以太坊端Wormhole:Solana Bridge合约地址为:
Solana端Wormhole合约地址为:
以太坊端Wormhole:Solana Bridge合约关键函数有:
- (1)submitVAA(),若action为0x10,即ActionTransfer,则将Solana中的资产通过Wormhole:Solana Bridge合约转移出来,对应的target_chain应设置为以太坊,若资产为以太坊ERC20 token(即token_chain=2),则直接将Solana Bridge合约中的ERC20 token转移到指定账号即可;若资产为其他链上的值,则在以太坊上根据token_chain和token_address在以太坊上创建相应的wrapped合约,往该合约mint或burn。
if (token_chain != CHAIN_ID) {
bytes32 token_address = data.toBytes32(71);
bytes32 asset_id = keccak256(abi.encodePacked(token_chain, token_address));
// if yes: mint to address
// if no: create and mint
address wrapped_asset = wrappedAssets[asset_id];
if (wrapped_asset == address(0)) {
uint8 asset_decimals = data.toUint8(103);
wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address, asset_decimals);
}
WrappedAsset(wrapped_asset).mint(target_address, amount);
} else {
address token_address = data.toAddress(71 + 12);
uint8 decimals = ERC20(token_address).decimals();
// Readjust decimals if they've previously been truncated
if (decimals > 9) {
amount = amount.mul(10 ** uint256(decimals - 9));
}
IERC20(token_address).safeTransfer(target_address, amount);
}
- (2)LockAssets(),用于将以太坊上的资产转移至Solana。
if (isWrappedAsset[asset]) { //非以太坊上发行的token,通过wrapped合约销毁
WrappedAsset(asset).burn(msg.sender, amount);
asset_chain = WrappedAsset(asset).assetChain();
asset_address = WrappedAsset(asset).assetAddress();
} else { //以太坊上的ERC20 token,直接将相应金额转入本Solana Bridge合约。
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
// The amount that was transferred in is the delta between balance before and after the transfer.
// This is to properly handle tokens that charge a fee on transfer.
amount = balanceAfter.sub(balanceBefore);
// Decimal adjust amount - we keep the dust
if (decimals > 9) {
uint256 original_amount = amount;
amount = amount.div(10 ** uint256(decimals - 9));
if (refund_dust) {
IERC20(asset).safeTransfer(msg.sender, original_amount.mod(10 ** uint256(decimals - 9)));
}
decimals = 9;
}
require(balanceAfter.div(10 ** uint256(ERC20(asset).decimals() - 9)) <= MAX_UINT64, "bridge balance would exceed maximum");
asset_address = bytes32(uint256(asset));
}
以太坊Wormhole:Solana Bridge合约guardian set更新交易为:
0x833e7bec034d9904713d6a7dc13d197ec41af2d0611122603289a1e69ae72c43
更新Guardianset的submitVAA(bytes calldata vaa)调用数据为:0x01000000000100e799da27b55cff0f44b57b0d06065d573de679b0ba4ddd7d94c57e58cc31ea166c757a7dd034d7d6f3696a846a30d8aad6499254c71bdd3888147aaac092558d006019dfc2010000000113187727cdd17c8142fe9b29a066f577548423af0ee4276d2ef965b37a492c5f0f85e9a558f82895bd854e8be1ee471e70a152b3fbb093057efbb9e20f43f98818f1f0e93a921c2a74ed868213946a358079071c82011b4a542c7d67f290ef4181ce6b2b0f7580aa7e036dc199bf0e152c71004716ae0aa747e7af00e4b5f69b8e8e49adbe7b1a2b6bdd81f346c155a290149bb2f58332aba8dc31217df483501ef73c0e6f9a242b3564f9e65fbd94fcbc332a0deb1fca1307c7beef596f209cf393f3d50d41e4a0f9f51a742452e9c2b4bd131a934c10f97b80455669e0718fca4dc271f4ef43ff624a6b730a339ef1041bb88a04dda307bc4f5e97c4856c068d1a4182a85cf8ca288d9077160ff9d6583b63aa20eff40b7dde2e046412ebcc6563831aa7a9b2104e0c58b3514ed7abb1408f91058bfedac3caecb03f8ec22c1be3727bbc8342f631ec9a20d8a6fb44fe97c6dfcd9f09c8e49757486e98887b49373620290d963729e263fdfedf000121046578253312861e83f28bd5
3. Solana中HUSD转移至以太坊HUSD ERC20 token示例
{
"chainId": 101,
"address": "BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX",
"symbol": "wHUSD",
"name": "HUSD Stablecoin (Wormhole)",
"decimals": 8,
"logoURI": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/BybpSTBoZHsmKnfxYG47GDhVPKrnEKX31CScShbrzUhX/logo.png",
"tags": [
"wrapped",
"wormhole"
],
"extensions": {
"website": "https://www.stcoins.com/",
"address": "0xdf574c24545e5ffecb9a659c229253d4111d87e1",
"bridgeContract": "https://etherscan.io/address/0xf92cD566Ea4864356C5491c177A430C222d7e678",
"assetContract": "https://etherscan.io/address/0xdf574c24545e5ffecb9a659c229253d4111d87e1",
"coingeckoId": "husd"
}
}
将Solana中的HUSD转移至以太坊中的HUSD ERC20 token示例为:
- 以太坊端的交易为:https://etherscan.io/tx/0x4e3a1f0ab7f6551ae6e50598ff3c89c8933d7fa6a233d9f509d7b93379b0c96b
- Solana端的交易为:https://explorer.solana.com/tx/5UapTG2W81XBUMTLEEcwKmqmwEoyww5ZvF4F3S2aLnkNyXRWvVF637rAEjyPVsEjXiPVNmHT7KPrExd2HXq2MBDt
以太坊接收地址为:0xe0b2026e3db1606ef0beb764ccdf7b3cee30db4a
Solana端发送地址为:EH4Ee8SikRZzWWujY88wwXSaAv2AosgdurgjEvS32P4K(Owner为:CaBptTpgdBjhiLfdPjJzz7rRWuwNhaZXhvygM8zxuuyk)
HUSD资产由Solana端转移出时,详细的流程为:
- 1)将相应的转出金额授权给Wormhole合约的账号:9zyPU1mjgzaVyQsYwKJJ7AhVz5bgx5uc1NPABvAcUXsT
- 2)由发起转出账号将手续费(0.02046048 SOL)发给Wormhole合约的账号
- 3)调用Wormhole合约的Transfer Assets Out函数,其中在Instruction Data中包含了接收资产的以太坊地址:
– 3.1)由转出账号创建相应的Wormhole合约账号:
– 3.2)将转出账号的相应资产由 1)中授权的账号 进行Burn销毁:
完整的log为:
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]
Instruction: Approve
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2297 of 200000 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC invoke [1]
Instruction: TransferOut
wrapped transfer out
deriving key
deploying contract
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Instruction: Burn
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2886 of 136268 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC consumed 67592 of 200000 compute units
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC success
- 4)Validator会监听3)中的Burn操作,详细代码见:
wormhole/bridge/pkg/solana/client.go
:
accounts, err := rpcClient.GetProgramAccounts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
Commitment: rpc.CommitmentMax, // TODO: deprecated, use Finalized
Filters: []rpc.RPCFilter{
{
DataSize: 1184, // Search for TransferOutProposal accounts
},
{
Memcmp: &rpc.RPCFilterMemcmp{
Offset: 1140, // Offset of VaaTime
Bytes: solana.Base58{0, 0, 0, 0}, // VAA time is 0 when no VAA is present
},
},
},
})
// 相应结果的proposal解析结果为:
lock := &common.ChainLock{
TxHash: txHash,
Timestamp: proposal.LockupTime,
Nonce: proposal.Nonce,
SourceAddress: proposal.SourceAddress,
TargetAddress: proposal.ForeignAddress,
SourceChain: vaa.ChainIDSolana,
TargetChain: proposal.ToChainID,
TokenChain: proposal.Asset.Chain,
TokenAddress: proposal.Asset.Address,
TokenDecimals: proposal.Asset.Decimals,
Amount: proposal.Amount,
}
Validator提交通过SubmitVAA提交Unlock Sig,详细交易见:https://explorer.solana.com/tx/5SKH9ppMdPQJn5EtvNdW9jztmzf5y3YZdxX25UXPaFU1yFcYCmUkmGuz4D32iaWBMMpSivg4zjzh3qkzHsUqXBjF,详细代码见:wormhole/solana/agent/src/main.rs
中的post_vaa() 函数:
VAABody::Transfer(t) => {
if t.source_chain == CHAIN_ID_SOLANA {
// Solana (any) -> Ethereum (any)
let transfer_key = Bridge::derive_transfer_id(
program_id,
&bridge_key,
t.asset.chain,
t.asset.address,
t.target_chain,
t.target_address,
t.source_address,
t.nonce,
)?;
accounts.push(AccountMeta::new(transfer_key, false))
}
......
}
5)用户Agent监听4)中的Unlock签名,然后Aggregates & Publish Signatures 向以太坊发起SubmitVAA()交易。详细代码见:wormhole/bridge/pkg/solana/submitter.go
。
// handleObservation processes a remote VAA observation, verifies it, checks whether the VAA has met quorum,
// and assembles and submits a valid VAA if possible.
func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObservation) {
// SECURITY: at this point, observations received from the p2p network are fully untrusted (all fields!)
//
// Note that observations are never tied to the (verified) p2p identity key - the p2p network
// identity is completely decoupled from the guardian identity, p2p is just transport.
.......
}
timeout, cancel := context.WithTimeout(ctx, 120*time.Second)
res, err := c.SubmitVAA(timeout, &agentv1.SubmitVAARequest{Vaa: vaaBytes, SkipPreflight: e.skipPreflight})
具体交易见:
https://etherscan.io/tx/0x4e3a1f0ab7f6551ae6e50598ff3c89c8933d7fa6a233d9f509d7b93379b0c96b
4. 以太坊HUSD ERC20 token转移至Solana中HUSD示例
以太坊端交易为:https://etherscan.io/tx/0xffb94e1578a44730baac95782bf8f436ca7bdc5d6ca28c7b0811eb139a45fd83,转出账号为:0x47ed57f375d3dddae2ded7a6de522c35bc9419af。
Solana端交易为:https://explorer.solana.com/tx/4pn6CxjXYuA4nRUdUjXTK5yivwxSRSgDJJ9Rg37KNo5tQnKq61RWfxVmsmsqroUdS18NnQ7R1mz3yZbNajamS8SZ,转入账号为:CUAqsZjcsoaHJPfQTPLLvJRHpQuegguBwEzKehdztovT。
详细流程为:
1)用户调用LockAssets()将以太坊ERC20 token转移至Wormhole: Solana Bridge合约。
2)Validator监听LogTokensLocked
event,并对其进行签名。
LogTokensLocked (uint8 target_chain, uint8 token_chain, uint8 token_decimals, index_topic_1 bytes32 token, index_topic_2 bytes32 sender, bytes32 recipient, uint256 amount, uint32 nonce)View Source
具体值为:
target_chain :
1
token_chain :
2
token_decimals :
8
recipient :
AA665DFB6CDD379DACB27DCCABDB9888128F6B47709FDA8809E72F1CA300901C
amount :
1709034375
nonce :
1409
// handleLockup processes a lockup received from a chain and instantiates our deterministic copy of the VAA. A lockup
// event may be received multiple times until it has been successfully completed.
func (p *Processor) handleLockup(ctx context.Context, k *common.ChainLock) {
......
// All nodes will create the exact same VAA and sign its digest.
// Consensus is established on this digest.
v := &vaa.VAA{
Version: vaa.SupportedVAAVersion,
GuardianSetIndex: p.gs.Index,
Signatures: nil,
Timestamp: k.Timestamp,
Payload: &vaa.BodyTransfer{
Nonce: k.Nonce,
SourceChain: k.SourceChain,
TargetChain: k.TargetChain,
SourceAddress: k.SourceAddress,
TargetAddress: k.TargetAddress,
Asset: &vaa.AssetMeta{
Chain: k.TokenChain,
Address: k.TokenAddress,
Decimals: k.TokenDecimals,
},
Amount: k.Amount,
},
}
// Generate digest of the unsigned VAA.
digest, err := v.SigningMsg()
if err != nil {
panic(err)
}
// Sign the digest using our node's guardian key.
s, err := crypto.Sign(digest.Bytes(), p.gk)
if err != nil {
panic(err)
}
......
p.broadcastSignature(v, s)
}
3)提交post_vaa()给Solana转账。
else {
// Foreign (native) -> Solana (wrapped)
let wrapped_key = Bridge::derive_wrapped_asset_id(
program_id,
&bridge_key,
t.asset.chain,
t.asset.decimals,
t.asset.address,
)?;
let wrapped_meta_key =
Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_key)?;
accounts.push(AccountMeta::new_readonly(spl_token::id(), false));
accounts.push(AccountMeta::new(wrapped_key, false));
accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false));
accounts.push(AccountMeta::new(wrapped_meta_key, false));
}
相应的log为:
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC invoke [1]
Instruction: PostVAA
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Instruction: MintTo
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2779 of 104301 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
deriving key
deploying contract
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC consumed 124268 of 200000 compute units
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC success
以上是关于Wormhole资产跨链项目代码解析的主要内容,如果未能解决你的问题,请参考以下文章