test: accountant e2e contract queries

This commit is contained in:
Kevin Peters 2023-02-03 19:42:41 +00:00 committed by Evan Gray
parent 9aeb704e58
commit 8dcd3f615f
6 changed files with 5824 additions and 376 deletions

View File

@ -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!");
});
});

View File

@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"main": "deploy_wormchain.ts",
"scripts": {
"deploy-wormchain": "ts-node deploy_wormchain.ts",
"test-accountant": "ts-node test_accountant.ts",
"test-accountant": "jest test_accountant.ts --verbose",
"test-wormchain": "ts-node test_wormchain.ts",
"deploy-and-test": "npm run deploy-wormchain && npm run test-wormchain"
},
@ -26,6 +26,9 @@
},
"devDependencies": {
"@types/elliptic": "6.4.14",
"@types/jest": "29.4.0",
"jest": "29.4.1",
"ts-jest": "29.0.5",
"ts-node": "10.9.1",
"typescript": "4.9.4"
}

View File

@ -1,371 +0,0 @@
import "dotenv/config";
import {
approveEth,
attestFromEth,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CONTRACTS,
createWrappedOnEth,
getEmitterAddressEth,
getForeignAssetEth,
getSignedVAAWithRetry,
hexToUint8Array,
parseSequenceFromLogEth,
redeemOnEth,
serialiseVAA,
sign,
TokenBridgeTransfer,
transferFromEth,
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";
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;
}
// TODO: consider using jest
/*
* 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;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 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);
(async () => {
//
// PREAMBLE
//
// create a signer for Eth
const ethProvider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
const ethSigner = new ethers.Wallet(ETH_PRIVATE_KEY9, ethProvider);
// create a signer for BSC
const bscProvider = new ethers.providers.WebSocketProvider(BSC_NODE_URL);
const bscSigner = new ethers.Wallet(ETH_PRIVATE_KEY9, bscProvider);
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 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");
}
}
//
// STEP 2 - send the token back
//
{
const beforeMetrics = await fetchGlobalAccountantMetrics();
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");
}
}
//
// 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)
);
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");
}
}
ethProvider.destroy();
bscProvider.destroy();
console.log("success!");
})();

View File

@ -1,5 +1,6 @@
{
"compilerOptions": {
"resolveJsonModule": true
}
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true
}
}