import { expect } from "chai"; import * as web3 from "@solana/web3.js"; import { createMint, getAccount, getAssociatedTokenAddressSync, getMint, getOrCreateAssociatedTokenAccount, mintTo, NATIVE_MINT, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { MockGuardians, GovernanceEmitter, MockEthereumTokenBridge, } from "../../../sdk/js/src/mock"; import { createApproveAuthoritySignerInstruction, createAttestTokenInstruction, createCompleteTransferNativeInstruction, createCompleteTransferWrappedInstruction, createCreateWrappedInstruction, createRegisterChainInstruction, createTransferNativeInstruction, createTransferNativeWithPayloadInstruction, createTransferWrappedInstruction, createTransferWrappedWithPayloadInstruction, deriveCustodyKey, deriveEndpointKey, deriveMintAuthorityKey, deriveRedeemerAccountKey, deriveWrappedMintKey, getAttestTokenAccounts, getCompleteTransferNativeAccounts, getCompleteTransferWrappedAccounts, getCreateWrappedAccounts, getEndpointRegistration, getInitializeAccounts, getRegisterChainAccounts, getTransferNativeAccounts, getTransferNativeWithPayloadAccounts, getTransferWrappedAccounts, getTransferWrappedWithPayloadAccounts, getUpgradeContractAccounts, getWrappedMeta, } from "../../../sdk/js/src/solana/tokenBridge"; import { postVaa } from "../../../sdk/js/src/solana/sendAndConfirmPostVaa"; import { BpfLoaderUpgradeable, getCompleteTransferNativeWithPayloadCpiAccounts, getCompleteTransferWrappedWithPayloadCpiAccounts, getTransferNativeWithPayloadCpiAccounts, getTransferWrappedWithPayloadCpiAccounts, NodeWallet, signSendAndConfirmTransaction, SplTokenMetadataProgram, } from "../../../sdk/js/src/solana"; import { deriveWormholeEmitterKey, getPostedMessage, getPostedVaa, getProgramSequenceTracker, } from "../../../sdk/js/src/solana/wormhole"; import { parseGovernanceVaa, parseAttestMetaVaa, parseAttestMetaPayload, parseTokenBridgeRegisterChainVaa, parseTokenTransferPayload, parseVaa, } from "../../../sdk/js/src/vaa"; import { CORE_BRIDGE_ADDRESS, TOKEN_BRIDGE_ADDRESS, ETHEREUM_TOKEN_BRIDGE_ADDRESS, GOVERNANCE_EMITTER_ADDRESS, GUARDIAN_KEYS, GUARDIAN_SET_INDEX, LOCALHOST, WETH_ADDRESS, } from "./helpers/consts"; import { ethAddressToBuffer, now } from "./helpers/utils"; import { getForeignAssetSolana, getIsWrappedAssetSolana, getOriginalAssetSolana, } from "../../../sdk/js/src/token_bridge"; import { ChainId } from "../../../sdk/js/src"; import { transferNativeSol, redeemAndUnwrapOnSolana, } from "../../../sdk/js/src/token_bridge"; describe("Token Bridge", () => { const connection = new web3.Connection(LOCALHOST, "processed"); const wallet = new NodeWallet(web3.Keypair.generate()); // for signing wormhole messages const guardians = new MockGuardians(GUARDIAN_SET_INDEX + 1, GUARDIAN_KEYS); const localVariables: any = {}; before("Airdrop SOL", async () => { await connection .requestAirdrop(wallet.key(), 1000 * web3.LAMPORTS_PER_SOL) .then(async (signature) => connection.confirmTransaction(signature)); }); before("Create Mint", async () => { localVariables.mint = await createMint( connection, wallet.signer(), wallet.key(), null, 9 ); localVariables.mintAta = await getOrCreateAssociatedTokenAccount( connection, wallet.signer(), localVariables.mint, wallet.key() ).then((account) => account.address); const mintToTx = await mintTo( connection, wallet.signer(), localVariables.mint, localVariables.mintAta, wallet.key(), 1000 * web3.LAMPORTS_PER_SOL ); }); before("Create Mint with Metadata", async () => { // TODO }); describe("Accounts", () => { // for generating governance wormhole messages const governance = new GovernanceEmitter( GOVERNANCE_EMITTER_ADDRESS.toBuffer().toString("hex") ); // token bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); const payer = new web3.PublicKey( "6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J" ); it("Instruction 0: Initialize", () => { const accounts = getInitializeAccounts(TOKEN_BRIDGE_ADDRESS, payer); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; }); it("Instruction 1: Attest Token", () => { const mint = NATIVE_MINT; const message = web3.Keypair.generate(); const accounts = getAttestTokenAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, mint, message.publicKey ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.mint.equals(mint)).is.true; expect(accounts.wrappedMeta.toString()).to.equal( "8xGY7Bx9cWocPYpKRe3sjCYTdm3YchFJFHmJC5FenW6B" ); expect(accounts.splMetadata.toString()).to.equal( "6dM4TqWyWJsbx7obrdLcviBkTafD5E8av61zfU6jq57X" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.wormholeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.wormholeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 2: Complete Native", () => { const mint = NATIVE_MINT; const mintAta = getAssociatedTokenAddressSync(mint, payer); const amountEncoded = 42069n; const fee = 0n; const nonce = 420; const timestamp = 23456789; const message = ethereumTokenBridge.publishTransferTokens( mint.toBuffer().toString("hex"), 1, amountEncoded, 1, mintAta.toBuffer().toString("hex"), fee, nonce, timestamp ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getCompleteTransferNativeAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.vaa.toString()).to.equal( "GMBEenvtgYkQrHZNkx3kYbdYEUYN1GFRGBFN172bk3cN" ); expect(accounts.claim.toString()).to.equal( "HzjTihvhEx7BbKnB2KHATNBwGFCEm2nnMG6c4Pwx6pPE" ); expect(accounts.endpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.to.equals(mintAta)).is.true; expect(accounts.toFees.equals(mintAta)).is.true; expect(accounts.custody.toString()).to.equal( "2Aczo4H847TNDsPradsVXQUZFquJ37ZoHhRmJ2MAqtiM" ); expect(accounts.mint.equals(mint)).is.true; expect(accounts.custodySigner.toString()).to.equal( "Eb8xqkMEZYeTnDse4BgWiHVByeUj3JDpgbuz98pWdgPE" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 3: Complete Wrapped", () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, payer); const amount = 4206942069n; const recipientChain = 1; const fee = 0n; const nonce = 420; const timestamp = 34567890; const message = ethereumTokenBridge.publishTransferTokens( tokenAddress.toString("hex"), tokenChain, amount, recipientChain, mintAta.toBuffer().toString("hex"), fee, nonce, timestamp ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getCompleteTransferWrappedAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.vaa.toString()).to.equal( "7NQnr9aG4xQCp9AjnF37L3CrBbM6GJ2gN98JeFjn7nnf" ); expect(accounts.claim.toString()).to.equal( "7Ae57QxvZMwCrknoDWpeaMTLbMP3LBeCJee6KaLEwxP6" ); expect(accounts.endpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.to.equals(mintAta)).is.true; expect(accounts.toFees.equals(mintAta)).is.true; expect(accounts.mint.equals(mint)).is.true; expect(accounts.wrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.mintAuthority.toString()).to.equal( "J2mhpFfGCwHtUjmeQGhJSa2yk5h3egRoSd1AUhaKx2WG" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 4: Transfer Wrapped", () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, payer); const message = web3.Keypair.generate(); const accounts = getTransferWrappedAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, payer, tokenChain, tokenAddress ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.from.equals(mintAta)).is.true; expect(accounts.fromOwner.equals(payer)).is.true; expect(accounts.mint.equals(mint)).is.true; expect(accounts.wrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.authoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.wormholeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.wormholeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 5: Transfer Native", () => { const mint = NATIVE_MINT; const mintAta = getAssociatedTokenAddressSync(mint, payer); const message = web3.Keypair.generate(); const accounts = getTransferNativeAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, mint ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.from.equals(mintAta)).is.true; expect(accounts.mint.equals(mint)).is.true; expect(accounts.custody.toString()).to.equal( "2Aczo4H847TNDsPradsVXQUZFquJ37ZoHhRmJ2MAqtiM" ); expect(accounts.authoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.custodySigner.toString()).to.equal( "Eb8xqkMEZYeTnDse4BgWiHVByeUj3JDpgbuz98pWdgPE" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.wormholeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.wormholeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 6: Register Chain", () => { const timestamp = 45678901; const message = governance.publishTokenBridgeRegisterChain( timestamp, 2, ETHEREUM_TOKEN_BRIDGE_ADDRESS ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getRegisterChainAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa ); // verify accounts const parsed = parseGovernanceVaa(signedVaa); expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.endpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.vaa.toString()).to.equal( "92pVby4LJPSyQxSSHLYv3EdpqWjH5bBoLGBJAeQkunf8" ); expect(accounts.claim.toString()).to.equal( "J5LWxMcXo1xmdZq57VD4wrUgvw5taizQ9QEPHooHTwJv" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 7: Create Wrapped", () => { const tokenAddress = WETH_ADDRESS; const decimals = 18; const symbol = "WETH"; const name = "Wrapped ETH"; const nonce = 420; const message = ethereumTokenBridge.publishAttestMeta( tokenAddress, decimals, symbol, name, nonce ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getCreateWrappedAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.endpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.vaa.toString()).to.equal( "AaZqjqvg8QirKetuR799Smfw5gyAWobUooGsvxzr1aoX" ); expect(accounts.claim.toString()).to.equal( "4dyk94hhqektDX9wUBCL1ZkyQC1Xn3QaTSAdJeZzbTcJ" ); expect(accounts.mint.toString()).to.equal( "3tUXFuBNWzZZ8p2xNx5UoWCH664M2KHdDAWrdZAD1VQ3" ); expect(accounts.wrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.splMetadata.toString()).to.equal( "46nJp6UehY8XpgNsSZFTamdXcwiSEQpRGvbBCt2KvVUf" ); expect(accounts.mintAuthority.toString()).to.equal( "J2mhpFfGCwHtUjmeQGhJSa2yk5h3egRoSd1AUhaKx2WG" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect( accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) ).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 8: Upgrade Contract", () => { const timestamp = 56789012; const chain = 1; const implementation = new web3.PublicKey( "2B5wMnErS8oKWV1wPTNQQhM1WLyxee2obtBMDtsYeHgA" ); const message = governance.publishTokenBridgeUpgradeContract( timestamp, chain, implementation.toString() ); const signedVaa = guardians.addSignatures(message, [0]); const accounts = getUpgradeContractAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.vaa.toString()).to.equal( "HM2U2HEfbjrkvYLvry8Sqfmd5cCVxq6RjdLUUrNW2ELR" ); expect(accounts.claim.toString()).to.equal( "9jDWWzAosaD6EWH9SMFT3ZwJnDZTeGcCdMU8H5Ba7dpx" ); expect(accounts.upgradeAuthority.toString()).to.equal( "B2LFmpNCkfBFpoorLy4BghGbZyi5sdRsbjxSSASpjUoA" ); expect(accounts.spill.equals(payer)).is.true; expect(accounts.implementation.equals(implementation)).is.true; expect(accounts.programData.toString()).to.equal( "3zHkdon6x9fUVqjxu6fCgdp3qMxLFZz59pj1H2NtnbGe" ); expect(accounts.tokenBridgeProgram.equals(TOKEN_BRIDGE_ADDRESS)).to.be .true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect( accounts.bpfLoaderUpgradeable.equals(BpfLoaderUpgradeable.programId) ).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; }); it("Instruction 11: Transfer Wrapped With Payload", () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, payer); const message = web3.Keypair.generate(); const accounts = getTransferWrappedWithPayloadAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, payer, tokenChain, tokenAddress ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.from.equals(mintAta)).is.true; expect(accounts.fromOwner.equals(payer)).is.true; expect(accounts.mint.equals(mint)).is.true; expect(accounts.wrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.authoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.wormholeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.wormholeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.sender.equals(payer)).is.true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("Instruction 12: Transfer Native With Payload", () => { const mint = NATIVE_MINT; const mintAta = getAssociatedTokenAddressSync(mint, payer); const message = web3.Keypair.generate(); const accounts = getTransferNativeWithPayloadAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, mint ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.config.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.from.equals(mintAta)).is.true; expect(accounts.custody.toString()).to.equal( "2Aczo4H847TNDsPradsVXQUZFquJ37ZoHhRmJ2MAqtiM" ); expect(accounts.authoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.custodySigner.toString()).to.equal( "Eb8xqkMEZYeTnDse4BgWiHVByeUj3JDpgbuz98pWdgPE" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.wormholeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.wormholeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.sender.equals(payer)).is.true; expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); }); describe("CPI Accounts", () => { // token bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); const payer = new web3.PublicKey( "6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J" ); // mock program integrating token bridge const cpiProgramId = new web3.PublicKey( "pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ" ); it("getCompleteTransferNativeWithPayloadCpiAccounts", () => { const mint = NATIVE_MINT; const redeemer = deriveRedeemerAccountKey(cpiProgramId); const mintAta = getAssociatedTokenAddressSync(mint, redeemer, true); const amountEncoded = 42069n; const nonce = 420; const timestamp = 23456789; const message = ethereumTokenBridge.publishTransferTokensWithPayload( mint.toBuffer().toString("hex"), 1, amountEncoded, 1, cpiProgramId.toBuffer().toString("hex"), Buffer.alloc(32, 0), Buffer.from("All your base are belong to us"), nonce, timestamp ); expect(message[51]).to.equal(3); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getCompleteTransferNativeWithPayloadCpiAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa, mintAta ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.tokenBridgeConfig.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.vaa.toString()).to.equal( "GtiCPc4mxBVsrPQVgYnuVUzhuvh24A54KaDZhcP4mhDa" ); expect(accounts.tokenBridgeClaim.toString()).to.equal( "HzjTihvhEx7BbKnB2KHATNBwGFCEm2nnMG6c4Pwx6pPE" ); expect(accounts.tokenBridgeForeignEndpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.toTokenAccount.equals(mintAta)).is.true; expect(accounts.tokenBridgeRedeemer.toString()).to.equal( "A2SNTmahH9ryK2PupNMfKibPPaMtcfYBSX4WjZchhatX" ); expect(accounts.toFeesTokenAccount.equals(mintAta)).is.true; expect(accounts.tokenBridgeCustody.toString()).to.equal( "2Aczo4H847TNDsPradsVXQUZFquJ37ZoHhRmJ2MAqtiM" ); expect(accounts.mint.equals(mint)).is.true; expect(accounts.tokenBridgeCustodySigner.toString()).to.equal( "Eb8xqkMEZYeTnDse4BgWiHVByeUj3JDpgbuz98pWdgPE" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("getCompleteTransferWrappedWithPayloadCpiAccounts", () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const redeemer = deriveRedeemerAccountKey(cpiProgramId); const mintAta = getAssociatedTokenAddressSync(mint, redeemer, true); const amount = 4206942069n; const recipientChain = 1; const nonce = 420; const timestamp = 34567890; const message = ethereumTokenBridge.publishTransferTokensWithPayload( tokenAddress.toString("hex"), tokenChain, amount, recipientChain, cpiProgramId.toBuffer().toString("hex"), Buffer.alloc(32, 0), Buffer.from("All your base are belong to us"), nonce, timestamp ); expect(message[51]).to.equal(3); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const accounts = getCompleteTransferWrappedWithPayloadCpiAccounts( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, signedVaa, mintAta ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.tokenBridgeConfig.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.vaa.toString()).to.equal( "9nFMaAfuXmE4FdJe8koZ4ScvYcJ5znoJPDgT29aVZM1x" ); expect(accounts.tokenBridgeClaim.toString()).to.equal( "7Ae57QxvZMwCrknoDWpeaMTLbMP3LBeCJee6KaLEwxP6" ); expect(accounts.tokenBridgeForeignEndpoint.toString()).to.equal( "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.toTokenAccount.equals(mintAta)).is.true; expect(accounts.tokenBridgeRedeemer.toString()).to.equal( "A2SNTmahH9ryK2PupNMfKibPPaMtcfYBSX4WjZchhatX" ); expect(accounts.toFeesTokenAccount.equals(mintAta)).is.true; expect(accounts.tokenBridgeWrappedMint.equals(mint)).is.true; expect(accounts.tokenBridgeWrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.tokenBridgeMintAuthority.toString()).to.equal( "J2mhpFfGCwHtUjmeQGhJSa2yk5h3egRoSd1AUhaKx2WG" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("getTransferWrappedWithPayloadCpiAccounts", () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, cpiProgramId, true); const message = web3.Keypair.generate(); const accounts = getTransferWrappedWithPayloadCpiAccounts( cpiProgramId, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, tokenChain, tokenAddress ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.tokenBridgeConfig.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.fromTokenAccount.equals(mintAta)).is.true; expect(accounts.fromTokenAccountOwner.equals(cpiProgramId)).is.true; expect(accounts.tokenBridgeWrappedMint.equals(mint)).is.true; expect(accounts.tokenBridgeWrappedMeta.toString()).to.equal( "AWUK8RTEBvUNAWLz1VfagK3rnvJ9oLDZPBJEBCzpjqj7" ); expect(accounts.tokenBridgeAuthoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.tokenBridgeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.tokenBridgeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.tokenBridgeSender.toString()).to.equal( "7r3GbMGbRRp3cbPRPv9v5GBktxGpDmK5LBnvjDVxsEDN" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); it("getTransferNativeWithPayloadCpiAccounts", () => { const mint = NATIVE_MINT; const mintAta = getAssociatedTokenAddressSync(mint, cpiProgramId, true); const message = web3.Keypair.generate(); const accounts = getTransferNativeWithPayloadCpiAccounts( cpiProgramId, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, payer, message.publicKey, mintAta, mint ); // verify accounts expect(accounts.payer.equals(payer)).is.true; expect(accounts.tokenBridgeConfig.toString()).to.equal( "GnQ6fGttTRnJpAJuy2XEg5TLgEMtbyU4HDJnBWmojsTv" ); expect(accounts.fromTokenAccount.equals(mintAta)).is.true; expect(accounts.tokenBridgeCustody.toString()).to.equal( "2Aczo4H847TNDsPradsVXQUZFquJ37ZoHhRmJ2MAqtiM" ); expect(accounts.tokenBridgeAuthoritySigner.toString()).to.equal( "FDYbeBnX3rZnWM1jE6vTSxjxYdeGryxZtirXmzW71FTH" ); expect(accounts.tokenBridgeCustodySigner.toString()).to.equal( "Eb8xqkMEZYeTnDse4BgWiHVByeUj3JDpgbuz98pWdgPE" ); expect(accounts.wormholeBridge.toString()).to.equal( "DNN2VhmrGTGj6QVnPz4NVfsiSk64cRHzKBLP5kUaQrf8" ); expect(accounts.tokenBridgeEmitter.toString()).to.equal( "Ard2Zy4HckbJS2bL7y4361wbKSUH68JZqYBura5d4xtw" ); expect(accounts.tokenBridgeSequence.toString()).to.equal( "Gdeob8iLpTN4Fc8BEgRdFUWikdUsvrv9Rfc1rNQWy4b7" ); expect(accounts.wormholeFeeCollector.toString()).to.equal( "Cxt3Uka7X8vyHYjU6szcuYVPPFyg1fAtoeVy7eyzPjGV" ); expect(accounts.clock.equals(web3.SYSVAR_CLOCK_PUBKEY)).is.true; expect(accounts.tokenBridgeSender.toString()).to.equal( "7r3GbMGbRRp3cbPRPv9v5GBktxGpDmK5LBnvjDVxsEDN" ); expect(accounts.rent.equals(web3.SYSVAR_RENT_PUBKEY)).is.true; expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); }); describe("Token Bridge Program Interaction", () => { // for generating governance wormhole messages const governance = new GovernanceEmitter( GOVERNANCE_EMITTER_ADDRESS.toBuffer().toString("hex"), 20 ); // token bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); describe("Setup Token Bridge", () => { it("Register Ethereum Token Bridge", async () => { const timestamp = now(); const message = governance.publishTokenBridgeRegisterChain( timestamp, 2, ETHEREUM_TOKEN_BRIDGE_ADDRESS ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const txSignatures = await postVaa( connection, wallet.signTransaction, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ).then((results) => results.map((result) => result.signature)); const postTx = txSignatures.pop()!; for (const verifyTx of txSignatures) { // console.log(`verifySignatures: ${verifyTx}`); } // console.log(`postVaa: ${postTx}`); const registerChainTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add( createRegisterChainInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ) ), [wallet.signer()] ); // console.log(`registerChainTx: ${registerChainTx}`); // verify data const parsed = parseTokenBridgeRegisterChainVaa(signedVaa); const endpoint = deriveEndpointKey( TOKEN_BRIDGE_ADDRESS, parsed.foreignChain, parsed.foreignAddress ); const endpointRegistration = await getEndpointRegistration( connection, endpoint ); expect(endpointRegistration.chain).to.equal(2); const expectedEmitter = ethAddressToBuffer( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); expect( Buffer.compare(endpointRegistration.contract, expectedEmitter) ).to.equal(0); }); }); describe("Native Token Handling", () => { it("Attest Mint Without Metadata", async () => { const mint = localVariables.mint; const message = web3.Keypair.generate(); const nonce = 69; const attestTokenTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add( createAttestTokenInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), mint, message.publicKey, nonce ) ), [wallet.signer(), message] ); // console.log(`attestTokenTx: ${attestTokenTx}`); // verify data const messageData = await getPostedMessage( connection, message.publicKey ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal(32); expect( Buffer.compare( messageData.emitterAddress, deriveWormholeEmitterKey(TOKEN_BRIDGE_ADDRESS).toBuffer() ) ).to.equal(0); expect(messageData.emitterChain).to.equal(1); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(0n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaSignatureAccount.equals(web3.PublicKey.default)) .is.true; expect(messageData.vaaVersion).to.equal(0); const assetMeta = parseAttestMetaPayload(messageData.payload); expect(assetMeta.payloadType).to.equal(2); expect( Buffer.compare(assetMeta.tokenAddress, mint.toBuffer()) ).to.equal(0); expect(assetMeta.tokenChain).to.equal(1); expect(assetMeta.decimals).to.equal(9); expect(assetMeta.symbol).to.equal(""); expect(assetMeta.name).to.equal(""); const sequenceTracker = await getProgramSequenceTracker( connection, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS ); expect(sequenceTracker.value()).to.equal(messageData.sequence + 1n); }); // it("Attest Mint With Metadata", async () => { // // TODO // }); it("Send Token", async () => { const mint = localVariables.mint; const mintAta = localVariables.mintAta; const custodyAccount = deriveCustodyKey(TOKEN_BRIDGE_ADDRESS, mint); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceBefore = 0n; const nonce = 69; const amount = BigInt(420 * web3.LAMPORTS_PER_SOL); const fee = 0n; const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); const targetChain = 2; const approveIx = createApproveAuthoritySignerInstruction( TOKEN_BRIDGE_ADDRESS, mintAta, wallet.key(), amount ); const message = web3.Keypair.generate(); const transferNativeIx = createTransferNativeInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), message.publicKey, mintAta, mint, nonce, amount, fee, targetAddress, targetChain ); const approveAndTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(approveIx, transferNativeIx), [wallet.signer(), message] ); // console.log(`approveAndTransferTx: ${approveAndTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceAfter = await getAccount( connection, custodyAccount ).then((account) => account.amount); // check balance changes expect(walletBalanceBefore - walletBalanceAfter).to.equal(amount); expect(custodyBalanceAfter - custodyBalanceBefore).to.equal(amount); // verify data const messageData = await getPostedMessage( connection, message.publicKey ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal(32); expect( Buffer.compare( messageData.emitterAddress, deriveWormholeEmitterKey(TOKEN_BRIDGE_ADDRESS).toBuffer() ) ).to.equal(0); expect(messageData.emitterChain).to.equal(1); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(1n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaSignatureAccount.equals(web3.PublicKey.default)) .is.true; expect(messageData.vaaVersion).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(1); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).greaterThan(8); // decimals will be 8 on Ethereum token bridge const amountEncoded = amount / BigInt(Math.pow(10, mintInfo.decimals - 8)); expect(tokenTransfer.amount).to.equal(amountEncoded); expect(tokenTransfer.fee).is.not.null; expect(tokenTransfer.fee).to.equal(fee); expect(tokenTransfer.fromAddress).is.null; expect(Buffer.compare(tokenTransfer.to, targetAddress)).to.equal(0); expect(tokenTransfer.toChain).to.equal(targetChain); expect( Buffer.compare(tokenTransfer.tokenAddress, mint.toBuffer()) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(1); const sequenceTracker = await getProgramSequenceTracker( connection, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS ); expect(sequenceTracker.value()).to.equal(messageData.sequence + 1n); }); it("Receive Token", async () => { const mint = localVariables.mint; const mintAta = localVariables.mintAta; const custodyAccount = deriveCustodyKey(TOKEN_BRIDGE_ADDRESS, mint); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceBefore = await getAccount( connection, custodyAccount ).then((account) => account.amount); const amount = 420n * BigInt(web3.LAMPORTS_PER_SOL); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).greaterThan(8); // decimals will be 8 on Ethereum token bridge const amountEncoded = amount / BigInt(Math.pow(10, mintInfo.decimals - 8)); const tokenChain = 1; const recipientChain = 1; const fee = 0n; const nonce = 420; const message = ethereumTokenBridge.publishTransferTokens( mint.toBuffer().toString("hex"), tokenChain, amountEncoded, recipientChain, mintAta.toBuffer().toString("hex"), fee, nonce ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const txSignatures = await postVaa( connection, wallet.signTransaction, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ).then((results) => results.map((result) => result.signature)); const postTx = txSignatures.pop()!; for (const verifyTx of txSignatures) { // console.log(`verifySignatures: ${verifyTx}`); } // console.log(`postVaa: ${postTx}`); const completeNativeTransferIx = createCompleteTransferNativeInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ); const completeNativeTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(completeNativeTransferIx), [wallet.signer()] ); // console.log(`completeNativeTransferTx: ${completeNativeTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceAfter = await getAccount( connection, custodyAccount ).then((account) => account.amount); // check balance changes expect(walletBalanceAfter - walletBalanceBefore).to.equal(amount); expect(custodyBalanceBefore - custodyBalanceAfter).to.equal(amount); // verify data const messageData = await getPostedVaa( connection, CORE_BRIDGE_ADDRESS, parseVaa(signedVaa).hash ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal( ethereumTokenBridge.consistencyLevel ); expect( Buffer.compare( messageData.emitterAddress, ethAddressToBuffer(ETHEREUM_TOKEN_BRIDGE_ADDRESS) ) ).to.equal(0); expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(1n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaVersion).to.equal(1); expect( Buffer.compare(parseVaa(signedVaa).payload, messageData.payload) ).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(1); expect(tokenTransfer.amount).to.equal(amountEncoded); expect(tokenTransfer.fee).is.not.null; expect(tokenTransfer.fee).to.equal(fee); expect(tokenTransfer.fromAddress).is.null; expect(Buffer.compare(tokenTransfer.to, mintAta.toBuffer())).to.equal( 0 ); expect(tokenTransfer.toChain).to.equal(recipientChain); expect( Buffer.compare(tokenTransfer.tokenAddress, mint.toBuffer()) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(tokenChain); }); it("Send Token With Payload", async () => { const mint = localVariables.mint; const mintAta = localVariables.mintAta; const custodyAccount = deriveCustodyKey(TOKEN_BRIDGE_ADDRESS, mint); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceBefore = await getAccount( connection, custodyAccount ).then((account) => account.amount); const nonce = 420; const amount = BigInt(69 * web3.LAMPORTS_PER_SOL); const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); const targetChain = 2; const approveIx = createApproveAuthoritySignerInstruction( TOKEN_BRIDGE_ADDRESS, mintAta, wallet.key(), amount ); const message = web3.Keypair.generate(); const transferPayload = Buffer.from("All your base are belong to us"); const transferNativeIx = createTransferNativeWithPayloadInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), message.publicKey, mintAta, mint, nonce, amount, targetAddress, targetChain, transferPayload ); const approveAndTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(approveIx, transferNativeIx), [wallet.signer(), message] ); // console.log(`approveAndTransferTx: ${approveAndTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const custodyBalanceAfter = await getAccount( connection, custodyAccount ).then((account) => account.amount); // check balance changes expect(walletBalanceBefore - walletBalanceAfter).to.equal(amount); expect(custodyBalanceAfter - custodyBalanceBefore).to.equal(amount); // verify data const messageData = await getPostedMessage( connection, message.publicKey ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal(32); expect( Buffer.compare( messageData.emitterAddress, deriveWormholeEmitterKey(TOKEN_BRIDGE_ADDRESS).toBuffer() ) ).to.equal(0); expect(messageData.emitterChain).to.equal(1); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(2n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaSignatureAccount.equals(web3.PublicKey.default)) .is.true; expect(messageData.vaaVersion).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(3); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).greaterThan(8); // decimals will be 8 on Ethereum token bridge const amountEncoded = amount / BigInt(Math.pow(10, mintInfo.decimals - 8)); expect(tokenTransfer.amount).to.equal(amountEncoded); expect(tokenTransfer.fee).is.null; expect(tokenTransfer.fromAddress).is.not.null; expect( new web3.PublicKey(tokenTransfer.fromAddress!).equals(wallet.key()) ).is.true; expect(Buffer.compare(tokenTransfer.to, targetAddress)).to.equal(0); expect(tokenTransfer.toChain).to.equal(targetChain); expect( Buffer.compare(tokenTransfer.tokenAddress, mint.toBuffer()) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(1); expect( Buffer.compare(tokenTransfer.tokenTransferPayload, transferPayload) ).to.equal(0); const sequenceTracker = await getProgramSequenceTracker( connection, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS ); expect(sequenceTracker.value()).to.equal(messageData.sequence + 1n); }); }); describe("Token Bridge Wrapped Token Handling", () => { it("Create Wrapped with Metadata", async () => { const tokenAddress = WETH_ADDRESS; const decimals = 18; const symbol = "WETH"; const name = "Wrapped ETH"; const nonce = 420; const message = ethereumTokenBridge.publishAttestMeta( tokenAddress, decimals, symbol, name, nonce ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const txSignatures = await postVaa( connection, wallet.signTransaction, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ).then((results) => results.map((result) => result.signature)); const postTx = txSignatures.pop()!; for (const verifyTx of txSignatures) { // console.log(`verifySignatures: ${verifyTx}`); } // console.log(`postVaa: ${postTx}`); const createWrappedIx = createCreateWrappedInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ); const createWrappedTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(createWrappedIx), [wallet.signer()] ); // console.log(`createWrappedTx: ${createWrappedTx}`); // verify data const parsed = parseAttestMetaVaa(signedVaa); const messageData = await getPostedVaa( connection, CORE_BRIDGE_ADDRESS, parsed.hash ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal( ethereumTokenBridge.consistencyLevel ); const expectedEmitter = ethAddressToBuffer( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); expect( Buffer.compare(messageData.emitterAddress, expectedEmitter) ).to.equal(0); expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(2n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaVersion).to.equal(1); expect(Buffer.compare(parsed.payload, messageData.payload)).to.equal(0); const assetMeta = parseAttestMetaPayload(messageData.payload); expect(assetMeta.payloadType).to.equal(2); const expectedTokenAddress = ethAddressToBuffer(tokenAddress); expect( Buffer.compare(assetMeta.tokenAddress, expectedTokenAddress) ).to.equal(0); expect(assetMeta.tokenChain).to.equal(ethereumTokenBridge.chain); expect(assetMeta.decimals).to.equal(decimals); expect(assetMeta.symbol).to.equal(symbol); expect(assetMeta.name).to.equal(name); // check wrapped mint const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, assetMeta.tokenChain, assetMeta.tokenAddress ); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).to.equal(8); expect(mintInfo.mintAuthority).is.not.null; expect( mintInfo.mintAuthority?.equals( deriveMintAuthorityKey(TOKEN_BRIDGE_ADDRESS) ) ).is.true; expect(mintInfo.supply).to.equal(0n); // check wrapped meta const wrappedMeta = await getWrappedMeta( connection, TOKEN_BRIDGE_ADDRESS, mint ); expect(wrappedMeta.chain).to.equal(ethereumTokenBridge.chain); expect( Buffer.compare(wrappedMeta.tokenAddress, expectedTokenAddress) ).to.equal(0); expect(wrappedMeta.originalDecimals).to.equal(decimals); }); it("Receive Token", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = await getOrCreateAssociatedTokenAccount( connection, wallet.signer(), mint, wallet.key() ).then((account) => account.address); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyBefore = await getMint(connection, mint).then( (info) => info.supply ); const amount = 2n * 4206942069n; const recipientChain = 1; const fee = 0n; const nonce = 420; const message = ethereumTokenBridge.publishTransferTokens( tokenAddress.toString("hex"), tokenChain, amount, recipientChain, mintAta.toBuffer().toString("hex"), fee, nonce ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const txSignatures = await postVaa( connection, wallet.signTransaction, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ).then((results) => results.map((result) => result.signature)); const postTx = txSignatures.pop()!; for (const verifyTx of txSignatures) { // console.log(`verifySignatures: ${verifyTx}`); } // console.log(`postVaa: ${postTx}`); const completeTransferWrappedIx = createCompleteTransferWrappedInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ); const completeWrappedTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(completeTransferWrappedIx), [wallet.signer()] ); // console.log(`completeWrappedTransferTx: ${completeWrappedTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyAfter = await getMint(connection, mint).then( (info) => info.supply ); // check balance and supply changes expect(walletBalanceAfter - walletBalanceBefore).to.equal(amount); expect(supplyAfter - supplyBefore).to.equal(amount); // verify data const parsed = parseVaa(signedVaa); const messageData = await getPostedVaa( connection, CORE_BRIDGE_ADDRESS, parsed.hash ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal( ethereumTokenBridge.consistencyLevel ); expect( Buffer.compare( messageData.emitterAddress, ethAddressToBuffer(ETHEREUM_TOKEN_BRIDGE_ADDRESS) ) ).to.equal(0); expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(3n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaVersion).to.equal(1); expect( Buffer.compare(parseVaa(signedVaa).payload, messageData.payload) ).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(1); expect(tokenTransfer.amount).to.equal(amount); expect(tokenTransfer.fee).is.not.null; expect(tokenTransfer.fee).to.equal(fee); expect(tokenTransfer.fromAddress).is.null; expect(Buffer.compare(tokenTransfer.to, mintAta.toBuffer())).to.equal( 0 ); expect(tokenTransfer.toChain).to.equal(recipientChain); expect( Buffer.compare(tokenTransfer.tokenAddress, tokenAddress) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(tokenChain); }); it("Send Token", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, wallet.key()); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyBefore = await getMint(connection, mint).then( (info) => info.supply ); const nonce = 69; const amount = 4206942069n; const fee = 0n; const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); const targetChain = 2; const approveIx = createApproveAuthoritySignerInstruction( TOKEN_BRIDGE_ADDRESS, mintAta, wallet.key(), amount ); const message = web3.Keypair.generate(); const transferNativeIx = createTransferWrappedInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), message.publicKey, mintAta, wallet.key(), tokenChain, tokenAddress, nonce, amount, fee, targetAddress, targetChain ); const approveAndTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(approveIx, transferNativeIx), [wallet.signer(), message] ); // console.log(`approveAndTransferTx: ${approveAndTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyAfter = await getMint(connection, mint).then( (info) => info.supply ); // check balance changes expect(walletBalanceBefore - walletBalanceAfter).to.equal(amount); expect(supplyBefore - supplyAfter).to.equal(amount); // verify data const messageData = await getPostedMessage( connection, message.publicKey ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal(32); expect( Buffer.compare( messageData.emitterAddress, deriveWormholeEmitterKey(TOKEN_BRIDGE_ADDRESS).toBuffer() ) ).to.equal(0); expect(messageData.emitterChain).to.equal(1); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(3n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaSignatureAccount.equals(web3.PublicKey.default)) .is.true; expect(messageData.vaaVersion).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(1); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).to.equal(8); expect(tokenTransfer.amount).to.equal(amount); expect(tokenTransfer.fee).is.not.null; expect(tokenTransfer.fee).to.equal(fee); expect(tokenTransfer.fromAddress).is.null; expect(Buffer.compare(tokenTransfer.to, targetAddress)).to.equal(0); expect(tokenTransfer.toChain).to.equal(targetChain); expect( Buffer.compare(tokenTransfer.tokenAddress, tokenAddress) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(tokenChain); const sequenceTracker = await getProgramSequenceTracker( connection, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS ); expect(sequenceTracker.value()).to.equal(messageData.sequence + 1n); }); it("Send Token With Payload", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const mintAta = getAssociatedTokenAddressSync(mint, wallet.key()); const walletBalanceBefore = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyBefore = await getMint(connection, mint).then( (info) => info.supply ); const nonce = 69; const amount = 4206942069n; const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); const targetChain = 2; const approveIx = createApproveAuthoritySignerInstruction( TOKEN_BRIDGE_ADDRESS, mintAta, wallet.key(), amount ); const message = web3.Keypair.generate(); const transferPayload = Buffer.from("All your base are belong to us"); const transferNativeIx = createTransferWrappedWithPayloadInstruction( TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS, wallet.key(), message.publicKey, mintAta, wallet.key(), tokenChain, tokenAddress, nonce, amount, targetAddress, targetChain, transferPayload ); const approveAndTransferTx = await web3.sendAndConfirmTransaction( connection, new web3.Transaction().add(approveIx, transferNativeIx), [wallet.signer(), message] ); // console.log(`approveAndTransferTx: ${approveAndTransferTx}`); const walletBalanceAfter = await getAccount(connection, mintAta).then( (account) => account.amount ); const supplyAfter = await getMint(connection, mint).then( (info) => info.supply ); // check balance changes expect(walletBalanceBefore - walletBalanceAfter).to.equal(amount); expect(supplyBefore - supplyAfter).to.equal(amount); // verify data const messageData = await getPostedMessage( connection, message.publicKey ).then((posted) => posted.message); expect(messageData.consistencyLevel).to.equal(32); expect( Buffer.compare( messageData.emitterAddress, deriveWormholeEmitterKey(TOKEN_BRIDGE_ADDRESS).toBuffer() ) ).to.equal(0); expect(messageData.emitterChain).to.equal(1); expect(messageData.nonce).to.equal(nonce); expect(messageData.sequence).to.equal(4n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaSignatureAccount.equals(web3.PublicKey.default)) .is.true; expect(messageData.vaaVersion).to.equal(0); const tokenTransfer = parseTokenTransferPayload(messageData.payload); expect(tokenTransfer.payloadType).to.equal(3); const mintInfo = await getMint(connection, mint); expect(mintInfo.decimals).to.equal(8); expect(tokenTransfer.amount).to.equal(amount); expect(Buffer.compare(tokenTransfer.to, targetAddress)).to.equal(0); expect(tokenTransfer.toChain).to.equal(targetChain); expect( Buffer.compare(tokenTransfer.tokenAddress, tokenAddress) ).to.equal(0); expect(tokenTransfer.tokenChain).to.equal(tokenChain); expect( Buffer.compare(tokenTransfer.tokenTransferPayload, transferPayload) ).to.equal(0); const sequenceTracker = await getProgramSequenceTracker( connection, TOKEN_BRIDGE_ADDRESS, CORE_BRIDGE_ADDRESS ); expect(sequenceTracker.value()).to.equal(messageData.sequence + 1n); }); }); }); describe("SDK Methods", () => { // nft bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS, 3 // startSequence ); describe("getOriginalAssetSolana", () => { it("Non-existent Token", async () => { const mint = "wot m8?"; const asset = await getOriginalAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, mint ); // verify results expect(asset.isWrapped).to.be.false; expect(asset.chainId).to.equal(1); expect( Buffer.compare(Buffer.from(asset.assetAddress), Buffer.alloc(32)) ).to.equal(0); }); it("Native Token", async () => { const mint = localVariables.mint; const asset = await getOriginalAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, mint ); // verify results expect(asset.isWrapped).to.be.false; expect(asset.chainId).to.equal(1); expect( Buffer.compare(Buffer.from(asset.assetAddress), mint.toBuffer()) ).to.equal(0); }); it("Wrapped Token", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const asset = await getOriginalAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, mint ); // verify results expect(asset.isWrapped).is.true; expect(asset.chainId).to.equal(tokenChain); expect( Buffer.compare(Buffer.from(asset.assetAddress), tokenAddress) ).to.equal(0); }); }); describe("getForeignAssetSolana", () => { it("Wrapped Token", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const asset = await getForeignAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, tokenChain as ChainId, tokenAddress ); // verify results expect(asset).to.equal("3tUXFuBNWzZZ8p2xNx5UoWCH664M2KHdDAWrdZAD1VQ3"); }); }); describe("getIsWrappedAsset", () => { it("Non-existent Token", async () => { const mint = null; const isWrapped = await getIsWrappedAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, // @ts-ignore: mint is null mint ); // verify results expect(isWrapped).to.be.false; }); it("Native Token", async () => { const mint = localVariables.mint; const isWrapped = await getIsWrappedAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, mint ); // verify results expect(isWrapped).to.be.false; }); it("Wrapped Token", async () => { const tokenAddress = ethAddressToBuffer(WETH_ADDRESS); const tokenChain = ethereumTokenBridge.chain; const mint = deriveWrappedMintKey( TOKEN_BRIDGE_ADDRESS, tokenChain, tokenAddress ); const isWrapped = await getIsWrappedAssetSolana( connection, TOKEN_BRIDGE_ADDRESS, mint ); // verify results expect(isWrapped).is.true; }); }); describe("transferNativeSol", () => { it("Send SOL To Ethereum", async () => { const balanceBefore = await connection .getBalance(wallet.key()) .then((num) => BigInt(num)); const amount = 6969696969n; const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); const transferNativeSolTx = await transferNativeSol( connection, CORE_BRIDGE_ADDRESS, TOKEN_BRIDGE_ADDRESS, wallet.key(), amount, targetAddress, "ethereum" ) .then((transaction) => signSendAndConfirmTransaction( connection, wallet.key(), wallet.signTransaction, transaction ) ) .then((response) => response.signature); //console.log(`transferNativeSolTx: ${transferNativeSolTx}`); const balanceAfter = await connection .getBalance(wallet.key()) .then((num) => BigInt(num)); const transactionCost = 4601551n; expect(balanceBefore - balanceAfter - transactionCost).to.equal(amount); }); }); describe("redeemAndUnwrapOnSolana", () => { it("Receive SOL From Ethereum", async () => { const balanceBefore = await connection .getBalance(wallet.key()) .then((num) => BigInt(num)); const tokenChain = 1; const mintAta = await getOrCreateAssociatedTokenAccount( connection, wallet.signer(), NATIVE_MINT, wallet.key() ).then((account) => account.address); const amount = 42042042n; const recipientChain = 1; const fee = 0n; const nonce = 420; const message = ethereumTokenBridge.publishTransferTokens( NATIVE_MINT.toBuffer().toString("hex"), tokenChain, amount, recipientChain, mintAta.toBuffer().toString("hex"), fee, nonce ); const signedVaa = guardians.addSignatures( message, [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] ); const txSignatures = await postVaa( connection, wallet.signTransaction, CORE_BRIDGE_ADDRESS, wallet.key(), signedVaa ).then((results) => results.map((result) => result.signature)); const postTx = txSignatures.pop()!; for (const verifyTx of txSignatures) { // console.log(`verifySignatures: ${verifyTx}`); } // console.log(`postVaa: ${postTx}`); const transferNativeSolTx = await redeemAndUnwrapOnSolana( connection, CORE_BRIDGE_ADDRESS, TOKEN_BRIDGE_ADDRESS, wallet.key(), signedVaa ) .then((transaction) => signSendAndConfirmTransaction( connection, wallet.key(), wallet.signTransaction, transaction ) ) .then((response) => response.signature); //console.log(`transferNativeSolTx: ${transferNativeSolTx}`); const balanceAfter = await connection .getBalance(wallet.key()) .then((num) => BigInt(num)); const transactionCost = 6821400n; expect(balanceAfter - (balanceBefore - transactionCost)).to.equal( amount * 10n ); }); }); }); });