diff --git a/ethereum/scripts/deploy_test_token.js b/ethereum/scripts/deploy_test_token.js index f91e9b669..e9e09a4d6 100644 --- a/ethereum/scripts/deploy_test_token.js +++ b/ethereum/scripts/deploy_test_token.js @@ -36,7 +36,7 @@ const interateToStandardTransactionCount = async () => { return Promise.resolve(); }; -module.exports = async function(callback) { +module.exports = async function (callback) { try { const accounts = await web3.eth.getAccounts(); @@ -61,6 +61,10 @@ module.exports = async function(callback) { from: accounts[0], gas: 1000000, }); + await token.methods.mint(accounts[2], "1000000000000000000000").send({ + from: accounts[0], + gas: 1000000, + }); const nftAddress = ( await ERC721.new( diff --git a/sdk/js/src/token_bridge/__tests__/consts.ts b/sdk/js/src/token_bridge/__tests__/consts.ts index e3936e724..84440eb84 100644 --- a/sdk/js/src/token_bridge/__tests__/consts.ts +++ b/sdk/js/src/token_bridge/__tests__/consts.ts @@ -7,6 +7,8 @@ const ci = !!process.env.CI; export const ETH_NODE_URL = ci ? "ws://eth-devnet:8545" : "ws://localhost:8545"; export const ETH_PRIVATE_KEY = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; +export const ETH_PRIVATE_KEY2 = + "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c"; // account 2 export const ETH_CORE_BRIDGE_ADDRESS = "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"; export const ETH_TOKEN_BRIDGE_ADDRESS = @@ -31,12 +33,19 @@ export const TERRA_CHAIN_ID = "localterra"; export const TERRA_GAS_PRICES_URL = ci ? "http://terra-fcd:3060/v1/txs/gas_prices" : "http://localhost:3060/v1/txs/gas_prices"; +export const TERRA2_GAS_PRICES_URL = ci + ? "http://terra2-fcd:3060/v1/txs/gas_prices" + : "http://localhost:3061/v1/txs/gas_prices"; export const TERRA_CORE_BRIDGE_ADDRESS = "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"; export const TERRA_TOKEN_BRIDGE_ADDRESS = "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"; +export const TERRA2_TOKEN_BRIDGE_ADDRESS = + "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6"; export const TERRA_PRIVATE_KEY = "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"; +export const TERRA2_PRIVATE_KEY = + "symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb"; // test3 export const TEST_ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A"; export const TEST_SOLANA_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"; export const WORMHOLE_RPC_HOSTS = ci diff --git a/sdk/js/src/token_bridge/__tests__/helpers.ts b/sdk/js/src/token_bridge/__tests__/helpers.ts index 076e1717f..c80e5cdd4 100644 --- a/sdk/js/src/token_bridge/__tests__/helpers.ts +++ b/sdk/js/src/token_bridge/__tests__/helpers.ts @@ -105,9 +105,9 @@ export async function transferFromEthToSolana(): Promise { } export async function waitForTerraExecution( - transaction: string + transaction: string, + lcd: LCDClient ): Promise { - const lcd = new LCDClient(TERRA_HOST); let done: boolean = false; let info; while (!done) { diff --git a/sdk/js/src/token_bridge/__tests__/integration.ts b/sdk/js/src/token_bridge/__tests__/integration.ts index e5634a1bf..8017aabb7 100644 --- a/sdk/js/src/token_bridge/__tests__/integration.ts +++ b/sdk/js/src/token_bridge/__tests__/integration.ts @@ -1180,7 +1180,7 @@ describe("Integration Tests", () => { fee: feeEstimate, }); const result = await lcd.tx.broadcast(executeTx); - const info = await waitForTerraExecution(result.txhash); + const info = await waitForTerraExecution(result.txhash, lcd); if (!info) { throw new Error("info not found"); } @@ -1316,7 +1316,7 @@ describe("Integration Tests", () => { fee: feeEstimate, }); const result = await lcd.tx.broadcast(executeTx); - const info = await waitForTerraExecution(result.txhash); + const info = await waitForTerraExecution(result.txhash, lcd); if (!info) { throw new Error("info not found"); } @@ -1559,7 +1559,7 @@ describe("Integration Tests", () => { fee: feeEstimate, }); let result = await lcd.tx.broadcast(executeTx); - let info = await waitForTerraExecution(result.txhash); + let info = await waitForTerraExecution(result.txhash, lcd); if (!info) { throw new Error("info not found"); } @@ -1678,7 +1678,7 @@ describe("Integration Tests", () => { fee: feeEstimate, }); result = await lcd.tx.broadcast(executeTx); - info = await waitForTerraExecution(result.txhash); + info = await waitForTerraExecution(result.txhash, lcd); if (!info) { throw new Error("info not found"); } @@ -2390,7 +2390,10 @@ describe("Integration Tests", () => { fee: feeEstimate, }); const attestResult = await lcd.tx.broadcast(executeAttest); - const attestInfo = await waitForTerraExecution(attestResult.txhash); + const attestInfo = await waitForTerraExecution( + attestResult.txhash, + lcd + ); if (!attestInfo) { throw new Error("info not found"); } @@ -2471,7 +2474,7 @@ describe("Integration Tests", () => { fee: feeEstimate, }); const txResult = await lcd.tx.broadcast(executeTx); - const txInfo = await waitForTerraExecution(txResult.txhash); + const txInfo = await waitForTerraExecution(txResult.txhash, lcd); if (!txInfo) { throw new Error("info not found"); } diff --git a/sdk/js/src/token_bridge/__tests__/terra2-integration.ts b/sdk/js/src/token_bridge/__tests__/terra2-integration.ts new file mode 100644 index 000000000..466a9877a --- /dev/null +++ b/sdk/js/src/token_bridge/__tests__/terra2-integration.ts @@ -0,0 +1,183 @@ +import { beforeAll, afterAll, expect, test } from "@jest/globals"; +import { + isTxError, + LCDClient, + MnemonicKey, + Msg, + Wallet, +} from "@terra-money/terra.js"; +import { ethers } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { + createWrappedOnEth, + createWrappedOnTerra, + getEmitterAddressEth, + getEmitterAddressTerra, + getIsTransferCompletedTerra, + parseSequenceFromLogEth, + parseSequenceFromLogTerra, + redeemOnEth, + redeemOnTerra, + updateWrappedOnEth, +} from "../.."; +import { tryNativeToUint8Array } from "../../utils"; +import { CHAIN_ID_ETH, CHAIN_ID_TERRA2 } from "../../utils/consts"; +import { attestFromEth, attestFromTerra } from "../attest"; +import { approveEth, transferFromEth, transferFromTerra } from "../transfer"; +import { + ETH_CORE_BRIDGE_ADDRESS, + ETH_NODE_URL, + ETH_PRIVATE_KEY2, + ETH_TOKEN_BRIDGE_ADDRESS, + TERRA2_GAS_PRICES_URL, + TERRA2_TOKEN_BRIDGE_ADDRESS, + TERRA2_PRIVATE_KEY, + TEST_ERC20, +} from "./consts"; +import { getSignedVAABySequence, waitForTerraExecution } from "./helpers"; + +const lcd = new LCDClient({ + URL: !!process.env.CI ? "http://terra2-terrad:1317" : "http://localhost:1318", + chainID: "localterra", +}); +const terraWallet = lcd.wallet( + new MnemonicKey({ mnemonic: TERRA2_PRIVATE_KEY }) +); +const terraWalletAddress = terraWallet.key.accAddress; + +const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL); +const signer = new ethers.Wallet(ETH_PRIVATE_KEY2, provider); +const ethEmitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS); +const ethTransferAmount = parseUnits("1", 18); + +let ethWalletAddress: string; +let terraEmitterAddress: string; + +beforeAll(async () => { + ethWalletAddress = await signer.getAddress(); + terraEmitterAddress = await getEmitterAddressTerra( + TERRA2_TOKEN_BRIDGE_ADDRESS + ); +}); + +afterAll(async () => { + provider.destroy(); +}); + +const terraBroadcastAndWaitForExecution = async ( + msgs: Msg[], + wallet: Wallet +) => { + const tx = await wallet.createAndSignTx({ + msgs, + }); + const txResult = await lcd.tx.broadcast(tx); + if (isTxError(txResult)) { + throw new Error("tx error"); + } + const txInfo = await waitForTerraExecution(txResult.txhash, lcd); + if (!txInfo) { + throw new Error("tx info not found"); + } + return txInfo; +}; + +const terraBroadcastTxAndGetSignedVaa = async (msgs: Msg[], wallet: Wallet) => { + const txInfo = await terraBroadcastAndWaitForExecution(msgs, wallet); + const txSequence = parseSequenceFromLogTerra(txInfo); + if (!txSequence) { + throw new Error("tx sequence not found"); + } + return await getSignedVAABySequence( + CHAIN_ID_TERRA2, + txSequence, + terraEmitterAddress + ); +}; + +const ethParseLogAndGetSignedVaa = async (receipt: ethers.ContractReceipt) => { + const sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS); + return await getSignedVAABySequence( + CHAIN_ID_ETH, + sequence, + ethEmitterAddress + ); +}; + +test("Attest and transfer token from Terra2 to Ethereum", async () => { + // Attest + const attestMsg = await attestFromTerra( + TERRA2_TOKEN_BRIDGE_ADDRESS, + terraWalletAddress, + "uluna" + ); + const attestSignedVaa = await terraBroadcastTxAndGetSignedVaa( + [attestMsg], + terraWallet + ); + try { + await createWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, attestSignedVaa); + } catch { + await updateWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, attestSignedVaa); + } + // Transfer + const transferMsgs = await transferFromTerra( + terraWalletAddress, + TERRA2_TOKEN_BRIDGE_ADDRESS, + "uluna", + "1000000", + CHAIN_ID_ETH, + tryNativeToUint8Array(ethWalletAddress, CHAIN_ID_ETH) + ); + const transferSignedVaa = await terraBroadcastTxAndGetSignedVaa( + transferMsgs, + terraWallet + ); + await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, transferSignedVaa); +}); + +test("Attest and transfer token from Ethereum to Terra2", async () => { + // Attest + const attestReceipt = await attestFromEth( + ETH_TOKEN_BRIDGE_ADDRESS, + signer, + TEST_ERC20 + ); + const attestSignedVaa = await ethParseLogAndGetSignedVaa(attestReceipt); + const createWrappedMsg = await createWrappedOnTerra( + TERRA2_TOKEN_BRIDGE_ADDRESS, + terraWalletAddress, + attestSignedVaa + ); + await terraBroadcastAndWaitForExecution([createWrappedMsg], terraWallet); + // Transfer + await approveEth( + ETH_TOKEN_BRIDGE_ADDRESS, + TEST_ERC20, + signer, + ethTransferAmount + ); + const transferReceipt = await transferFromEth( + ETH_TOKEN_BRIDGE_ADDRESS, + signer, + TEST_ERC20, + ethTransferAmount, + CHAIN_ID_TERRA2, + tryNativeToUint8Array(terraWalletAddress, CHAIN_ID_TERRA2) + ); + const transferSignedVaa = await ethParseLogAndGetSignedVaa(transferReceipt); + const redeemMsg = await redeemOnTerra( + TERRA2_TOKEN_BRIDGE_ADDRESS, + terraWalletAddress, + transferSignedVaa + ); + await terraBroadcastAndWaitForExecution([redeemMsg], terraWallet); + expect( + await getIsTransferCompletedTerra( + TERRA2_TOKEN_BRIDGE_ADDRESS, + transferSignedVaa, + lcd, + TERRA2_GAS_PRICES_URL + ) + ).toBe(true); +});