|
@@ -1,2901 +0,0 @@
|
|
|
-import { formatUnits, parseUnits } from "@ethersproject/units";
|
|
|
|
|
-import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
|
|
|
|
-import { describe, expect, jest, test } from "@jest/globals";
|
|
|
|
|
-import {
|
|
|
|
|
- ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
|
|
|
- Token,
|
|
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
|
|
-} from "@solana/spl-token";
|
|
|
|
|
-import {
|
|
|
|
|
- Connection,
|
|
|
|
|
- Keypair,
|
|
|
|
|
- PublicKey,
|
|
|
|
|
- TokenAccountsFilter,
|
|
|
|
|
- Transaction,
|
|
|
|
|
-} from "@solana/web3.js";
|
|
|
|
|
-import {
|
|
|
|
|
- LCDClient,
|
|
|
|
|
- MnemonicKey,
|
|
|
|
|
- MsgExecuteContract,
|
|
|
|
|
-} from "@terra-money/terra.js";
|
|
|
|
|
-import algosdk, {
|
|
|
|
|
- Account,
|
|
|
|
|
- decodeAddress,
|
|
|
|
|
- getApplicationAddress,
|
|
|
|
|
- makeApplicationCallTxnFromObject,
|
|
|
|
|
- OnApplicationComplete,
|
|
|
|
|
- waitForConfirmation,
|
|
|
|
|
-} from "algosdk";
|
|
|
|
|
-import axios from "axios";
|
|
|
|
|
-import { BigNumber, ethers, utils } from "ethers";
|
|
|
|
|
-import {
|
|
|
|
|
- approveEth,
|
|
|
|
|
- attestFromAlgorand,
|
|
|
|
|
- attestFromEth,
|
|
|
|
|
- attestFromSolana,
|
|
|
|
|
- attestFromTerra,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- createWrappedOnAlgorand,
|
|
|
|
|
- createWrappedOnEth,
|
|
|
|
|
- createWrappedOnSolana,
|
|
|
|
|
- createWrappedOnTerra,
|
|
|
|
|
- getEmitterAddressAlgorand,
|
|
|
|
|
- getEmitterAddressEth,
|
|
|
|
|
- getEmitterAddressSolana,
|
|
|
|
|
- getEmitterAddressTerra,
|
|
|
|
|
- getForeignAssetEth,
|
|
|
|
|
- getForeignAssetSolana,
|
|
|
|
|
- getForeignAssetTerra,
|
|
|
|
|
- getIsTransferCompletedAlgorand,
|
|
|
|
|
- getIsTransferCompletedEth,
|
|
|
|
|
- getIsTransferCompletedSolana,
|
|
|
|
|
- getIsTransferCompletedTerra,
|
|
|
|
|
- getOriginalAssetAlgorand,
|
|
|
|
|
- hexToUint8Array,
|
|
|
|
|
- nativeToHexString,
|
|
|
|
|
- parseSequenceFromLogAlgorand,
|
|
|
|
|
- parseSequenceFromLogEth,
|
|
|
|
|
- parseSequenceFromLogSolana,
|
|
|
|
|
- parseSequenceFromLogTerra,
|
|
|
|
|
- postVaaSolana,
|
|
|
|
|
- redeemOnAlgorand,
|
|
|
|
|
- redeemOnEth,
|
|
|
|
|
- redeemOnSolana,
|
|
|
|
|
- redeemOnTerra,
|
|
|
|
|
- textToUint8Array,
|
|
|
|
|
- TokenImplementation__factory,
|
|
|
|
|
- transferFromAlgorand,
|
|
|
|
|
- transferFromEth,
|
|
|
|
|
- transferFromSolana,
|
|
|
|
|
- transferFromTerra,
|
|
|
|
|
- tryNativeToHexString,
|
|
|
|
|
- tryNativeToUint8Array,
|
|
|
|
|
- uint8ArrayToHex,
|
|
|
|
|
- updateWrappedOnEth,
|
|
|
|
|
- WormholeWrappedInfo,
|
|
|
|
|
-} from "../..";
|
|
|
|
|
-import { _parseVAAAlgorand } from "../../algorand";
|
|
|
|
|
-import {
|
|
|
|
|
- createAsset,
|
|
|
|
|
- getAlgoClient,
|
|
|
|
|
- getBalance,
|
|
|
|
|
- getBalances,
|
|
|
|
|
- getForeignAssetFromVaaAlgorand,
|
|
|
|
|
- getTempAccounts,
|
|
|
|
|
- signSendAndConfirmAlgorand,
|
|
|
|
|
-} from "../../algorand/__tests__/testHelpers";
|
|
|
|
|
-import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
|
|
|
|
-import { postVaaWithRetry } from "../../solana/postVaa";
|
|
|
|
|
-import { setDefaultWasm } from "../../solana/wasm";
|
|
|
|
|
-import { safeBigIntToNumber } from "../../utils/bigint";
|
|
|
|
|
-import {
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- ETH_NODE_URL,
|
|
|
|
|
- ETH_PRIVATE_KEY,
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_HOST,
|
|
|
|
|
- SOLANA_PRIVATE_KEY,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TERRA_CHAIN_ID,
|
|
|
|
|
- TERRA_GAS_PRICES_URL,
|
|
|
|
|
- TERRA_NODE_URL,
|
|
|
|
|
- TERRA_PRIVATE_KEY,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- TEST_SOLANA_TOKEN,
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
-} from "./consts";
|
|
|
|
|
-import {
|
|
|
|
|
- getSignedVAABySequence,
|
|
|
|
|
- getTerraGasPrices,
|
|
|
|
|
- queryBalanceOnTerra,
|
|
|
|
|
- transferFromEthToSolana,
|
|
|
|
|
- waitForTerraExecution,
|
|
|
|
|
-} from "./helpers";
|
|
|
|
|
-
|
|
|
|
|
-const CORE_ID = BigInt(4);
|
|
|
|
|
-const TOKEN_BRIDGE_ID = BigInt(6);
|
|
|
|
|
-
|
|
|
|
|
-setDefaultWasm("node");
|
|
|
|
|
-
|
|
|
|
|
-jest.setTimeout(60000);
|
|
|
|
|
-
|
|
|
|
|
-// TODO: setup keypair and provider/signer before, destroy provider after
|
|
|
|
|
-// TODO: make the repeatable (can't attest an already attested token)
|
|
|
|
|
-
|
|
|
|
|
-describe("Integration Tests", () => {
|
|
|
|
|
- describe("Ethereum to Solana", () => {
|
|
|
|
|
- test("Attest Ethereum ERC-20 to Solana", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- // attest the test token
|
|
|
|
|
- const receipt = await attestFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- // post vaa to Solana
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
- await postVaaSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- async (transaction) => {
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- return transaction;
|
|
|
|
|
- },
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- Buffer.from(signedVAA)
|
|
|
|
|
- );
|
|
|
|
|
- // create wormhole wrapped token (mint and metadata) on solana
|
|
|
|
|
- const transaction = await createWrappedOnSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- try {
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- // this could fail because the token is already attested (in an unclean env)
|
|
|
|
|
- }
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to attest from Ethereum to Solana"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- // TODO: it is attested
|
|
|
|
|
- test("Send Ethereum ERC-20 to Solana", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const DECIMALS: number = 18;
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- // determine destination address - an associated token account
|
|
|
|
|
- const SolanaForeignAsset = await getForeignAssetSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- tryNativeToUint8Array(TEST_ERC20, CHAIN_ID_ETH)
|
|
|
|
|
- );
|
|
|
|
|
- const solanaMintKey = new PublicKey(SolanaForeignAsset || "");
|
|
|
|
|
- const recipient = await Token.getAssociatedTokenAddress(
|
|
|
|
|
- ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
|
|
- solanaMintKey,
|
|
|
|
|
- keypair.publicKey
|
|
|
|
|
- );
|
|
|
|
|
- // create the associated token account if it doesn't exist
|
|
|
|
|
- const associatedAddressInfo = await connection.getAccountInfo(
|
|
|
|
|
- recipient
|
|
|
|
|
- );
|
|
|
|
|
- if (!associatedAddressInfo) {
|
|
|
|
|
- const transaction = new Transaction().add(
|
|
|
|
|
- await Token.createAssociatedTokenAccountInstruction(
|
|
|
|
|
- ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
|
|
- solanaMintKey,
|
|
|
|
|
- recipient,
|
|
|
|
|
- keypair.publicKey, // owner
|
|
|
|
|
- keypair.publicKey // payer
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- const { blockhash } = await connection.getRecentBlockhash();
|
|
|
|
|
- transaction.recentBlockhash = blockhash;
|
|
|
|
|
- transaction.feePayer = keypair.publicKey;
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- }
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- const amount = parseUnits("1", DECIMALS);
|
|
|
|
|
-
|
|
|
|
|
- // Get the initial wallet balance of ERC20 on Eth
|
|
|
|
|
- let token = TokenImplementation__factory.connect(TEST_ERC20, signer);
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const initialErc20BalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const initialErc20BalOnEthFormatted = formatUnits(
|
|
|
|
|
- initialErc20BalOnEth._hex,
|
|
|
|
|
- DECIMALS
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get the initial balance on Solana
|
|
|
|
|
- const tokenFilter: TokenAccountsFilter = {
|
|
|
|
|
- programId: TOKEN_PROGRAM_ID,
|
|
|
|
|
- };
|
|
|
|
|
- let results = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
- keypair.publicKey,
|
|
|
|
|
- tokenFilter
|
|
|
|
|
- );
|
|
|
|
|
- let initialSolanaBalance: number = 0;
|
|
|
|
|
- for (const item of results.value) {
|
|
|
|
|
- const tokenInfo = item.account.data.parsed.info;
|
|
|
|
|
- const address = tokenInfo.mint;
|
|
|
|
|
- const amount = tokenInfo.tokenAmount.uiAmount;
|
|
|
|
|
- if (tokenInfo.mint === SolanaForeignAsset) {
|
|
|
|
|
- initialSolanaBalance = amount;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- signer,
|
|
|
|
|
- amount
|
|
|
|
|
- );
|
|
|
|
|
- // transfer tokens
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- amount,
|
|
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
|
|
- tryNativeToUint8Array(recipient.toString(), CHAIN_ID_SOLANA)
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- // post vaa to Solana
|
|
|
|
|
- await postVaaSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- async (transaction) => {
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- return transaction;
|
|
|
|
|
- },
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- Buffer.from(signedVAA)
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedSolana(
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- connection
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(false);
|
|
|
|
|
- // redeem tokens on solana
|
|
|
|
|
- const transaction = await redeemOnSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedSolana(
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- connection
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Get the final wallet balance of ERC20 on Eth
|
|
|
|
|
- const finalErc20BalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const finalErc20BalOnEthFormatted = formatUnits(
|
|
|
|
|
- finalErc20BalOnEth._hex,
|
|
|
|
|
- DECIMALS
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- parseInt(initialErc20BalOnEthFormatted) -
|
|
|
|
|
- parseInt(finalErc20BalOnEthFormatted) ===
|
|
|
|
|
- 1
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance on Solana
|
|
|
|
|
- results = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
- keypair.publicKey,
|
|
|
|
|
- tokenFilter
|
|
|
|
|
- );
|
|
|
|
|
- let finalSolanaBalance: number = 0;
|
|
|
|
|
- for (const item of results.value) {
|
|
|
|
|
- const tokenInfo = item.account.data.parsed.info;
|
|
|
|
|
- const address = tokenInfo.mint;
|
|
|
|
|
- const amount = tokenInfo.tokenAmount.uiAmount;
|
|
|
|
|
- if (tokenInfo.mint === SolanaForeignAsset) {
|
|
|
|
|
- finalSolanaBalance = amount;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- expect(finalSolanaBalance - initialSolanaBalance === 1).toBe(true);
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to send from Ethereum to Solana"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Solana to Ethereum", () => {
|
|
|
|
|
- test("Attest Solana SPL to Ethereum", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- // attest the test token
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
- const transaction = await attestFromSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- TEST_SOLANA_TOKEN
|
|
|
|
|
- );
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- const info = await connection.getTransaction(txid);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error(
|
|
|
|
|
- "An error occurred while fetching the transaction info"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogSolana(info);
|
|
|
|
|
- const emitterAddress = await getEmitterAddressSolana(
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- try {
|
|
|
|
|
- await createWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- // this could fail because the token is already attested (in an unclean env)
|
|
|
|
|
- }
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to attest from Solana to Ethereum"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- // TODO: it is attested
|
|
|
|
|
- test("Send Solana SPL to Ethereum", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- const targetAddress = await signer.getAddress();
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- // find the associated token account
|
|
|
|
|
- const fromAddress = (
|
|
|
|
|
- await Token.getAssociatedTokenAddress(
|
|
|
|
|
- ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
|
|
- new PublicKey(TEST_SOLANA_TOKEN),
|
|
|
|
|
- keypair.publicKey
|
|
|
|
|
- )
|
|
|
|
|
- ).toString();
|
|
|
|
|
-
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
-
|
|
|
|
|
- // Get the initial solana token balance
|
|
|
|
|
- const tokenFilter: TokenAccountsFilter = {
|
|
|
|
|
- programId: TOKEN_PROGRAM_ID,
|
|
|
|
|
- };
|
|
|
|
|
- let results = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
- keypair.publicKey,
|
|
|
|
|
- tokenFilter
|
|
|
|
|
- );
|
|
|
|
|
- let initialSolanaBalance: number = 0;
|
|
|
|
|
- for (const item of results.value) {
|
|
|
|
|
- const tokenInfo = item.account.data.parsed.info;
|
|
|
|
|
- const address = tokenInfo.mint;
|
|
|
|
|
- const amount = tokenInfo.tokenAmount.uiAmount;
|
|
|
|
|
- if (tokenInfo.mint === TEST_SOLANA_TOKEN) {
|
|
|
|
|
- initialSolanaBalance = amount;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Get the initial wallet balance on Eth
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const originAssetHex = tryNativeToHexString(
|
|
|
|
|
- TEST_SOLANA_TOKEN,
|
|
|
|
|
- CHAIN_ID_SOLANA
|
|
|
|
|
- );
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
- const initialBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const initialBalOnEthFormatted = formatUnits(initialBalOnEth._hex, 9);
|
|
|
|
|
-
|
|
|
|
|
- // transfer the test token
|
|
|
|
|
- const amount = parseUnits("1", 9).toBigInt();
|
|
|
|
|
- const transaction = await transferFromSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- fromAddress,
|
|
|
|
|
- TEST_SOLANA_TOKEN,
|
|
|
|
|
- amount,
|
|
|
|
|
- tryNativeToUint8Array(targetAddress, CHAIN_ID_ETH),
|
|
|
|
|
- CHAIN_ID_ETH
|
|
|
|
|
- );
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- const info = await connection.getTransaction(txid);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error(
|
|
|
|
|
- "An error occurred while fetching the transaction info"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogSolana(info);
|
|
|
|
|
- const emitterAddress = await getEmitterAddressSolana(
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(false);
|
|
|
|
|
- await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance on Solana
|
|
|
|
|
- results = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
- keypair.publicKey,
|
|
|
|
|
- tokenFilter
|
|
|
|
|
- );
|
|
|
|
|
- let finalSolanaBalance: number = 0;
|
|
|
|
|
- for (const item of results.value) {
|
|
|
|
|
- const tokenInfo = item.account.data.parsed.info;
|
|
|
|
|
- const address = tokenInfo.mint;
|
|
|
|
|
- const amount = tokenInfo.tokenAmount.uiAmount;
|
|
|
|
|
- if (tokenInfo.mint === TEST_SOLANA_TOKEN) {
|
|
|
|
|
- finalSolanaBalance = amount;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- expect(initialSolanaBalance - finalSolanaBalance).toBeCloseTo(1);
|
|
|
|
|
-
|
|
|
|
|
- // Get the final balance on Eth
|
|
|
|
|
- const finalBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const finalBalOnEthFormatted = formatUnits(finalBalOnEth._hex, 9);
|
|
|
|
|
- expect(
|
|
|
|
|
- parseInt(finalBalOnEthFormatted) -
|
|
|
|
|
- parseInt(initialBalOnEthFormatted) ===
|
|
|
|
|
- 1
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to send from Solana to Ethereum"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Ethereum to Terra", () => {
|
|
|
|
|
- test("Attest Ethereum ERC-20 to Terra", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- // attest the test token
|
|
|
|
|
- const receipt = await attestFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- const msg = await createWrappedOnTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "test",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- try {
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- // this could fail because the token is already attested (in an unclean env)
|
|
|
|
|
- }
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to attest from Ethereum to Terra"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- // TODO: it is attested
|
|
|
|
|
- test("Send Ethereum ERC-20 to Terra", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- const amount = parseUnits("1", 18);
|
|
|
|
|
- const ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A";
|
|
|
|
|
- const TerraWalletAddress: string =
|
|
|
|
|
- "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";
|
|
|
|
|
- interface Erc20Balance {
|
|
|
|
|
- balance: string;
|
|
|
|
|
- }
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // Get initial wallet balances
|
|
|
|
|
- let token = TokenImplementation__factory.connect(ERC20, signer);
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const initialBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- let initialBalOnEthStr = ethers.utils.formatUnits(
|
|
|
|
|
- initialBalOnEth,
|
|
|
|
|
- 18
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of ERC20 on Terra
|
|
|
|
|
- const originAssetHex = tryNativeToHexString(ERC20, CHAIN_ID_ETH);
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- const foreignAsset = await getForeignAssetTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- lcd,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- const tokenDefinition: any = await lcd.wasm.contractQuery(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- {
|
|
|
|
|
- token_info: {},
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- let cw20BalOnTerra: Erc20Balance = await lcd.wasm.contractQuery(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- {
|
|
|
|
|
- balance: {
|
|
|
|
|
- address: TerraWalletAddress,
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- let balAmount = ethers.utils.formatUnits(
|
|
|
|
|
- cw20BalOnTerra.balance,
|
|
|
|
|
- tokenDefinition.decimals
|
|
|
|
|
- );
|
|
|
|
|
- // let initialCW20BalOnTerra: number = parseInt(balAmount);
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- signer,
|
|
|
|
|
- amount
|
|
|
|
|
- );
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- // transfer tokens
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- amount,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- lcd,
|
|
|
|
|
- TERRA_GAS_PRICES_URL
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(false);
|
|
|
|
|
- const msg = await redeemOnTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- lcd,
|
|
|
|
|
- TERRA_GAS_PRICES_URL
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Get wallet balance on Eth
|
|
|
|
|
- const finalBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- let finalBalOnEthStr = ethers.utils.formatUnits(finalBalOnEth, 18);
|
|
|
|
|
- expect(
|
|
|
|
|
- parseInt(initialBalOnEthStr) - parseInt(finalBalOnEthStr)
|
|
|
|
|
- ).toEqual(1);
|
|
|
|
|
-
|
|
|
|
|
- // Get wallet balance on Tera
|
|
|
|
|
- cw20BalOnTerra = await lcd.wasm.contractQuery(foreignAsset, {
|
|
|
|
|
- balance: {
|
|
|
|
|
- address: TerraWalletAddress,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- balAmount = ethers.utils.formatUnits(
|
|
|
|
|
- cw20BalOnTerra.balance,
|
|
|
|
|
- tokenDefinition.decimals
|
|
|
|
|
- );
|
|
|
|
|
- // let finalCW20BalOnTerra: number = parseInt(balAmount);
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done("An error occurred while trying to send from Ethereum to Terra");
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Terra deposit and transfer tokens", () => {
|
|
|
|
|
- test("Tokens transferred can't exceed tokens deposited", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- // deposit some tokens (separate transactions)
|
|
|
|
|
- for (let i = 0; i < 3; i++) {
|
|
|
|
|
- const deposit = new MsgExecuteContract(
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- {
|
|
|
|
|
- deposit_tokens: {},
|
|
|
|
|
- },
|
|
|
|
|
- { uusd: "900000087654321" }
|
|
|
|
|
- );
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [deposit],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [deposit],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- }
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- // attempt to transfer more than we've deposited
|
|
|
|
|
- const transfer = new MsgExecuteContract(
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- {
|
|
|
|
|
- initiate_transfer: {
|
|
|
|
|
- asset: {
|
|
|
|
|
- amount: "5900000087654321",
|
|
|
|
|
- info: {
|
|
|
|
|
- native_token: {
|
|
|
|
|
- denom: "uusd",
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- recipient_chain: CHAIN_ID_ETH,
|
|
|
|
|
- recipient: Buffer.from(signer.publicKey).toString("base64"),
|
|
|
|
|
- fee: "0",
|
|
|
|
|
- nonce: Math.round(Math.round(Math.random() * 100000)),
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- {}
|
|
|
|
|
- );
|
|
|
|
|
- let error = false;
|
|
|
|
|
- try {
|
|
|
|
|
- await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [transfer],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- error = e.response.data.message.includes("Overflow: Cannot Sub");
|
|
|
|
|
- }
|
|
|
|
|
- expect(error).toEqual(true);
|
|
|
|
|
- // withdraw the tokens we deposited
|
|
|
|
|
- const withdraw = new MsgExecuteContract(
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- {
|
|
|
|
|
- withdraw_tokens: {
|
|
|
|
|
- asset: {
|
|
|
|
|
- native_token: {
|
|
|
|
|
- denom: "uusd",
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- {}
|
|
|
|
|
- );
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [withdraw],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [withdraw],
|
|
|
|
|
- memo: "test",
|
|
|
|
|
- feeDenoms: ["uluna"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while testing deposits to and transfers from Terra"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Post VAA with retry", () => {
|
|
|
|
|
- test("postVAA with retry, no failures", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- const sequence = await transferFromEthToSolana();
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- let maxFailures = 0;
|
|
|
|
|
- // post vaa to Solana
|
|
|
|
|
-
|
|
|
|
|
- const postPromise = postVaaWithRetry(
|
|
|
|
|
- connection,
|
|
|
|
|
- async (transaction) => {
|
|
|
|
|
- await new Promise(function (resolve) {
|
|
|
|
|
- //We delay here so the connection has time to get wrecked
|
|
|
|
|
- setTimeout(function () {
|
|
|
|
|
- resolve(500);
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- return transaction;
|
|
|
|
|
- },
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- Buffer.from(signedVAA),
|
|
|
|
|
- maxFailures
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- await postPromise;
|
|
|
|
|
- // redeem tokens on solana
|
|
|
|
|
- const transaction = await redeemOnSolana(
|
|
|
|
|
- connection,
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- // sign, send, and confirm transaction
|
|
|
|
|
- transaction.partialSign(keypair);
|
|
|
|
|
- const txid = await connection.sendRawTransaction(
|
|
|
|
|
- transaction.serialize()
|
|
|
|
|
- );
|
|
|
|
|
- await connection.confirmTransaction(txid);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedSolana(
|
|
|
|
|
- SOLANA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- connection
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while happy-path testing post VAA with retry."
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Reject on signature failure", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // create a keypair for Solana
|
|
|
|
|
- const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
|
|
|
- const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
|
|
|
|
- const payerAddress = keypair.publicKey.toString();
|
|
|
|
|
- const sequence = await transferFromEthToSolana();
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- let maxFailures = 5;
|
|
|
|
|
- // post vaa to Solana
|
|
|
|
|
-
|
|
|
|
|
- let error = false;
|
|
|
|
|
- try {
|
|
|
|
|
- const postPromise = postVaaWithRetry(
|
|
|
|
|
- connection,
|
|
|
|
|
- async (transaction) => {
|
|
|
|
|
- return Promise.reject();
|
|
|
|
|
- },
|
|
|
|
|
- SOLANA_CORE_BRIDGE_ADDRESS,
|
|
|
|
|
- payerAddress,
|
|
|
|
|
- Buffer.from(signedVAA),
|
|
|
|
|
- maxFailures
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- await postPromise;
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- error = true;
|
|
|
|
|
- }
|
|
|
|
|
- expect(error).toBe(true);
|
|
|
|
|
- done();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error(e);
|
|
|
|
|
- done(
|
|
|
|
|
- "An error occurred while trying to send from Ethereum to Solana"
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Terra to Ethereum", () => {
|
|
|
|
|
- test("Attestation from Terra to ETH", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- const Asset: string = "uluna";
|
|
|
|
|
- const TerraWalletAddress: string =
|
|
|
|
|
- "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";
|
|
|
|
|
- const msg = await attestFromTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TerraWalletAddress,
|
|
|
|
|
- Asset
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const executeTx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "Testing...",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- const result = await lcd.tx.broadcast(executeTx);
|
|
|
|
|
- const info = await waitForTerraExecution(result.txhash, lcd);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
- const sequence = parseSequenceFromLogTerra(info);
|
|
|
|
|
- if (!sequence) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- const emitterAddress = await getEmitterAddressTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const signedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- sequence,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- let success: boolean = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await createWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- success = false;
|
|
|
|
|
- }
|
|
|
|
|
- if (!success) {
|
|
|
|
|
- const cr = await updateWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- success = true;
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Attestation failure: ", e);
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Transfer from Terra", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const Asset: string = "uluna";
|
|
|
|
|
- const FeeAsset: string = "uusd";
|
|
|
|
|
- const Amount: string = "1000000";
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of luna on Terra
|
|
|
|
|
- const initialTerraBalance: number = await queryBalanceOnTerra(Asset);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of uusd on Terra
|
|
|
|
|
- // const initialFeeBalance: number = await queryBalanceOnTerra(FeeAsset);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of wrapped luna on Eth
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- const originAssetHex = tryNativeToHexString(Asset, CHAIN_ID_TERRA);
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of wrapped luna on ethereum
|
|
|
|
|
- const initialLunaBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const initialLunaBalOnEthInt = parseInt(initialLunaBalOnEth._hex);
|
|
|
|
|
-
|
|
|
|
|
- // Start transfer from Terra to Ethereum
|
|
|
|
|
- const hexStr = tryNativeToHexString(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY,
|
|
|
|
|
- CHAIN_ID_ETH
|
|
|
|
|
- );
|
|
|
|
|
- if (!hexStr) {
|
|
|
|
|
- throw new Error("Failed to convert to hexStr");
|
|
|
|
|
- }
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- const msgs = await transferFromTerra(
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- Asset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- hexToUint8Array(hexStr) // This needs to be ETH wallet
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: msgs,
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const executeTx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: msgs,
|
|
|
|
|
- memo: "Testing transfer...",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- const result = await lcd.tx.broadcast(executeTx);
|
|
|
|
|
- const info = await waitForTerraExecution(result.txhash, lcd);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Get VAA in order to do redemption step
|
|
|
|
|
- const sequence = parseSequenceFromLogTerra(info);
|
|
|
|
|
- if (!sequence) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- const emitterAddress = await getEmitterAddressTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const signedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- sequence,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const roe = await redeemOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Test finished. Check wallet balances
|
|
|
|
|
- // Get final balance of uluna on Terra
|
|
|
|
|
- const finalTerraBalance = await queryBalanceOnTerra(Asset);
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance of uusd on Terra
|
|
|
|
|
- // const finalFeeBalance: number = await queryBalanceOnTerra(FeeAsset);
|
|
|
|
|
- expect(initialTerraBalance - 1e6 === finalTerraBalance).toBe(true);
|
|
|
|
|
- const lunaBalOnEthAfter = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const lunaBalOnEthAfterInt = parseInt(lunaBalOnEthAfter._hex);
|
|
|
|
|
- expect(initialLunaBalOnEthInt + 1e6 === lunaBalOnEthAfterInt).toBe(
|
|
|
|
|
- true
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Terra to Ethereum failure: ", e);
|
|
|
|
|
- done("Terra to Ethereum Failure");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Transfer wrapped luna back to Terra", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- // Get initial wallet balances
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const Asset: string = "uluna";
|
|
|
|
|
- const initialTerraBalance: number = await queryBalanceOnTerra(Asset);
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- const originAssetHex = tryNativeToHexString(Asset, CHAIN_ID_TERRA);
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
- const initialLunaBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const initialLunaBalOnEthInt = parseInt(initialLunaBalOnEth._hex);
|
|
|
|
|
- const Amount: string = "1000000";
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer,
|
|
|
|
|
- Amount
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // transfer wrapped luna from Ethereum to Terra
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
-
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const msg = await redeemOnTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- const feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- lcd,
|
|
|
|
|
- TERRA_GAS_PRICES_URL
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Check wallet balances after
|
|
|
|
|
- const finalTerraBalance = await queryBalanceOnTerra(Asset);
|
|
|
|
|
- expect(initialTerraBalance + 1e6 === finalTerraBalance).toBe(true);
|
|
|
|
|
- const finalLunaBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const finalLunaBalOnEthInt = parseInt(finalLunaBalOnEth._hex);
|
|
|
|
|
- expect(initialLunaBalOnEthInt - 1e6 === finalLunaBalOnEthInt).toBe(
|
|
|
|
|
- true
|
|
|
|
|
- );
|
|
|
|
|
- // const uusdBal = await queryBalanceOnTerra("uusd");
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Transfer back failure: ", e);
|
|
|
|
|
- done("Transfer back Failure");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Terra <=> Ethereum roundtrip", () => {
|
|
|
|
|
- test("Transfer CW20 token from Terra to Ethereum and back again", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const CW20: string = "terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
|
|
|
|
|
- const Asset: string = "uluna";
|
|
|
|
|
- const FeeAsset: string = "uusd";
|
|
|
|
|
- const Amount: string = "1000000";
|
|
|
|
|
- const TerraWalletAddress: string =
|
|
|
|
|
- "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";
|
|
|
|
|
-
|
|
|
|
|
- interface Cw20Balance {
|
|
|
|
|
- balance: string;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const wallet = lcd.wallet(mk);
|
|
|
|
|
-
|
|
|
|
|
- // This is the attestation phase of the CW20 token
|
|
|
|
|
- let msg = await attestFromTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TerraWalletAddress,
|
|
|
|
|
- CW20
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- let feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- let executeTx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "Testing...",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- let result = await lcd.tx.broadcast(executeTx);
|
|
|
|
|
- let info = await waitForTerraExecution(result.txhash, lcd);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
- let sequence = parseSequenceFromLogTerra(info);
|
|
|
|
|
- if (!sequence) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- let emitterAddress = await getEmitterAddressTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- let signedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- sequence,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- let success: boolean = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await createWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- success = false;
|
|
|
|
|
- }
|
|
|
|
|
- if (!success) {
|
|
|
|
|
- const cr = await updateWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- success = true;
|
|
|
|
|
- }
|
|
|
|
|
- // Attestation is complete
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of uusd on Terra
|
|
|
|
|
- // const initialFeeBalance: number = await queryBalanceOnTerra(FeeAsset);
|
|
|
|
|
-
|
|
|
|
|
- // Get wallet on eth
|
|
|
|
|
- const originAssetHex = tryNativeToHexString(CW20, CHAIN_ID_TERRA);
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const initialCW20BalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- let initialCW20BalOnEthInt = parseInt(initialCW20BalOnEth._hex);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of CW20 on Terra
|
|
|
|
|
- const tokenDefinition: any = await lcd.wasm.contractQuery(CW20, {
|
|
|
|
|
- token_info: {},
|
|
|
|
|
- });
|
|
|
|
|
- let cw20BalOnTerra: Cw20Balance = await lcd.wasm.contractQuery(CW20, {
|
|
|
|
|
- balance: {
|
|
|
|
|
- address: TerraWalletAddress,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- let amount = ethers.utils.formatUnits(
|
|
|
|
|
- cw20BalOnTerra.balance,
|
|
|
|
|
- tokenDefinition.decimals
|
|
|
|
|
- );
|
|
|
|
|
- let initialCW20BalOnTerra: number = parseInt(amount);
|
|
|
|
|
- const hexStr = tryNativeToHexString(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY,
|
|
|
|
|
- CHAIN_ID_ETH
|
|
|
|
|
- );
|
|
|
|
|
- if (!hexStr) {
|
|
|
|
|
- throw new Error("Failed to convert to hexStr");
|
|
|
|
|
- }
|
|
|
|
|
- const msgs = await transferFromTerra(
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- CW20,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- hexToUint8Array(hexStr) // This needs to be ETH wallet
|
|
|
|
|
- );
|
|
|
|
|
- feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: msgs,
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- executeTx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: msgs,
|
|
|
|
|
- memo: "Testing transfer...",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- result = await lcd.tx.broadcast(executeTx);
|
|
|
|
|
- info = await waitForTerraExecution(result.txhash, lcd);
|
|
|
|
|
- if (!info) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
- sequence = parseSequenceFromLogTerra(info);
|
|
|
|
|
- if (!sequence) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- emitterAddress = await getEmitterAddressTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- signedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- sequence,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const roe = await redeemOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVaa
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Check the wallet balances
|
|
|
|
|
- let finalCW20BalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- let finalCW20BalOnEthInt = parseInt(finalCW20BalOnEth._hex);
|
|
|
|
|
- expect(initialCW20BalOnEthInt + 1e6 === finalCW20BalOnEthInt).toBe(
|
|
|
|
|
- true
|
|
|
|
|
- );
|
|
|
|
|
- cw20BalOnTerra = await lcd.wasm.contractQuery(CW20, {
|
|
|
|
|
- balance: {
|
|
|
|
|
- address: TerraWalletAddress,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- amount = ethers.utils.formatUnits(
|
|
|
|
|
- cw20BalOnTerra.balance,
|
|
|
|
|
- tokenDefinition.decimals
|
|
|
|
|
- );
|
|
|
|
|
- let finalCW20BalOnTerra: number = parseInt(amount);
|
|
|
|
|
- expect(initialCW20BalOnTerra - finalCW20BalOnTerra === 1).toBe(true);
|
|
|
|
|
- // Done checking wallet balances
|
|
|
|
|
-
|
|
|
|
|
- // Start the reverse transfer from Ethereum back to Terra
|
|
|
|
|
- // Get initial wallet balances
|
|
|
|
|
- initialCW20BalOnTerra = finalCW20BalOnTerra;
|
|
|
|
|
- initialCW20BalOnEthInt = finalCW20BalOnEthInt;
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer,
|
|
|
|
|
- Amount
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // transfer token from Ethereum to Terra
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- tryNativeToUint8Array(wallet.key.accAddress, CHAIN_ID_TERRA)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS);
|
|
|
|
|
- emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
-
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- msg = await redeemOnTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- wallet.key.accAddress,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await wallet.sequence(),
|
|
|
|
|
- publicKey: wallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await wallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- lcd,
|
|
|
|
|
- TERRA_GAS_PRICES_URL
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Check wallet balances after transfer back
|
|
|
|
|
- finalCW20BalOnEth = await token.balanceOf(ETH_TEST_WALLET_PUBLIC_KEY);
|
|
|
|
|
- finalCW20BalOnEthInt = parseInt(finalCW20BalOnEth._hex);
|
|
|
|
|
- expect(initialCW20BalOnEthInt - 1e6 === finalCW20BalOnEthInt).toBe(
|
|
|
|
|
- true
|
|
|
|
|
- );
|
|
|
|
|
- cw20BalOnTerra = await lcd.wasm.contractQuery(CW20, {
|
|
|
|
|
- balance: {
|
|
|
|
|
- address: TerraWalletAddress,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
- amount = ethers.utils.formatUnits(
|
|
|
|
|
- cw20BalOnTerra.balance,
|
|
|
|
|
- tokenDefinition.decimals
|
|
|
|
|
- );
|
|
|
|
|
- finalCW20BalOnTerra = parseInt(amount);
|
|
|
|
|
- expect(finalCW20BalOnTerra - initialCW20BalOnTerra === 1).toBe(true);
|
|
|
|
|
- // Done checking wallet balances
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("CW20 Transfer failure: ", e);
|
|
|
|
|
- done("CW20 Transfer Failure");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
- describe("Algorand tests", () => {
|
|
|
|
|
- test("Algorand transfer native ALGO to Eth and back again", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const client: algosdk.Algodv2 = getAlgoClient();
|
|
|
|
|
- const tempAccts: Account[] = await getTempAccounts();
|
|
|
|
|
- const numAccts: number = tempAccts.length;
|
|
|
|
|
- expect(numAccts).toBeGreaterThan(0);
|
|
|
|
|
- const wallet: Account = tempAccts[0];
|
|
|
|
|
-
|
|
|
|
|
- // let accountInfo = await client.accountInformation(wallet.addr).do();
|
|
|
|
|
- // Asset Index of native ALGO is 0
|
|
|
|
|
- const AlgoIndex = BigInt(0);
|
|
|
|
|
- // const b = await getBalances(client, wallet.addr);
|
|
|
|
|
- const txs = await attestFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- wallet.addr,
|
|
|
|
|
- AlgoIndex
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const result = await signSendAndConfirmAlgorand(client, txs, wallet);
|
|
|
|
|
-
|
|
|
|
|
- const sn = parseSequenceFromLogAlgorand(result);
|
|
|
|
|
-
|
|
|
|
|
- // Now, try to send a NOP
|
|
|
|
|
- const suggParams: algosdk.SuggestedParams = await client
|
|
|
|
|
- .getTransactionParams()
|
|
|
|
|
- .do();
|
|
|
|
|
- const nopTxn = makeApplicationCallTxnFromObject({
|
|
|
|
|
- from: wallet.addr,
|
|
|
|
|
- appIndex: safeBigIntToNumber(TOKEN_BRIDGE_ID),
|
|
|
|
|
- onComplete: OnApplicationComplete.NoOpOC,
|
|
|
|
|
- appArgs: [textToUint8Array("nop")],
|
|
|
|
|
- suggestedParams: suggParams,
|
|
|
|
|
- });
|
|
|
|
|
- const resp = await client
|
|
|
|
|
- .sendRawTransaction(nopTxn.signTxn(wallet.sk))
|
|
|
|
|
- .do();
|
|
|
|
|
- await waitForConfirmation(client, resp.txId, 1);
|
|
|
|
|
- // End of NOP
|
|
|
|
|
-
|
|
|
|
|
- const emitterAddr = getEmitterAddressAlgorand(TOKEN_BRIDGE_ID);
|
|
|
|
|
- const { vaaBytes } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- emitterAddr,
|
|
|
|
|
- sn,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
- const pvaa = _parseVAAAlgorand(vaaBytes);
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- let success: boolean = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await createWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- success = false;
|
|
|
|
|
- }
|
|
|
|
|
- if (!success) {
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await updateWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- success = true;
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("failed to updateWrappedOnEth", e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // Check wallet
|
|
|
|
|
- const a = parseInt(AlgoIndex.toString());
|
|
|
|
|
- const originAssetHex = (
|
|
|
|
|
- "0000000000000000000000000000000000000000000000000000000000000000" +
|
|
|
|
|
- a.toString(16)
|
|
|
|
|
- ).slice(-64);
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance on ethereum
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- const initialBalOnEth = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const initialBalOnEthInt = parseInt(initialBalOnEth._hex);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance on Algorand
|
|
|
|
|
- let algoWalletBals: Map<number, number> = await getBalances(
|
|
|
|
|
- client,
|
|
|
|
|
- wallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- const startingAlgoBal = algoWalletBals.get(
|
|
|
|
|
- safeBigIntToNumber(AlgoIndex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!startingAlgoBal) {
|
|
|
|
|
- throw new Error("startingAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Start transfer from Algorand to Ethereum
|
|
|
|
|
- const hexStr = nativeToHexString(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY,
|
|
|
|
|
- CHAIN_ID_ETH
|
|
|
|
|
- );
|
|
|
|
|
- if (!hexStr) {
|
|
|
|
|
- throw new Error("Failed to convert to hexStr");
|
|
|
|
|
- }
|
|
|
|
|
- const AmountToTransfer: number = 12300;
|
|
|
|
|
- const Fee: number = 0;
|
|
|
|
|
- const transferTxs = await transferFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- wallet.addr,
|
|
|
|
|
- AlgoIndex,
|
|
|
|
|
- BigInt(AmountToTransfer),
|
|
|
|
|
- hexStr,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- BigInt(Fee)
|
|
|
|
|
- );
|
|
|
|
|
- const transferResult = await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- transferTxs,
|
|
|
|
|
- wallet
|
|
|
|
|
- );
|
|
|
|
|
- const txSid = parseSequenceFromLogAlgorand(transferResult);
|
|
|
|
|
- const signedVaa = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- emitterAddr,
|
|
|
|
|
- txSid,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
- const pv = _parseVAAAlgorand(signedVaa.vaaBytes);
|
|
|
|
|
- const roe = await redeemOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- // Test finished. Check wallet balances
|
|
|
|
|
- const balOnEthAfter = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const balOnEthAfterInt = parseInt(balOnEthAfter._hex);
|
|
|
|
|
- expect(balOnEthAfterInt - initialBalOnEthInt).toEqual(
|
|
|
|
|
- AmountToTransfer
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance on Algorand
|
|
|
|
|
- algoWalletBals = await getBalances(client, wallet.addr);
|
|
|
|
|
- const finalAlgoBal = algoWalletBals.get(
|
|
|
|
|
- safeBigIntToNumber(AlgoIndex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!finalAlgoBal) {
|
|
|
|
|
- throw new Error("finalAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
- // expect(startingAlgoBal - finalAlgoBal).toBe(AmountToTransfer);
|
|
|
|
|
-
|
|
|
|
|
- // Attempt to transfer from Eth back to Algorand
|
|
|
|
|
- const Amount: string = "100";
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer,
|
|
|
|
|
- Amount
|
|
|
|
|
- );
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- decodeAddress(wallet.addr).publicKey
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
-
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- algoWalletBals = await getBalances(client, wallet.addr);
|
|
|
|
|
- const redeemTxs = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- wallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(client, redeemTxs, wallet);
|
|
|
|
|
- const completed = await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- expect(completed).toBe(true);
|
|
|
|
|
- // const newBal = await token.balanceOf(ETH_TEST_WALLET_PUBLIC_KEY);
|
|
|
|
|
- // const newBalInt = parseInt(newBal._hex);
|
|
|
|
|
- // expect(newBalInt).toBe(AmountToTransfer - parseInt(Amount));
|
|
|
|
|
-
|
|
|
|
|
- // Get second final balance on Algorand
|
|
|
|
|
- algoWalletBals = await getBalances(client, wallet.addr);
|
|
|
|
|
- const secondFinalAlgoBal = algoWalletBals.get(
|
|
|
|
|
- safeBigIntToNumber(AlgoIndex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!secondFinalAlgoBal) {
|
|
|
|
|
- throw new Error("secondFinalAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
- // expect(secondFinalAlgoBal - finalAlgoBal).toBe(
|
|
|
|
|
- // parseInt(Amount) * 100
|
|
|
|
|
- // );
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Algorand ALGO transfer error:", e);
|
|
|
|
|
- done("Algorand ALGO transfer error");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Algorand create chuckNorium, transfer to Eth and back again", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const client: algosdk.Algodv2 = getAlgoClient();
|
|
|
|
|
- const tempAccts: Account[] = await getTempAccounts();
|
|
|
|
|
- const numAccts: number = tempAccts.length;
|
|
|
|
|
- expect(numAccts).toBeGreaterThan(0);
|
|
|
|
|
- const wallet: Account = tempAccts[0];
|
|
|
|
|
-
|
|
|
|
|
- // let accountInfo = await client.accountInformation(wallet.addr).do();
|
|
|
|
|
-
|
|
|
|
|
- const assetIndex: number = await createAsset(wallet);
|
|
|
|
|
- const attestTxs = await attestFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- wallet.addr,
|
|
|
|
|
- BigInt(assetIndex)
|
|
|
|
|
- );
|
|
|
|
|
- const attestResult = await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- attestTxs,
|
|
|
|
|
- wallet
|
|
|
|
|
- );
|
|
|
|
|
- const attestSn = parseSequenceFromLogAlgorand(attestResult);
|
|
|
|
|
-
|
|
|
|
|
- // Now, try to send a NOP
|
|
|
|
|
- const suggParams: algosdk.SuggestedParams = await client
|
|
|
|
|
- .getTransactionParams()
|
|
|
|
|
- .do();
|
|
|
|
|
- const nopTxn = makeApplicationCallTxnFromObject({
|
|
|
|
|
- from: wallet.addr,
|
|
|
|
|
- appIndex: safeBigIntToNumber(TOKEN_BRIDGE_ID),
|
|
|
|
|
- onComplete: OnApplicationComplete.NoOpOC,
|
|
|
|
|
- appArgs: [textToUint8Array("nop")],
|
|
|
|
|
- suggestedParams: suggParams,
|
|
|
|
|
- });
|
|
|
|
|
- const resp = await client
|
|
|
|
|
- .sendRawTransaction(nopTxn.signTxn(wallet.sk))
|
|
|
|
|
- .do();
|
|
|
|
|
- await waitForConfirmation(client, resp.txId, 1);
|
|
|
|
|
- // End of NOP
|
|
|
|
|
-
|
|
|
|
|
- const emitterAddr = getEmitterAddressAlgorand(TOKEN_BRIDGE_ID);
|
|
|
|
|
- const { vaaBytes } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- emitterAddr,
|
|
|
|
|
- attestSn,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(
|
|
|
|
|
- ETH_NODE_URL
|
|
|
|
|
- ) as any;
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- let success: boolean = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await createWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- success = false;
|
|
|
|
|
- }
|
|
|
|
|
- if (!success) {
|
|
|
|
|
- try {
|
|
|
|
|
- const cr = await updateWrappedOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- success = true;
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("failed to updateWrappedOnEth", e);
|
|
|
|
|
- done("failed to update attestation on Eth");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // Check wallet
|
|
|
|
|
- const a = parseInt(assetIndex.toString());
|
|
|
|
|
- const originAssetHex = (
|
|
|
|
|
- "0000000000000000000000000000000000000000000000000000000000000000" +
|
|
|
|
|
- a.toString(16)
|
|
|
|
|
- ).slice(-64);
|
|
|
|
|
- const foreignAsset = await getForeignAssetEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- hexToUint8Array(originAssetHex)
|
|
|
|
|
- );
|
|
|
|
|
- if (!foreignAsset) {
|
|
|
|
|
- throw new Error("foreignAsset is null");
|
|
|
|
|
- }
|
|
|
|
|
- let token = TokenImplementation__factory.connect(
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance on ethereum
|
|
|
|
|
- const ETH_TEST_WALLET_PUBLIC_KEY =
|
|
|
|
|
- "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
|
|
|
|
- // const initialBalOnEth = await token.balanceOf(
|
|
|
|
|
- // ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- // );
|
|
|
|
|
- // const initialBalOnEthInt = parseInt(initialBalOnEth._hex);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance on Algorand
|
|
|
|
|
- let algoWalletBals: Map<number, number> = await getBalances(
|
|
|
|
|
- client,
|
|
|
|
|
- wallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- const startingAlgoBal = algoWalletBals.get(assetIndex);
|
|
|
|
|
- if (!startingAlgoBal) {
|
|
|
|
|
- throw new Error("startingAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Start transfer from Algorand to Ethereum
|
|
|
|
|
- const hexStr = nativeToHexString(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY,
|
|
|
|
|
- CHAIN_ID_ETH
|
|
|
|
|
- );
|
|
|
|
|
- if (!hexStr) {
|
|
|
|
|
- throw new Error("Failed to convert to hexStr");
|
|
|
|
|
- }
|
|
|
|
|
- const AmountToTransfer: number = 12300;
|
|
|
|
|
- const Fee: number = 0;
|
|
|
|
|
- const transferTxs = await transferFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- wallet.addr,
|
|
|
|
|
- BigInt(assetIndex),
|
|
|
|
|
- BigInt(AmountToTransfer),
|
|
|
|
|
- hexStr,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- BigInt(Fee)
|
|
|
|
|
- );
|
|
|
|
|
- const transferResult = await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- transferTxs,
|
|
|
|
|
- wallet
|
|
|
|
|
- );
|
|
|
|
|
- const txSid = parseSequenceFromLogAlgorand(transferResult);
|
|
|
|
|
- const signedVaa = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- emitterAddr,
|
|
|
|
|
- txSid,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
- await redeemOnEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- provider,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- // Test finished. Check wallet balances
|
|
|
|
|
- const balOnEthAfter = await token.balanceOf(
|
|
|
|
|
- ETH_TEST_WALLET_PUBLIC_KEY
|
|
|
|
|
- );
|
|
|
|
|
- const balOnEthAfterInt = parseInt(balOnEthAfter._hex);
|
|
|
|
|
- const FinalAmt: number = AmountToTransfer / 100;
|
|
|
|
|
- expect(balOnEthAfterInt).toEqual(FinalAmt);
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance on Algorand
|
|
|
|
|
- algoWalletBals = await getBalances(client, wallet.addr);
|
|
|
|
|
- const finalAlgoBal = algoWalletBals.get(assetIndex);
|
|
|
|
|
- if (!finalAlgoBal) {
|
|
|
|
|
- throw new Error("finalAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
- expect(startingAlgoBal - finalAlgoBal).toBe(AmountToTransfer);
|
|
|
|
|
-
|
|
|
|
|
- // Attempt to transfer from Eth back to Algorand
|
|
|
|
|
- const Amount: string = "100";
|
|
|
|
|
-
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- signer,
|
|
|
|
|
- Amount
|
|
|
|
|
- );
|
|
|
|
|
- const receipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- foreignAsset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- decodeAddress(wallet.addr).publicKey
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
-
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const redeemTxs = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- signedVAA,
|
|
|
|
|
- wallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(client, redeemTxs, wallet);
|
|
|
|
|
- const completed = await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- expect(completed).toBe(true);
|
|
|
|
|
- const newBal = await token.balanceOf(ETH_TEST_WALLET_PUBLIC_KEY);
|
|
|
|
|
- const newBalInt = parseInt(newBal._hex);
|
|
|
|
|
- expect(newBalInt).toBe(FinalAmt - parseInt(Amount));
|
|
|
|
|
-
|
|
|
|
|
- // Get second final balance on Algorand
|
|
|
|
|
- algoWalletBals = await getBalances(client, wallet.addr);
|
|
|
|
|
- const secondFinalAlgoBal = algoWalletBals.get(assetIndex);
|
|
|
|
|
- if (!secondFinalAlgoBal) {
|
|
|
|
|
- throw new Error("secondFinalAlgoBal is undefined");
|
|
|
|
|
- }
|
|
|
|
|
- expect(secondFinalAlgoBal - finalAlgoBal).toBe(
|
|
|
|
|
- parseInt(Amount) * 100
|
|
|
|
|
- );
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Algorand chuckNorium transfer error:", e);
|
|
|
|
|
- done("Algorand chuckNorium transfer error");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Transfer wrapped Luna from Terra to Algorand and back again", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const tbAddr: string = getApplicationAddress(TOKEN_BRIDGE_ID);
|
|
|
|
|
- const decTbAddr: Uint8Array = decodeAddress(tbAddr).publicKey;
|
|
|
|
|
- const aa: string = uint8ArrayToHex(decTbAddr);
|
|
|
|
|
- const client: algosdk.Algodv2 = getAlgoClient();
|
|
|
|
|
- const tempAccts: Account[] = await getTempAccounts();
|
|
|
|
|
- const numAccts: number = tempAccts.length;
|
|
|
|
|
- expect(numAccts).toBeGreaterThan(0);
|
|
|
|
|
- const algoWallet: Account = tempAccts[0];
|
|
|
|
|
- const lcd = new LCDClient({
|
|
|
|
|
- URL: TERRA_NODE_URL,
|
|
|
|
|
- chainID: TERRA_CHAIN_ID,
|
|
|
|
|
- isClassic: true,
|
|
|
|
|
- });
|
|
|
|
|
- const mk = new MnemonicKey({
|
|
|
|
|
- mnemonic: TERRA_PRIVATE_KEY,
|
|
|
|
|
- });
|
|
|
|
|
- const terraWallet = lcd.wallet(mk);
|
|
|
|
|
- const Asset: string = "uluna";
|
|
|
|
|
- // const Asset: string = "uusd";
|
|
|
|
|
- const FeeAsset: string = "uusd";
|
|
|
|
|
- const Amount: string = "1000000";
|
|
|
|
|
- const TerraWalletAddress: string =
|
|
|
|
|
- "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v";
|
|
|
|
|
- const msg = await attestFromTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TerraWalletAddress,
|
|
|
|
|
- Asset
|
|
|
|
|
- );
|
|
|
|
|
- const gasPrices = await getTerraGasPrices();
|
|
|
|
|
- let feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await terraWallet.sequence(),
|
|
|
|
|
- publicKey: terraWallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const executeAttest = await terraWallet.createAndSignTx({
|
|
|
|
|
- msgs: [msg],
|
|
|
|
|
- memo: "Testing...",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- const attestResult = await lcd.tx.broadcast(executeAttest);
|
|
|
|
|
- const attestInfo = await waitForTerraExecution(
|
|
|
|
|
- attestResult.txhash,
|
|
|
|
|
- lcd
|
|
|
|
|
- );
|
|
|
|
|
- if (!attestInfo) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
- const attestSn = parseSequenceFromLogTerra(attestInfo);
|
|
|
|
|
- if (!attestSn) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- const emitterAddress = await getEmitterAddressTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const attestSignedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- attestSn,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const createWrappedTxs = await createWrappedOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- algoWallet.addr,
|
|
|
|
|
- attestSignedVaa
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- createWrappedTxs,
|
|
|
|
|
- algoWallet
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let assetIdCreated = await getForeignAssetFromVaaAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- attestSignedVaa
|
|
|
|
|
- );
|
|
|
|
|
- if (!assetIdCreated) {
|
|
|
|
|
- throw new Error("Failed to create asset");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Start of transfer from Terra to Algorand
|
|
|
|
|
- // Get initial balance of luna on Terra
|
|
|
|
|
- const initialTerraBalance: number = await queryBalanceOnTerra(Asset);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of uusd on Terra
|
|
|
|
|
- // const initialFeeBalance: number = await queryBalanceOnTerra(FeeAsset);
|
|
|
|
|
-
|
|
|
|
|
- // Get initial balance of wrapped luna on Algorand
|
|
|
|
|
- const originAssetHex = nativeToHexString(Asset, CHAIN_ID_TERRA);
|
|
|
|
|
- if (!originAssetHex) {
|
|
|
|
|
- throw new Error("originAssetHex is null");
|
|
|
|
|
- }
|
|
|
|
|
- // TODO: Get wallet balance on Algorand
|
|
|
|
|
-
|
|
|
|
|
- // Get Balances
|
|
|
|
|
- const tbBals: Map<number, number> = await getBalances(
|
|
|
|
|
- client,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- // "TPFKQBOR7RJ475XW6XMOZMSMBCZH6WNGFQNT7CM7NL2UMBCMBIU5PVBGPM"
|
|
|
|
|
- );
|
|
|
|
|
- let assetIdCreatedBegBal: number = 0;
|
|
|
|
|
- const tempBal = tbBals.get(safeBigIntToNumber(assetIdCreated));
|
|
|
|
|
- if (tempBal) {
|
|
|
|
|
- assetIdCreatedBegBal = tempBal;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Start transfer from Terra to Algorand
|
|
|
|
|
- const txMsgs = await transferFromTerra(
|
|
|
|
|
- terraWallet.key.accAddress,
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- Asset,
|
|
|
|
|
- Amount,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- decodeAddress(algoWallet.addr).publicKey // This needs to be Algorand wallet
|
|
|
|
|
- );
|
|
|
|
|
- const executeTx = await terraWallet.createAndSignTx({
|
|
|
|
|
- msgs: txMsgs,
|
|
|
|
|
- memo: "Testing transfer...",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- const txResult = await lcd.tx.broadcast(executeTx);
|
|
|
|
|
- const txInfo = await waitForTerraExecution(txResult.txhash, lcd);
|
|
|
|
|
- if (!txInfo) {
|
|
|
|
|
- throw new Error("info not found");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Get VAA in order to do redemption step
|
|
|
|
|
- const txSn = parseSequenceFromLogTerra(txInfo);
|
|
|
|
|
- if (!txSn) {
|
|
|
|
|
- throw new Error("Sequence not found");
|
|
|
|
|
- }
|
|
|
|
|
- const txSignedVaa = await getSignedVAABySequence(
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- txSn,
|
|
|
|
|
- emitterAddress
|
|
|
|
|
- );
|
|
|
|
|
- const redeemTxs = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- txSignedVaa,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(client, redeemTxs, algoWallet);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- txSignedVaa
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Test finished. Check wallet balances
|
|
|
|
|
- // Get Balances
|
|
|
|
|
- const bals: Map<number, number> = await getBalances(
|
|
|
|
|
- client,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- let assetIdCreatedEndBal: number = 0;
|
|
|
|
|
- const tmpBal = bals.get(safeBigIntToNumber(assetIdCreated));
|
|
|
|
|
- if (tmpBal) {
|
|
|
|
|
- assetIdCreatedEndBal = tmpBal;
|
|
|
|
|
- }
|
|
|
|
|
- expect(assetIdCreatedEndBal - assetIdCreatedBegBal).toBe(
|
|
|
|
|
- parseInt(Amount)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance of uluna on Terra
|
|
|
|
|
- const finalTerraBalance = await queryBalanceOnTerra(Asset);
|
|
|
|
|
-
|
|
|
|
|
- // Get final balance of uusd on Terra
|
|
|
|
|
- // const finalFeeBalance: number = await queryBalanceOnTerra(FeeAsset);
|
|
|
|
|
- expect(initialTerraBalance - 1e6 === finalTerraBalance).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Start of transfer back to Terra
|
|
|
|
|
- const TransferBackAmount: number = 100000;
|
|
|
|
|
-
|
|
|
|
|
- // transfer wrapped luna from Algorand to Terra
|
|
|
|
|
- const terraHexStr = nativeToHexString(
|
|
|
|
|
- terraWallet.key.accAddress,
|
|
|
|
|
- CHAIN_ID_TERRA
|
|
|
|
|
- );
|
|
|
|
|
- if (!terraHexStr) {
|
|
|
|
|
- throw new Error("Failed to convert to hexStr");
|
|
|
|
|
- }
|
|
|
|
|
- const Fee: number = 0;
|
|
|
|
|
- const transferTxs = await transferFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- algoWallet.addr,
|
|
|
|
|
- assetIdCreated,
|
|
|
|
|
- BigInt(TransferBackAmount),
|
|
|
|
|
- terraHexStr,
|
|
|
|
|
- CHAIN_ID_TERRA,
|
|
|
|
|
- BigInt(Fee)
|
|
|
|
|
- );
|
|
|
|
|
- const transferResult = await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- transferTxs,
|
|
|
|
|
- algoWallet
|
|
|
|
|
- );
|
|
|
|
|
- const txSid = parseSequenceFromLogAlgorand(transferResult);
|
|
|
|
|
- const signedVaa = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- aa,
|
|
|
|
|
- txSid,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const redeemMsg = await redeemOnTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- terraWallet.key.accAddress,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- );
|
|
|
|
|
- feeEstimate = await lcd.tx.estimateFee(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- sequenceNumber: await terraWallet.sequence(),
|
|
|
|
|
- publicKey: terraWallet.key.publicKey,
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- {
|
|
|
|
|
- msgs: [redeemMsg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: [FeeAsset],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const tx = await terraWallet.createAndSignTx({
|
|
|
|
|
- msgs: [redeemMsg],
|
|
|
|
|
- memo: "localhost",
|
|
|
|
|
- feeDenoms: ["uusd"],
|
|
|
|
|
- gasPrices,
|
|
|
|
|
- fee: feeEstimate,
|
|
|
|
|
- });
|
|
|
|
|
- await lcd.tx.broadcast(tx);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedTerra(
|
|
|
|
|
- TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signedVaa.vaaBytes,
|
|
|
|
|
- lcd,
|
|
|
|
|
- TERRA_GAS_PRICES_URL
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
-
|
|
|
|
|
- // Check wallet balances after
|
|
|
|
|
- const finalLunaOnTerraBalance = await queryBalanceOnTerra(Asset);
|
|
|
|
|
- expect(finalLunaOnTerraBalance - finalTerraBalance).toBe(
|
|
|
|
|
- TransferBackAmount
|
|
|
|
|
- );
|
|
|
|
|
- const retBals: Map<number, number> = await getBalances(
|
|
|
|
|
- client,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- let assetIdCreatedFinBal: number = 0;
|
|
|
|
|
- const tBal = retBals.get(safeBigIntToNumber(assetIdCreated));
|
|
|
|
|
- if (tBal) {
|
|
|
|
|
- assetIdCreatedFinBal = tBal;
|
|
|
|
|
- }
|
|
|
|
|
- expect(assetIdCreatedEndBal - assetIdCreatedFinBal).toBe(
|
|
|
|
|
- TransferBackAmount
|
|
|
|
|
- );
|
|
|
|
|
- const info: WormholeWrappedInfo = await getOriginalAssetAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- assetIdCreated
|
|
|
|
|
- );
|
|
|
|
|
- expect(info.chainId).toBe(CHAIN_ID_TERRA);
|
|
|
|
|
- expect(info.isWrapped).toBe(true);
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("Terra <=> Algorand error:", e);
|
|
|
|
|
- done("Terra <=> Algorand error");
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- test("Testing relay type redeem", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const client: algosdk.Algodv2 = getAlgoClient();
|
|
|
|
|
- const tempAccts: Account[] = await getTempAccounts();
|
|
|
|
|
- const numAccts: number = tempAccts.length;
|
|
|
|
|
- expect(numAccts).toBeGreaterThan(0);
|
|
|
|
|
- const algoWallet: Account = tempAccts[0];
|
|
|
|
|
- const algoWalletBalance = await getBalance(
|
|
|
|
|
- client,
|
|
|
|
|
- algoWallet.addr,
|
|
|
|
|
- BigInt(0)
|
|
|
|
|
- );
|
|
|
|
|
- expect(algoWalletBalance).toBeGreaterThan(0);
|
|
|
|
|
- const relayerWallet: Account = tempAccts[1];
|
|
|
|
|
- const relayerWalletBalance = await getBalance(
|
|
|
|
|
- client,
|
|
|
|
|
- relayerWallet.addr,
|
|
|
|
|
- BigInt(0)
|
|
|
|
|
- );
|
|
|
|
|
- expect(relayerWalletBalance).toBeGreaterThan(0);
|
|
|
|
|
- // ETH setup to transfer LUNA to Algorand
|
|
|
|
|
-
|
|
|
|
|
- // create a signer for Eth
|
|
|
|
|
- const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
|
|
- const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
|
|
|
|
- // attest the test token
|
|
|
|
|
- const receipt = await attestFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const sequence = parseSequenceFromLogEth(
|
|
|
|
|
- receipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- emitterAddress,
|
|
|
|
|
- sequence,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- const createWrappedTxs = await createWrappedOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- algoWallet.addr,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- createWrappedTxs,
|
|
|
|
|
- algoWallet
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let assetIdCreated = await getForeignAssetFromVaaAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- signedVAA
|
|
|
|
|
- );
|
|
|
|
|
- if (!assetIdCreated) {
|
|
|
|
|
- throw new Error("Failed to create asset");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Start of transfer from ETH to Algorand
|
|
|
|
|
- // approve the bridge to spend tokens
|
|
|
|
|
- const amount = parseUnits("2", 18);
|
|
|
|
|
- const halfAmount = parseUnits("1", 18);
|
|
|
|
|
- await approveEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- signer,
|
|
|
|
|
- amount
|
|
|
|
|
- );
|
|
|
|
|
- // transfer half the tokens directly
|
|
|
|
|
- const firstHalfReceipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- halfAmount,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- decodeAddress(algoWallet.addr).publicKey // This needs to be Algorand wallet
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const firstHalfSn = parseSequenceFromLogEth(
|
|
|
|
|
- firstHalfReceipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- const ethEmitterAddress = getEmitterAddressEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: firstHalfVaa } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- ethEmitterAddress,
|
|
|
|
|
- firstHalfSn,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Redeem half the amount on Algorand
|
|
|
|
|
- const firstHalfRedeemTxs = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- firstHalfVaa,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- firstHalfRedeemTxs,
|
|
|
|
|
- algoWallet
|
|
|
|
|
- );
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- firstHalfVaa
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- // transfer second half of tokens via relayer
|
|
|
|
|
- const secondHalfReceipt = await transferFromEth(
|
|
|
|
|
- ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
|
- signer,
|
|
|
|
|
- TEST_ERC20,
|
|
|
|
|
- halfAmount,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- decodeAddress(algoWallet.addr).publicKey // This needs to be Algorand wallet
|
|
|
|
|
- );
|
|
|
|
|
- // get the sequence from the logs (needed to fetch the vaa)
|
|
|
|
|
- const secondHalfSn = parseSequenceFromLogEth(
|
|
|
|
|
- secondHalfReceipt,
|
|
|
|
|
- ETH_CORE_BRIDGE_ADDRESS
|
|
|
|
|
- );
|
|
|
|
|
- // poll until the guardian(s) witness and sign the vaa
|
|
|
|
|
- const { vaaBytes: secondHalfVaa } = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ETH,
|
|
|
|
|
- ethEmitterAddress,
|
|
|
|
|
- secondHalfSn,
|
|
|
|
|
- {
|
|
|
|
|
- transport: NodeHttpTransport(),
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Redeem second half the amount on Algorand
|
|
|
|
|
- const redeemTxs = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- secondHalfVaa,
|
|
|
|
|
- relayerWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
- await signSendAndConfirmAlgorand(client, redeemTxs, relayerWallet);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- secondHalfVaa
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- provider.destroy();
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("new test error:", e);
|
|
|
|
|
- done("new test error");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- test("testing algorand payload3", (done) => {
|
|
|
|
|
- (async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const tbAddr: string = getApplicationAddress(TOKEN_BRIDGE_ID);
|
|
|
|
|
- const decTbAddr: Uint8Array = decodeAddress(tbAddr).publicKey;
|
|
|
|
|
- const aa: string = uint8ArrayToHex(decTbAddr);
|
|
|
|
|
-
|
|
|
|
|
- const client: algosdk.Algodv2 = getAlgoClient();
|
|
|
|
|
- const tempAccts: Account[] = await getTempAccounts();
|
|
|
|
|
- const numAccts: number = tempAccts.length;
|
|
|
|
|
- expect(numAccts).toBeGreaterThan(0);
|
|
|
|
|
- const algoWallet: Account = tempAccts[0];
|
|
|
|
|
-
|
|
|
|
|
- const Fee: number = 0;
|
|
|
|
|
- var testapp: number = 8;
|
|
|
|
|
- var dest = utils
|
|
|
|
|
- .hexZeroPad(BigNumber.from(testapp).toHexString(), 32)
|
|
|
|
|
- .substring(2);
|
|
|
|
|
-
|
|
|
|
|
- const transferTxs = await transferFromAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- algoWallet.addr,
|
|
|
|
|
- BigInt(0),
|
|
|
|
|
- BigInt(100),
|
|
|
|
|
- dest,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- BigInt(Fee),
|
|
|
|
|
- hexToUint8Array("ff")
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const transferResult = await signSendAndConfirmAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- transferTxs,
|
|
|
|
|
- algoWallet
|
|
|
|
|
- );
|
|
|
|
|
- const txSid = parseSequenceFromLogAlgorand(transferResult);
|
|
|
|
|
- const signedVaa = await getSignedVAAWithRetry(
|
|
|
|
|
- WORMHOLE_RPC_HOSTS,
|
|
|
|
|
- CHAIN_ID_ALGORAND,
|
|
|
|
|
- aa,
|
|
|
|
|
- txSid,
|
|
|
|
|
- { transport: NodeHttpTransport() }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const txns = await redeemOnAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- CORE_ID,
|
|
|
|
|
- signedVaa.vaaBytes,
|
|
|
|
|
- algoWallet.addr
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const wbefore = await getBalance(
|
|
|
|
|
- client,
|
|
|
|
|
- getApplicationAddress(testapp),
|
|
|
|
|
- BigInt(0)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- await signSendAndConfirmAlgorand(client, txns, algoWallet);
|
|
|
|
|
- expect(
|
|
|
|
|
- await getIsTransferCompletedAlgorand(
|
|
|
|
|
- client,
|
|
|
|
|
- TOKEN_BRIDGE_ID,
|
|
|
|
|
- signedVaa.vaaBytes
|
|
|
|
|
- )
|
|
|
|
|
- ).toBe(true);
|
|
|
|
|
- const wafter = await getBalance(
|
|
|
|
|
- client,
|
|
|
|
|
- getApplicationAddress(testapp),
|
|
|
|
|
- BigInt(0)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- expect(BigInt(wafter - wbefore) === BigInt(100));
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error("new test error:", e);
|
|
|
|
|
- done("new test error");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- done();
|
|
|
|
|
- })();
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
-});
|
|
|