Wormhole支持基于Solana与多个链进行资产转移,开源代码为:
- https://github.com/certusone/wormhole/tree/main
实际部署配置信息参见:
- https://github.com/certusone/wormhole-networks
当前已上线V1,已支持:
未来V2将支持:
Connected chain contracts:
NetworkBridge contract addresssEthereum Mainnet (Bridge)0xf92cD566Ea4864356C5491c177A430C222d7e678
(verified on Etherscan)Ethereum Mainnet (Token)0x9A5e27995309a03f8B583feBdE7eF289FcCdC6Ae
(verified on Etherscan)Solana Mainnet BetaWormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC
Guardian set with 19 decentralized guardians:
NetworkGuardian Set initializationSolana3mQpDW2ofC3YpAsDzP4w5FNVzYRSmJjKoio4fcapL7xDAdaatvvKXCzrd2cfHiVNwYZs4hH1ePZ9Hep4zaasL27L
Ethereum0x833e7bec034d9904713d6a7dc13d197ec41af2d0611122603289a1e69ae72c43
19个Guardians采用PoA共识,不存在leader。Wormhole V1版本中的调用Solana合约的submitVAA()
函数的手续费由Guardian承担。由于没有leader,会存在有多个同时提交的情况:
- https://explorer.solana.com/tx/67dyCCe2cb3UEab8J64p8gMTxc5WaiQww64JUXQddyjt53HfCzumgt1JhsKjG2qTo2phHS5e6rSTzWD8gupa8PgP 成功。
- https://explorer.solana.com/tx/TZKNGEK5bkd3kY92NKXDxAtA8ZTmiR4C3MvyyBgMT6P3drn5XhcUUdUPaL7WViZkN6wqRWCBRKvNzMuvwgredty 失败,Solana合约中报错:AlreadyExists。
- https://explorer.solana.com/tx/2nbGGDxyqRKb9edcraZK3EoPHVq8CGm5BQEy6iiVzipzDqoWD3Smx9bTvapWjDYUhVt8boQBn6GvdGQRdrdrBDJV 失败,Solana合约中报错:AlreadyExists。
- https://explorer.solana.com/tx/4d2KwDKRtifdq6NCNztF3wk6iZBD77fq7aRf7TSnAgeVPL9fRUj3PtkhNL2GJ9M5TQXeku6ffMLfdAF7C5dyzwgj 失败,Solana合约中报错:AlreadyExists。
- https://explorer.solana.com/tx/4q55JYoo5Cb8XaXpxsZVyBdXs1imHhv44ygdti6Lf49ZddFzy3gCknRNiAHr5ZQmhXd9wdBkUeZKubAZWHMQTNet 合约中报错:AlreadyExists。
Solana端合约日志为:
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC invoke [1]
Instruction: VerifySignatures
Error: AlreadyExists
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC consumed 50274 of 200000 compute units
Program WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC failed: custom program error: 0xb
Transaction failed: Error processing Instruction 1: Unknown instruction error
2.1 关键代码解析
支持的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合约地址为:
- https://etherscan.io/address/0xf92cd566ea4864356c5491c177a430c222d7e678
Solana端Wormhole合约地址为:
- WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC
以太坊端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)) {
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})
相应的submitVAA()
data 解析为:
0x01
00000001
0d
00a89efafc639d6fdb746a2a858d7f532bce4bda1314ae34b6ff68a7862d9ef8dc351989a274709e9dc2b2db324a39b8a19fc69add9f42c2e96dd049571bc5452b01
0112dbc46c6a5585829600d4acaaea44dc9f66df32173c2b49f4700df88499a1db4960219683e96541c8f53660228f05d01a5c8cce8fd16e6a0b90cc3d913475b300
02558eef0c8813b7634a8a73955c404ea3c91826ecdbc1272635900ed0b0aee101570c7ab06d41c783c913906b8a553777914e3b783611f93095b3efc0afe67f6801
03445546203c8d9505cee405fdc62ddd4bf1f20ba9e482a373bc9889c885ea7627060d3c08a0b37b570ca14fc7aae8afdbbf022409b2835e63c71c16a154e852c701
04743d7d1e43db5efb32264bdaecd4cad4a1c7ef767b338543f768b083a7b2fc0b198bbaf96ac1c10b53b48f6bb9ed2b2421a49559931af214b6cf0a930f92589301
050dd0655fb879741c94247bb3cf96616f21b35db519f030568b29965add9c7fa23a59de432dcc3f45ecf56399a25ff9d5663816cefe77930450c6ec7f6625472300
076d748bf0fde5cfb93a0dd51cdf98b305c7e44aa9501bad1758061a68715fe35b445b1e94d9058dbd5564805d3a637bd938501027444e93f491e509d458ed7aec01
08b4861bdd15ae8127175c8fe899018ccb43800188af3db27008865e153ed7f9681227778b4cbec7be5a519240730672a44f3ee22b1cf63828299fca157787b50601
0a711a8b9e8201ba17b3072af956217245018f2c9f26fb68bd2aa4e41de405503c6e35d5d73140b38d7e25f9da6c9c05925cfe3c6b60de35edb3380c98aa7a210301
0b8372a1a3a528ccdcb68e5bc3219c474df1e5b78f58ea971b62a6f3b96e408ba67b88863dd9694f5778e4cbe3697c24e78b3b76213ecb60513230ad737c6b214b01
0d8677291bd7e1a8b67a44e5f886a6247d9331a96d46a815fae1781ea72196965234782191666e02016083456cf33573bfa305844a414a4b695fd0ce42f7106e4300
10a1426d82f020df651b1165c8eb165bf6daaeb81e081aeede1490cdf1563fa65e604826f1e6f820b891bccfb0be451afef9a9f62cc92c255a666c8c3251f1bf3f01
12fba1acf05bf2b79eb610d7b979b898234d119351a3b4d1b4ba6918e4926ac6081974799460485b2444ec2f3ddca95426a2f28790c1bed850daf188e6c7e5025301
61405447
10
00016ed6
01
02
c544e189fb7a9ec43914e407145ac395144d22445066411844fd356817b7ea80000000000000000000000000
e0b2026e3db1606ef0beb764ccdf7b3cee30db4a
02
000000000000000000000000df574c24545e5ffecb9a659c229253d4111d87e1
08
0000000000000000000000000000000000000000000000000001614b2224f700
有:
>>> hex(388450300000000)
'0x1614b2224f700'
2.3 以太坊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
3. Wormhole V2
Token Bridge 合约为:
NetworkWormhole token bridge contract addressEthereum Mainnet0x3ee18B2214AFF97000D974cf647E7C347E8fa585
Solana MainnetwormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb
Connected chain contracts合约为:
NetworkWormhole core contract addressEthereum Mainnet (Core)0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Ethereum Mainnet (Impl)0x736d2a394f7810c17b3c6fed017d5bc7d60c077d
Binance Smart Chain (Core)0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Binance Smart Chain (Impl)0x736d2a394f7810c17b3c6fed017d5bc7d60c077d
Solana Mainnetworm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth
Terra Columbus-5terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5
Eth and BSC use the same deployer key, leading to identical addresses. This key has no privileges.
详细的Wormhole V2 token bridge流程参见: https://blog.mercurial.finance/how-to-transfer-eth-assets-to-solana-using-wormhole-v2-514e728dc4fc
详细的Wormhole V2 NFT bridge流程参见: https://decrypt.co/81764/ethereum-solana-wormhole-send-nfts-across-blockchains