|
|
@@ -0,0 +1,524 @@
|
|
|
+import "dotenv/config";
|
|
|
+import { describe, expect, jest, test } from "@jest/globals";
|
|
|
+import {
|
|
|
+ approveEth,
|
|
|
+ attestFromEth,
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ ChainId,
|
|
|
+ CONTRACTS,
|
|
|
+ createWrappedOnEth,
|
|
|
+ getEmitterAddressEth,
|
|
|
+ getForeignAssetEth,
|
|
|
+ getSignedVAAWithRetry,
|
|
|
+ hexToUint8Array,
|
|
|
+ parseSequenceFromLogEth,
|
|
|
+ parseTokenTransferVaa,
|
|
|
+ redeemOnEth,
|
|
|
+ serialiseVAA,
|
|
|
+ sign,
|
|
|
+ TokenBridgeTransfer,
|
|
|
+ transferFromEth,
|
|
|
+ tryNativeToHexString,
|
|
|
+ tryNativeToUint8Array,
|
|
|
+ uint8ArrayToHex,
|
|
|
+ VAA,
|
|
|
+} from "@certusone/wormhole-sdk";
|
|
|
+import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
|
|
+import { ethers } from "ethers";
|
|
|
+import * as devnetConsts from "../devnet-consts.json";
|
|
|
+import { parseUnits } from "ethers/lib/utils";
|
|
|
+import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
|
|
+
|
|
|
+jest.setTimeout(60000);
|
|
|
+
|
|
|
+if (process.env.INIT_SIGNERS_KEYS_CSV === "undefined") {
|
|
|
+ let msg = `.env is missing. run "make contracts-tools-deps" to fetch.`;
|
|
|
+ console.error(msg);
|
|
|
+ throw msg;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Goals:
|
|
|
+ * 1. Ensure a token can be sent from its origin chain
|
|
|
+ * 2. Ensure a token can be sent back from a foreign chain
|
|
|
+ * 3. Ensure spoofed tokens for more than the outstanding amount rejects successfully
|
|
|
+ * 4. Validate the guardian metrics for each of these cases
|
|
|
+ * 5. Bonus: Validate the on chain contract state via queries
|
|
|
+ */
|
|
|
+
|
|
|
+const ci = !!process.env.CI;
|
|
|
+
|
|
|
+const GUARDIAN_HOST = ci ? "guardian" : "localhost";
|
|
|
+const GUARDIAN_RPCS = [`http://${GUARDIAN_HOST}:7071`];
|
|
|
+const GUARDIAN_METRICS = `http://${GUARDIAN_HOST}:6060/metrics`;
|
|
|
+const ETH_NODE_URL = ci ? "ws://eth-devnet:8545" : "ws://localhost:8545";
|
|
|
+const BSC_NODE_URL = ci ? "ws://eth-devnet2:8545" : "ws://localhost:8546";
|
|
|
+const ETH_PRIVATE_KEY9 =
|
|
|
+ "0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773";
|
|
|
+const ETH_GA_TEST_TOKEN =
|
|
|
+ devnetConsts.chains[CHAIN_ID_ETH].addresses.testGA.address;
|
|
|
+const DECIMALS = devnetConsts.chains[CHAIN_ID_ETH].addresses.testGA.decimals;
|
|
|
+const VAA_SIGNERS = process.env.INIT_SIGNERS_KEYS_CSV.split(",");
|
|
|
+const GOVERNANCE_CHAIN = Number(devnetConsts.global.governanceChainId);
|
|
|
+const GOVERNANCE_EMITTER = devnetConsts.global.governanceEmitterAddress;
|
|
|
+const TENDERMINT_URL = ci ? "http://wormchain:26657" : "http://localhost:26659";
|
|
|
+const GA_ADDRESS =
|
|
|
+ "wormhole14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9srrg465";
|
|
|
+
|
|
|
+function sleep(ms) {
|
|
|
+ return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
+}
|
|
|
+
|
|
|
+let ethProvider: ethers.providers.WebSocketProvider;
|
|
|
+let ethSigner: ethers.Wallet;
|
|
|
+let bscProvider: ethers.providers.WebSocketProvider;
|
|
|
+let bscSigner: ethers.Wallet;
|
|
|
+let cosmWasmClient: CosmWasmClient;
|
|
|
+
|
|
|
+beforeAll(async () => {
|
|
|
+ // create a signer for Eth
|
|
|
+ ethProvider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
|
|
+ ethSigner = new ethers.Wallet(ETH_PRIVATE_KEY9, ethProvider);
|
|
|
+ // create a signer for BSC
|
|
|
+ bscProvider = new ethers.providers.WebSocketProvider(BSC_NODE_URL);
|
|
|
+ bscSigner = new ethers.Wallet(ETH_PRIVATE_KEY9, bscProvider);
|
|
|
+ cosmWasmClient = await CosmWasmClient.connect(TENDERMINT_URL);
|
|
|
+});
|
|
|
+
|
|
|
+afterAll(async () => {
|
|
|
+ await ethProvider.destroy();
|
|
|
+ await bscProvider.destroy();
|
|
|
+ cosmWasmClient.disconnect();
|
|
|
+});
|
|
|
+
|
|
|
+// Guardian metrics are prometheus data
|
|
|
+const fetchGlobalAccountantMetrics = async (): Promise<{
|
|
|
+ global_accountant_connection_errors_total: number;
|
|
|
+ global_accountant_error_events_received: number;
|
|
|
+ global_accountant_events_received: number;
|
|
|
+ global_accountant_submit_failures: number;
|
|
|
+ global_accountant_total_balance_errors: number;
|
|
|
+ global_accountant_total_digest_mismatches: number;
|
|
|
+ global_accountant_transfer_vaas_outstanding: number;
|
|
|
+ global_accountant_transfer_vaas_submitted: number;
|
|
|
+ global_accountant_transfer_vaas_submitted_and_approved: number;
|
|
|
+}> =>
|
|
|
+ (await (await fetch(GUARDIAN_METRICS)).text())
|
|
|
+ .split("\n")
|
|
|
+ .filter((m) => m.startsWith("global_accountant"))
|
|
|
+ .reduce((p, m) => {
|
|
|
+ const [k, v] = m.split(" ");
|
|
|
+ p[k] = Number(v);
|
|
|
+ return p;
|
|
|
+ }, {} as any);
|
|
|
+
|
|
|
+const fetchGlobalAccountantBalance = async (
|
|
|
+ tokenAddress: string,
|
|
|
+ chainId: ChainId,
|
|
|
+ tokenChain: ChainId
|
|
|
+): Promise<BigInt> => {
|
|
|
+ try {
|
|
|
+ return BigInt(
|
|
|
+ await cosmWasmClient.queryContractSmart(GA_ADDRESS, {
|
|
|
+ balance: {
|
|
|
+ token_address: tokenAddress,
|
|
|
+ chain_id: chainId,
|
|
|
+ token_chain: tokenChain,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ );
|
|
|
+ } catch (e) {
|
|
|
+ if (e.message?.includes("accountant::state::account::Balance not found")) {
|
|
|
+ // account not created yet
|
|
|
+ return BigInt(0);
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const fetchGlobalAccountantTransferStatus = async (
|
|
|
+ emitterChain: ChainId,
|
|
|
+ emitterAddress: string,
|
|
|
+ sequence: string
|
|
|
+): Promise<any> => {
|
|
|
+ return await cosmWasmClient.queryContractSmart(GA_ADDRESS, {
|
|
|
+ transfer_status: {
|
|
|
+ emitter_chain: emitterChain,
|
|
|
+ emitter_address: emitterAddress,
|
|
|
+ sequence: Number(sequence),
|
|
|
+ },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+describe("Global Accountant Tests", () => {
|
|
|
+ test("Metrics and Contract Queries", async () => {
|
|
|
+ let attestedAddress = "";
|
|
|
+ //
|
|
|
+ // STEP 0 - attest the token
|
|
|
+ //
|
|
|
+ {
|
|
|
+ attestedAddress = await getForeignAssetEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscProvider,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ tryNativeToUint8Array(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH)
|
|
|
+ );
|
|
|
+ if (attestedAddress && attestedAddress !== ethers.constants.AddressZero) {
|
|
|
+ console.log("already attested");
|
|
|
+ } else {
|
|
|
+ console.log("attesting...");
|
|
|
+ // attest the test token
|
|
|
+ const receipt = await attestFromEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge,
|
|
|
+ ethSigner,
|
|
|
+ ETH_GA_TEST_TOKEN
|
|
|
+ );
|
|
|
+ // get the sequence from the logs (needed to fetch the vaa)
|
|
|
+ const sequence = parseSequenceFromLogEth(
|
|
|
+ receipt,
|
|
|
+ CONTRACTS.DEVNET.ethereum.core
|
|
|
+ );
|
|
|
+ const emitterAddress = getEmitterAddressEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge
|
|
|
+ );
|
|
|
+ console.log(`fetching vaa ${sequence}...`);
|
|
|
+ // poll until the guardian(s) witness and sign the vaa
|
|
|
+ const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
+ GUARDIAN_RPCS,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ emitterAddress,
|
|
|
+ sequence,
|
|
|
+ {
|
|
|
+ transport: NodeHttpTransport(),
|
|
|
+ }
|
|
|
+ );
|
|
|
+ console.log("creating...");
|
|
|
+ await createWrappedOnEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscSigner,
|
|
|
+ signedVAA
|
|
|
+ );
|
|
|
+ attestedAddress = await getForeignAssetEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscProvider,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ tryNativeToUint8Array(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // STEP 1 - send the token out
|
|
|
+ //
|
|
|
+ {
|
|
|
+ const beforeMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ const beforeEthBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ const beforeBscBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ const amount = parseUnits("1", DECIMALS);
|
|
|
+ // approve the bridge to spend tokens
|
|
|
+ console.log("approving...");
|
|
|
+ await approveEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge,
|
|
|
+ ETH_GA_TEST_TOKEN,
|
|
|
+ ethSigner,
|
|
|
+ amount
|
|
|
+ );
|
|
|
+ // transfer tokens out
|
|
|
+ console.log("transferring...");
|
|
|
+ const receipt = await transferFromEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge,
|
|
|
+ ethSigner,
|
|
|
+ ETH_GA_TEST_TOKEN,
|
|
|
+ amount,
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ tryNativeToUint8Array(await bscSigner.getAddress(), CHAIN_ID_BSC)
|
|
|
+ );
|
|
|
+ // get the sequence from the logs (needed to fetch the vaa)
|
|
|
+ const sequence = parseSequenceFromLogEth(
|
|
|
+ receipt,
|
|
|
+ CONTRACTS.DEVNET.ethereum.core
|
|
|
+ );
|
|
|
+ const emitterAddress = getEmitterAddressEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge
|
|
|
+ );
|
|
|
+ console.log(`fetching vaa ${sequence}...`);
|
|
|
+ // poll until the guardian(s) witness and sign the vaa
|
|
|
+ const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
+ GUARDIAN_RPCS,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ emitterAddress,
|
|
|
+ sequence,
|
|
|
+ {
|
|
|
+ transport: NodeHttpTransport(),
|
|
|
+ }
|
|
|
+ );
|
|
|
+ console.log("redeeming...");
|
|
|
+ await redeemOnEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscSigner,
|
|
|
+ signedVAA
|
|
|
+ );
|
|
|
+ const afterMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ console.log(
|
|
|
+ "approved b/a:",
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted_and_approved,
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted_and_approved
|
|
|
+ );
|
|
|
+ if (
|
|
|
+ afterMetrics.global_accountant_events_received <=
|
|
|
+ beforeMetrics.global_accountant_events_received ||
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted <=
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted ||
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted_and_approved <=
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted_and_approved
|
|
|
+ ) {
|
|
|
+ throw new Error("Expected metrics change did not occur");
|
|
|
+ }
|
|
|
+ const parsedVAA = parseTokenTransferVaa(signedVAA);
|
|
|
+ const transferStatus = await fetchGlobalAccountantTransferStatus(
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ emitterAddress,
|
|
|
+ sequence
|
|
|
+ );
|
|
|
+ expect(transferStatus).toMatchObject({
|
|
|
+ committed: {
|
|
|
+ data: {
|
|
|
+ amount: parsedVAA.amount.toString(),
|
|
|
+ token_chain: CHAIN_ID_ETH,
|
|
|
+ token_address: tryNativeToHexString(
|
|
|
+ ETH_GA_TEST_TOKEN,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ ),
|
|
|
+ recipient_chain: CHAIN_ID_BSC,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ const afterEthBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ expect(afterEthBalance).toBeGreaterThan(beforeEthBalance.valueOf());
|
|
|
+ const afterBscBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ expect(afterBscBalance).toBeGreaterThan(beforeBscBalance.valueOf());
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // STEP 2 - send the token back
|
|
|
+ //
|
|
|
+ {
|
|
|
+ const beforeMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ const beforeEthBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ const beforeBscBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ const amount = parseUnits("1", DECIMALS);
|
|
|
+ // approve the bridge to spend tokens
|
|
|
+ console.log("approving...");
|
|
|
+ await approveEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ attestedAddress,
|
|
|
+ bscSigner,
|
|
|
+ amount
|
|
|
+ );
|
|
|
+ // transfer tokens out
|
|
|
+ console.log("transferring...");
|
|
|
+ const receipt = await transferFromEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscSigner,
|
|
|
+ attestedAddress,
|
|
|
+ amount,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ tryNativeToUint8Array(await ethSigner.getAddress(), CHAIN_ID_ETH)
|
|
|
+ );
|
|
|
+ // get the sequence from the logs (needed to fetch the vaa)
|
|
|
+ const sequence = parseSequenceFromLogEth(
|
|
|
+ receipt,
|
|
|
+ CONTRACTS.DEVNET.bsc.core
|
|
|
+ );
|
|
|
+ const emitterAddress = getEmitterAddressEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge
|
|
|
+ );
|
|
|
+ console.log(`fetching vaa ${sequence}...`);
|
|
|
+ // poll until the guardian(s) witness and sign the vaa
|
|
|
+ const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
|
|
+ GUARDIAN_RPCS,
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ emitterAddress,
|
|
|
+ sequence,
|
|
|
+ {
|
|
|
+ transport: NodeHttpTransport(),
|
|
|
+ }
|
|
|
+ );
|
|
|
+ console.log("redeeming...");
|
|
|
+ await redeemOnEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge,
|
|
|
+ ethSigner,
|
|
|
+ signedVAA
|
|
|
+ );
|
|
|
+ const afterMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ console.log(
|
|
|
+ "approved b/a:",
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted_and_approved,
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted_and_approved
|
|
|
+ );
|
|
|
+ if (
|
|
|
+ afterMetrics.global_accountant_events_received <=
|
|
|
+ beforeMetrics.global_accountant_events_received ||
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted <=
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted ||
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted_and_approved <=
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted_and_approved
|
|
|
+ ) {
|
|
|
+ throw new Error("Expected metrics change did not occur");
|
|
|
+ }
|
|
|
+ const parsedVAA = parseTokenTransferVaa(signedVAA);
|
|
|
+ const transferStatus = await fetchGlobalAccountantTransferStatus(
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ emitterAddress,
|
|
|
+ sequence
|
|
|
+ );
|
|
|
+ expect(transferStatus).toMatchObject({
|
|
|
+ committed: {
|
|
|
+ data: {
|
|
|
+ amount: parsedVAA.amount.toString(),
|
|
|
+ token_chain: CHAIN_ID_ETH,
|
|
|
+ token_address: tryNativeToHexString(
|
|
|
+ ETH_GA_TEST_TOKEN,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ ),
|
|
|
+ recipient_chain: CHAIN_ID_ETH,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ const afterEthBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ expect(afterEthBalance).toBeLessThan(beforeEthBalance.valueOf());
|
|
|
+ const afterBscBalance = await fetchGlobalAccountantBalance(
|
|
|
+ tryNativeToHexString(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH),
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ CHAIN_ID_ETH
|
|
|
+ );
|
|
|
+ expect(afterBscBalance).toBeLessThan(beforeBscBalance.valueOf());
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // STEP 3a - redeem spoofed tokens
|
|
|
+ //
|
|
|
+ {
|
|
|
+ console.log("redeeming spoofed tokens");
|
|
|
+ let vaa: VAA<TokenBridgeTransfer> = {
|
|
|
+ version: 1,
|
|
|
+ guardianSetIndex: 0,
|
|
|
+ signatures: [],
|
|
|
+ timestamp: 0,
|
|
|
+ nonce: 0,
|
|
|
+ emitterChain: CHAIN_ID_ETH,
|
|
|
+ emitterAddress: getEmitterAddressEth(
|
|
|
+ CONTRACTS.DEVNET.ethereum.token_bridge
|
|
|
+ ),
|
|
|
+ sequence: BigInt(979999116 + Math.floor(Math.random() * 100000000)),
|
|
|
+ consistencyLevel: 0,
|
|
|
+ payload: {
|
|
|
+ module: "TokenBridge",
|
|
|
+ type: "Transfer",
|
|
|
+ tokenChain: CHAIN_ID_ETH,
|
|
|
+ tokenAddress: uint8ArrayToHex(
|
|
|
+ tryNativeToUint8Array(ETH_GA_TEST_TOKEN, CHAIN_ID_ETH)
|
|
|
+ ),
|
|
|
+ amount: parseUnits("9000", DECIMALS).toBigInt(),
|
|
|
+ toAddress: uint8ArrayToHex(
|
|
|
+ tryNativeToUint8Array(await bscSigner.getAddress(), CHAIN_ID_BSC)
|
|
|
+ ),
|
|
|
+ chain: CHAIN_ID_BSC,
|
|
|
+ fee: BigInt(0),
|
|
|
+ },
|
|
|
+ };
|
|
|
+ vaa.signatures = sign(VAA_SIGNERS, vaa);
|
|
|
+ await redeemOnEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscSigner,
|
|
|
+ hexToUint8Array(serialiseVAA(vaa))
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // STEP 3b - send the spoofed tokens back
|
|
|
+ //
|
|
|
+ {
|
|
|
+ const beforeMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ const amount = parseUnits("9000", DECIMALS);
|
|
|
+ // approve the bridge to spend tokens
|
|
|
+ console.log("approving...");
|
|
|
+ await approveEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ attestedAddress,
|
|
|
+ bscSigner,
|
|
|
+ amount
|
|
|
+ );
|
|
|
+ // transfer tokens out
|
|
|
+ console.log("transferring...");
|
|
|
+ const receipt = await transferFromEth(
|
|
|
+ CONTRACTS.DEVNET.bsc.token_bridge,
|
|
|
+ bscSigner,
|
|
|
+ attestedAddress,
|
|
|
+ amount,
|
|
|
+ CHAIN_ID_ETH,
|
|
|
+ tryNativeToUint8Array(await ethSigner.getAddress(), CHAIN_ID_ETH)
|
|
|
+ );
|
|
|
+ const sequence = parseSequenceFromLogEth(
|
|
|
+ receipt,
|
|
|
+ CONTRACTS.DEVNET.bsc.core
|
|
|
+ );
|
|
|
+ console.log("waiting 30s to fetch metrics...");
|
|
|
+ await sleep(30 * 1000); // give the guardian a few seconds to pick up the transfers and attempt to submit them
|
|
|
+ const afterMetrics = await fetchGlobalAccountantMetrics();
|
|
|
+ console.log(
|
|
|
+ "balance errors b/a:",
|
|
|
+ beforeMetrics.global_accountant_total_balance_errors,
|
|
|
+ afterMetrics.global_accountant_total_balance_errors
|
|
|
+ );
|
|
|
+ if (
|
|
|
+ afterMetrics.global_accountant_error_events_received <=
|
|
|
+ beforeMetrics.global_accountant_error_events_received ||
|
|
|
+ afterMetrics.global_accountant_transfer_vaas_submitted <=
|
|
|
+ beforeMetrics.global_accountant_transfer_vaas_submitted ||
|
|
|
+ afterMetrics.global_accountant_total_balance_errors <=
|
|
|
+ beforeMetrics.global_accountant_total_balance_errors
|
|
|
+ ) {
|
|
|
+ throw new Error("Expected metrics change did not occur");
|
|
|
+ }
|
|
|
+ // the transfer should fail, because there's an insufficient source balance
|
|
|
+ await expect(
|
|
|
+ fetchGlobalAccountantTransferStatus(
|
|
|
+ CHAIN_ID_BSC,
|
|
|
+ getEmitterAddressEth(CONTRACTS.DEVNET.bsc.core),
|
|
|
+ sequence
|
|
|
+ )
|
|
|
+ ).rejects.toThrow();
|
|
|
+ }
|
|
|
+ console.log("success!");
|
|
|
+ });
|
|
|
+});
|