improve deployment

This commit is contained in:
Joe Howarth 2023-01-18 10:42:16 -07:00
parent c119f88024
commit 4cdeca0d34
24 changed files with 131 additions and 661 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
pkeys.sh
pkeys.sh
.vscode/settings.json

View File

@ -7,6 +7,6 @@
"**/.DS_Store": true,
"**/Thumbs.db": true,
"node_modules/": true,
"**/node_modules": true
"**/node_modules": false
}
}

View File

@ -25,7 +25,7 @@ export type Deployment = {
export let env = ""
let lastRunOverride: boolean | undefined
export function init(overrides?: { lastRunOverride?: boolean }): string {
export function init(overrides: { lastRunOverride?: boolean } = {}): string {
env = get_env_var("ENV")
if (!env) {
console.log("No environment was specified, using default environment files")

View File

@ -11,7 +11,6 @@ WORKDIR /usr/src/app
# Install app dependencies
# COPY . /src
COPY ../sdk ../sdk
COPY . .
CMD [ "npm", "run", "k8s-testnet" ]

View File

@ -1,4 +1,4 @@
bash deploy-redis.sh
# bash deploy-redis.sh
kubectl apply -f ./spy-service.yaml
source ../../pkeys.sh
bash inject-private-keys.sh

View File

@ -2,8 +2,10 @@
# typically source ../../pkeys.sh in project root first
# kubectl delete secret private-keys --ignore-not-found
kubectl delete secret private-keys --ignore-not-found
kubectl create secret generic private-keys \
--from-literal=PRIVATE_KEYS_CHAIN_14=${PRIVATE_KEYS_CHAIN_14} \
--from-literal=PRIVATE_KEYS_CHAIN_6=${PRIVATE_KEYS_CHAIN_6}
--from-literal=PRIVATE_KEYS_CHAIN_4=${PRIVATE_KEYS_CHAIN_4} \
--from-literal=PRIVATE_KEYS_CHAIN_5=${PRIVATE_KEYS_CHAIN_5} \
--from-literal=PRIVATE_KEYS_CHAIN_6=${PRIVATE_KEYS_CHAIN_6} \
--from-literal=PRIVATE_KEYS_CHAIN_14=${PRIVATE_KEYS_CHAIN_14}

View File

@ -1,6 +1,6 @@
{
"mode": "BOTH",
"logLevel": "info",
"logLevel": "debug",
"storeType": "InMemory",
"redisPort": 6379,
"redisHost": "redis-master.default.svc.cluster.local",
@ -16,6 +16,16 @@
"chainId": 14,
"chainName": "Celo",
"nodeUrl": "https://alfajores-forno.celo-testnet.org"
},
{
"chainName": "BSC",
"chainId": 4,
"nodeUrl": "https://bsc-testnet.public.blastapi.io"
},
{
"chainName": "Mumbai",
"chainId": 5,
"nodeUrl": "https://matic-mumbai.chainstacklabs.com"
}
]
}

View File

@ -4,31 +4,55 @@
"relayProviders": [
{
"chainId": 6,
"address": "0x302f4D287204b8c383a79BA86Ad1fD1F81fb00E2"
"address": "0xb19eBF87f3b957B6e99d8bACC994c987805C74E2"
},
{
"chainId": 14,
"address": "0x1A7d2aCBa5Ae7ad19e4DA7a512d369CC4aEFe66B"
"address": "0xb4e6BcD5dBbD502443bb4480aA5213DfCC9F1fC8"
},
{
"chainId": 4,
"address": "0x128eE477E3DEC6b97978E7EC41a95C5cBE111c44"
},
{
"chainId": 5,
"address": "0xde609ED85F6EaaD58AA422Fd95Fea213cDA83650"
}
],
"coreRelayers": [
{
"chainId": 6,
"address": "0xDED10060E839c497B8D71C3091f9f24dCe4110cF"
"address": "0x74D59cBFEBAf363769C78E2e2165503228F2b92F"
},
{
"chainId": 14,
"address": "0xDED10060E839c497B8D71C3091f9f24dCe4110cF"
"address": "0x22baa6Ff6454C1B5f514CfE09DFDc77f44a6be56"
},
{
"chainId": 4,
"address": "0x8bCce102F34C6DbF53655958586d7Bc196dCB3c5"
},
{
"chainId": 5,
"address": "0x128eE477E3DEC6b97978E7EC41a95C5cBE111c44"
}
],
"mockIntegrations": [
{
"chainId": 6,
"address": "0x62C4143AB8BEe162eBF6166a679A746cAE1D1385"
"address": "0xbFEA8140309070f0dB56c1970fc9de61970F464E"
},
{
"chainId": 14,
"address": "0xbeD6e30Ff857944931F3eF0B26EdC0B616e92d57"
"address": "0x3Af9D81E5AE37fE3828234E3dBC06c7b2983c7fD"
},
{
"chainId": 4,
"address": "0x26Dc6Fa3a53Ab99187176e0E445065E054ca5CB3"
},
{
"chainId": 5,
"address": "0x660fF93943d6741ECc60a97D695760F13f094628"
}
]
}
}

View File

@ -1,5 +1,7 @@
{
"privateKeys": {
"4": [""],
"5": [""],
"6": [""],
"14": [""]
}

View File

@ -3,13 +3,9 @@
"shouldRest": false,
"logWatcherSleepMs": 300000,
"supportedChains": {
"6": {
"relayerAddress": "0x932848Aed98d8af0f3eA685533966dA4551851db",
"mockIntegrationContractAddress": "0xF5C9730B9F8B4D3a352D0cC6358896B1e56E656C"
},
"14": {
"relayerAddress": "0x06ced51D388A66ff3d968818C4ea58e5CC199B0B",
"mockIntegrationContractAddress": "0xB7078f7384d4bb353A147a1035990eD6CDAF011E"
}
"4": {},
"5": {},
"6": {},
"14": {}
}
}

View File

@ -8,6 +8,9 @@ configMapGenerator:
- k8s-testnet/common.json
- k8s-testnet/executor.json
- k8s-testnet/listener.json
- name: relayer-contracts
files:
- k8s-testnet/contracts.json
- name: generic-relayer-plugin-config
files:
- k8s-testnet/k8s-testnet.json

View File

@ -19,27 +19,36 @@ spec:
containers:
- name: simple-gr
image: joehowarth/simple-gr:latest
imagePullPolicy: IfNotPresent
imagePullPolicy: Always
# uncomment to explore filesystem during crash loop
# command: [ "/bin/sh", "-c", "--" ]
# args: [ "while true; do sleep 30; done;" ]
resources:
requests:
cpu: 1
memory: 100Mi
cpu: 1000m
memory: 600Mi
limits:
cpu: 2
cpu: 1000m
memory: 2000Mi
volumeMounts:
- name: relayer-contracts
mountPath: /usr/src/ethereum/ts-scripts/config/k8s-testnet
- name: relayer-engine-config
mountPath: /usr/src/app/engine_config/k8s-testnet
- name: generic-relayer-plugin-config
mountPath: /usr/src/app/src/plugin/config
env:
- name: PRIVATE_KEYS_CHAIN_14
- name: PRIVATE_KEYS_CHAIN_4
valueFrom:
secretKeyRef:
name: private-keys
key: PRIVATE_KEYS_CHAIN_14
key: PRIVATE_KEYS_CHAIN_4
optional: false
- name: PRIVATE_KEYS_CHAIN_5
valueFrom:
secretKeyRef:
name: private-keys
key: PRIVATE_KEYS_CHAIN_5
optional: false
- name: PRIVATE_KEYS_CHAIN_6
valueFrom:
@ -47,7 +56,16 @@ spec:
name: private-keys
key: PRIVATE_KEYS_CHAIN_6
optional: false
- name: PRIVATE_KEYS_CHAIN_14
valueFrom:
secretKeyRef:
name: private-keys
key: PRIVATE_KEYS_CHAIN_14
optional: false
volumes:
- name: relayer-contracts
configMap:
name: relayer-contracts
- name: relayer-engine-config
configMap:
name: relayer-engine-config

View File

@ -14,6 +14,16 @@
"chainId": 14,
"chainName": "Celo",
"nodeUrl": "https://alfajores-forno.celo-testnet.org"
},
{
"chainName": "BSC",
"chainId": 4,
"nodeUrl": "https://bsc-testnet.public.blastapi.io"
},
{
"chainName": "Mumbai",
"chainId": 5,
"nodeUrl": "https://matic-mumbai.chainstacklabs.com"
}
]
}

View File

@ -1,5 +1,7 @@
{
"privateKeys": {
"4": [""],
"5": [""],
"6": [""],
"14": [""]
}

View File

@ -10,9 +10,7 @@
"license": "ISC",
"dependencies": {
"@certusone/wormhole-sdk": "^0.9.6",
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"@wormhole-foundation/relayer-engine": "github:wormhole-foundation/relayer-engine#f6491e6e59e905a9c9590cd8b6f62a58f730b4d6",
"lodash": "^4.17.21",
"ts-retry": "^4.1.1"
},
"devDependencies": {

View File

@ -6,20 +6,18 @@
"types": "lib/main.d.ts",
"scripts": {
"k8s-testnet": "ts-node src/main --k8s-testnet",
"testnet": "bash build.sh; ts-node src/main --testnet",
"testnet-watch": "bash build.sh; nodemon src/main --testnet",
"tilt": "bash build.sh; ts-node src/main --tilt",
"mainnet": "bash build.sh; ts-node src/main --mainnet",
"testnet": "ts-node src/main --testnet",
"testnet-watch": "nodemon src/main --testnet",
"tilt": "ts-node src/main --tilt",
"mainnet": "ts-node src/main --mainnet",
"typecheck": "tsc --noEmit",
"build": "bash build.sh; tsc",
"build": "tsc",
"watch": "tsc --watch",
"start": "ts-node src/main.ts"
},
"dependencies": {
"@certusone/wormhole-sdk": "^0.9.6",
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"@wormhole-foundation/relayer-engine": "github:wormhole-foundation/relayer-engine#f6491e6e59e905a9c9590cd8b6f62a58f730b4d6",
"lodash": "^4.17.21",
"ts-retry": "^4.1.1"
},
"author": "Chase Moran",

2
sdk/.gitignore vendored
View File

@ -1,3 +1,3 @@
lib
node_modules
src/ethers-contracts
src/ethers-contracts

View File

@ -2,7 +2,8 @@
"name": "generic-relayer-sdk",
"version": "1.0.0",
"description": "",
"main": "networks.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"devDependencies": {
"@openzeppelin/contracts": "^4.7.3",
"@poanet/solidity-flattener": "^3.0.8",
@ -15,6 +16,8 @@
},
"scripts": {
"clean": "rm -rf node_modules src/ethers-contracts",
"typecheck": "tsc --noEmit",
"tsc": "tsc",
"build": "bash scripts/make_ethers_types.sh",
"test": "ts-mocha src/__tests__/*.ts --timeout 60000"
},

View File

@ -1,222 +0,0 @@
import { expect } from "chai";
import { ethers } from "ethers";
import {
BSC_FORGE_BROADCAST,
BSC_RPC,
WORMHOLE_RPCS,
ETH_FORGE_BROADCAST,
ETH_RPC,
DEPLOYER_PRIVATE_KEY,
ZERO_ADDRESS_BYTES,
TARGET_GAS_LIMIT,
} from "./helpers/consts";
import { RelayerArgs } from "./helpers/structs";
import {
makeCoreRelayerFromForgeBroadcast,
makeGasOracleFromForgeBroadcast,
makeMockRelayerIntegrationFromForgeBroadcast,
resolvePath,
} from "./helpers/utils";
import {
CHAIN_ID_BSC,
CHAIN_ID_ETH,
getSignedBatchVAAWithRetry,
tryNativeToUint8Array,
tryNativeToHexString,
} from "@certusone/wormhole-sdk";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
describe("ETH <> BSC Generic Relayer Integration Test", () => {
const ethProvider = new ethers.providers.StaticJsonRpcProvider(ETH_RPC);
const bscProvider = new ethers.providers.StaticJsonRpcProvider(BSC_RPC);
// core relayers
const ethCoreRelayer = makeCoreRelayerFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
);
const bscCoreRelayer = makeCoreRelayerFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
);
// relayer integrators
const ethRelayerIntegrator = makeMockRelayerIntegrationFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
);
const bscRelayerIntegrator = makeMockRelayerIntegrationFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
);
// gas oracles
const ownedGasOracles = [
// eth
makeGasOracleFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
),
// bsc
makeGasOracleFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
),
];
const readonlyGasOracles = [
// eth
makeGasOracleFromForgeBroadcast(resolvePath(ETH_FORGE_BROADCAST), ethProvider),
// bsc
makeGasOracleFromForgeBroadcast(resolvePath(BSC_FORGE_BROADCAST), bscProvider),
];
const ethPrice = ethers.utils.parseUnits("2000.00", 8);
const bscPrice = ethers.utils.parseUnits("400.00", 8);
before("Setup Gas Oracle Prices And Register Relayer Contracts", async () => {
// now fetch gas prices from each provider
const gasPrices = await Promise.all(ownedGasOracles.map((oracle) => oracle.provider.getGasPrice()));
const updates = [
{
chainId: CHAIN_ID_ETH,
gasPrice: gasPrices.at(0)!,
nativeCurrencyPrice: ethPrice,
},
{
chainId: CHAIN_ID_BSC,
gasPrice: gasPrices.at(1)!,
nativeCurrencyPrice: bscPrice,
},
];
const oracleTxs = await Promise.all(
ownedGasOracles.map((oracle) => oracle.updatePrices(updates).then((tx: ethers.ContractTransaction) => tx.wait()))
);
// query the core relayer contracts to see if relayers have been registered
const registeredCoreRelayerOnBsc = await bscCoreRelayer.registeredRelayer(CHAIN_ID_ETH);
const registeredCoreRelayerOnEth = await ethCoreRelayer.registeredRelayer(CHAIN_ID_BSC);
// register the core relayer contracts
if (registeredCoreRelayerOnBsc == ZERO_ADDRESS_BYTES) {
await bscCoreRelayer
.registerChain(CHAIN_ID_ETH, tryNativeToUint8Array(ethCoreRelayer.address, CHAIN_ID_ETH))
.then((tx) => tx.wait());
}
if (registeredCoreRelayerOnEth == ZERO_ADDRESS_BYTES) {
await ethCoreRelayer
.registerChain(CHAIN_ID_BSC, tryNativeToUint8Array(bscCoreRelayer.address, CHAIN_ID_BSC))
.then((tx) => tx.wait());
}
});
describe("Send from Ethereum and Deliver to BSC", () => {
// batch Vaa payloads to relay to the target contract
let batchVaaPayloads: ethers.utils.BytesLike[] = [];
// save the batch VAA info
let batchToBscReceipt: ethers.ContractReceipt;
let batchVaaFromEth: ethers.utils.BytesLike;
it("Check Gas Oracles", async () => {
const chainIds = await Promise.all(readonlyGasOracles.map((oracle) => oracle.chainId()));
expect(chainIds.at(0)).is.not.undefined;
expect(chainIds.at(0)!).to.equal(CHAIN_ID_ETH);
expect(chainIds.at(1)).is.not.undefined;
expect(chainIds.at(1)!).to.equal(CHAIN_ID_BSC);
const ethPrices = await Promise.all(readonlyGasOracles.map((oracle) => oracle.gasPrice(CHAIN_ID_ETH)));
const bscPrices = await Promise.all(readonlyGasOracles.map((oracle) => oracle.gasPrice(CHAIN_ID_BSC)));
for (let i = 0; i < 2; ++i) {
expect(ethPrices.at(i)).is.not.undefined;
expect(ethPrices.at(i)?.toString()).to.equal("20000000000");
expect(bscPrices.at(i)).is.not.undefined;
expect(bscPrices.at(i)?.toString()).to.equal("20000000000");
}
});
it("Generate batch VAA with delivery instructions on Ethereum", async () => {
// estimate the relayer cost to relay a batch to BSC
const estimatedGasCost = await ethRelayerIntegrator.estimateRelayCosts(CHAIN_ID_BSC, TARGET_GAS_LIMIT);
// create an array of messages to deliver to the BSC target contract
batchVaaPayloads = [
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff0")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff1")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff2")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff3")),
];
const batchVaaConsistencyLevels = [15, 15, 15, 15];
// create relayerArgs interface to call the mock integration contract with
const relayerArgs: RelayerArgs = {
nonce: 69,
targetChainId: CHAIN_ID_BSC,
targetAddress: bscRelayerIntegrator.address,
targetGasLimit: TARGET_GAS_LIMIT,
consistencyLevel: batchVaaConsistencyLevels[0],
};
// call the mock integration contract and send the batch VAA
batchToBscReceipt = await ethRelayerIntegrator
.sendBatchToTargetChain(batchVaaPayloads, batchVaaConsistencyLevels, relayerArgs, {
value: estimatedGasCost,
})
.then((tx) => tx.wait());
});
it("Fetch batch VAA from Ethereum", async () => {
// fetch the batch VAA with getSignedBatchVAAWithRetry
const batchVaaRes = await getSignedBatchVAAWithRetry(
WORMHOLE_RPCS,
CHAIN_ID_ETH,
batchToBscReceipt.transactionHash,
{
transport: NodeHttpTransport(),
}
);
batchVaaFromEth = batchVaaRes.batchVaaBytes;
});
it("Wait for off-chain relayer to deliver the batch VAA to BSC", async () => {
// parse the batch VAA
const parsedBatch = await ethRelayerIntegrator.parseWormholeBatch(batchVaaFromEth);
// Check to see if the batch VAA was delivered by querying the contract
// for the first payload sent in the batch.
let isBatchDelivered: boolean = false;
const targetVm3 = await ethRelayerIntegrator.parseWormholeObservation(parsedBatch.observations[0]);
while (!isBatchDelivered) {
// query the contract to see if the batch was delivered
const storedPayload = await bscRelayerIntegrator.getPayload(targetVm3.hash);
if (storedPayload == targetVm3.payload) {
isBatchDelivered = true;
}
}
// confirm that the remaining payloads are stored in the contract
for (const observation of parsedBatch.observations) {
const vm3 = await bscRelayerIntegrator.parseWormholeObservation(observation);
// skip delivery instructions VM
if (vm3.emitterAddress == "0x" + tryNativeToHexString(ethCoreRelayer.address, CHAIN_ID_ETH)) {
continue;
}
// query the contract to see if the batch was delivered
const storedPayload = await bscRelayerIntegrator.getPayload(vm3.hash);
expect(storedPayload).to.equal(vm3.payload);
// clear the payload from the mock integration contract
await bscRelayerIntegrator.clearPayload(vm3.hash);
const emptyStoredPayload = await bscRelayerIntegrator.getPayload(vm3.hash);
expect(emptyStoredPayload).to.equal("0x");
}
});
});
});

View File

@ -1,27 +0,0 @@
// rpc
export const ETH_RPC = "http://localhost:8545";
export const BSC_RPC = "http://localhost:8546";
export const WORMHOLE_RPCS = ["http://localhost:7071"];
export const ETH_EVM_CHAINID = 1337;
export const BSC_EVM_CHAINID = 1397;
// evm wallets
export const DEPLOYER_PRIVATE_KEY = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; // account 0
export const EVM_PRIVATE_KEY = "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c"; // account 2
// io
export const ETHEREUM_ROOT = `${__dirname}/../../../../ethereum`; // holy parent directories, batman
export const ETH_FORGE_BROADCAST = `${ETHEREUM_ROOT}/broadcast/deploy_contracts.sol/${ETH_EVM_CHAINID}/run-latest.json`;
export const BSC_FORGE_BROADCAST = `${ETHEREUM_ROOT}/broadcast/deploy_contracts.sol/${BSC_EVM_CHAINID}/run-latest.json`;
// misc
export const ZERO_ADDRESS_BYTES = "0x0000000000000000000000000000000000000000000000000000000000000000";
// the amount of gas that the target relayer contract will invoke the wormhole receiver with
export const TARGET_GAS_LIMIT = 500000; // evm gas units
// wormhole event ABIs
export const WORMHOLE_MESSAGE_EVENT_ABI = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)",
];

View File

@ -1,24 +0,0 @@
import { ethers } from "ethers";
export interface RelayerArgs {
nonce: number;
targetChainId: number;
targetAddress: string;
targetGasLimit: number;
consistencyLevel: number;
}
export interface TargetDeliveryParameters {
encodedVM: ethers.utils.BytesLike;
deliveryIndex: number;
targetCallGasOverride: ethers.BigNumber;
}
export interface DeliveryStatus {
payloadId: number;
batchHash: ethers.utils.BytesLike;
emitterAddress: ethers.utils.BytesLike;
sequence: number;
deliveryCount: number;
deliverySuccess: number;
}

View File

@ -1,136 +0,0 @@
import {ethers} from "ethers";
import fs from "fs";
import path from "path";
import {DeliveryStatus} from "./structs";
import {WORMHOLE_RPCS, WORMHOLE_MESSAGE_EVENT_ABI} from "./consts";
import {NodeHttpTransport} from "@improbable-eng/grpc-web-node-http-transport";
import {ChainId, getEmitterAddressEth, getSignedVAAWithRetry} from "@certusone/wormhole-sdk";
import {
GasOracle,
GasOracle__factory,
MockRelayerIntegration,
MockRelayerIntegration__factory,
CoreRelayer,
CoreRelayer__factory,
} from "../../";
export function makeGasOracleFromForgeBroadcast(
broadcastPath: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
): GasOracle {
const address = getContractAddressFromForgeBroadcast(broadcastPath, "GasOracle");
return GasOracle__factory.connect(address, signerOrProvider);
}
export function makeMockRelayerIntegrationFromForgeBroadcast(
broadcastPath: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
): MockRelayerIntegration {
const address = getContractAddressFromForgeBroadcast(broadcastPath, "MockRelayerIntegration");
return MockRelayerIntegration__factory.connect(address, signerOrProvider);
}
export function makeCoreRelayerFromForgeBroadcast(
broadcastPath: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
): CoreRelayer {
const address = getContractAddressFromForgeBroadcast(broadcastPath, "ERC1967Proxy");
return CoreRelayer__factory.connect(address, signerOrProvider);
}
function readForgeBroadcast(broadcastPath: string): any {
if (!fs.existsSync(broadcastPath)) {
throw new Error("broadcastPath does not exist");
}
return JSON.parse(fs.readFileSync(broadcastPath, "utf8"));
}
function getContractAddressFromForgeBroadcast(broadcastPath: string, contractName: string) {
const transactions: any[] = readForgeBroadcast(broadcastPath).transactions;
const result = transactions.find((tx) => tx.contractName == contractName && tx.transactionType == "CREATE");
if (result == undefined) {
throw new Error("transaction.find == undefined");
}
return result.contractAddress;
}
export function resolvePath(fp: string) {
return path.resolve(fp);
}
export async function parseWormholeEventsFromReceipt(
receipt: ethers.ContractReceipt
): Promise<ethers.utils.LogDescription[]> {
// create the wormhole message interface
const wormholeMessageInterface = new ethers.utils.Interface(WORMHOLE_MESSAGE_EVENT_ABI);
// loop through the logs and parse the events that were emitted
const logDescriptions: ethers.utils.LogDescription[] = await Promise.all(
receipt.logs.map(async (log) => {
return wormholeMessageInterface.parseLog(log);
})
);
return logDescriptions;
}
export async function getSignedVaaFromReceiptOnEth(
receipt: ethers.ContractReceipt,
emitterChainId: ChainId,
contractAddress: ethers.BytesLike
): Promise<Uint8Array> {
const messageEvents = await parseWormholeEventsFromReceipt(receipt);
// grab the sequence from the parsed message log
if (messageEvents.length !== 1) {
throw Error("more than one message found in log");
}
const sequence = messageEvents[0].args.sequence;
// fetch the signed VAA
const result = await getSignedVAAWithRetry(
WORMHOLE_RPCS,
emitterChainId,
getEmitterAddressEth(contractAddress),
sequence.toString(),
{
transport: NodeHttpTransport(),
}
);
return result.vaaBytes;
}
export function parseDeliveryStatusVaa(payload: ethers.BytesLike): DeliveryStatus {
// confirm that the payload is formatted correctly
let index: number = 0;
// interface that we will parse the bytes into
let deliveryStatus: DeliveryStatus = {} as DeliveryStatus;
// grab the payloadID = 2
deliveryStatus.payloadId = parseInt(ethers.utils.hexDataSlice(payload, index, index + 1));
index += 1;
// delivery batch hash
deliveryStatus.batchHash = ethers.utils.hexDataSlice(payload, index, index + 32);
index += 32;
// deliveryId emitter address
deliveryStatus.emitterAddress = ethers.utils.hexDataSlice(payload, index, index + 32);
index += 32;
// deliveryId sequence
deliveryStatus.sequence = parseInt(ethers.utils.hexDataSlice(payload, index, index + 8));
index += 8;
// delivery count
deliveryStatus.deliveryCount = parseInt(ethers.utils.hexDataSlice(payload, index, index + 2));
index += 2;
// grab the success boolean
deliveryStatus.deliverySuccess = parseInt(ethers.utils.hexDataSlice(payload, index, index + 1));
index += 1;
return deliveryStatus;
}

View File

@ -1,203 +0,0 @@
import { expect } from "chai";
import { ethers } from "ethers";
import {
BSC_FORGE_BROADCAST,
BSC_RPC,
WORMHOLE_RPCS,
ETH_FORGE_BROADCAST,
ETH_RPC,
DEPLOYER_PRIVATE_KEY,
ZERO_ADDRESS_BYTES,
TARGET_GAS_LIMIT,
} from "../__tests__/helpers/consts";
import { DeliveryStatus, RelayerArgs, TargetDeliveryParameters } from "../__tests__/helpers/structs";
import {
makeCoreRelayerFromForgeBroadcast,
makeGasOracleFromForgeBroadcast,
makeMockRelayerIntegrationFromForgeBroadcast,
resolvePath,
getSignedVaaFromReceiptOnEth,
parseDeliveryStatusVaa,
} from "../__tests__/helpers/utils";
import { CHAIN_ID_BSC, CHAIN_ID_ETH, tryNativeToHexString, getSignedBatchVAAWithRetry } from "@certusone/wormhole-sdk";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
async function main() {
const ethProvider = new ethers.providers.StaticJsonRpcProvider(ETH_RPC);
const bscProvider = new ethers.providers.StaticJsonRpcProvider(BSC_RPC);
// core relayers
const ethCoreRelayer = makeCoreRelayerFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
);
const bscCoreRelayer = makeCoreRelayerFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
);
// relayer integrators
const ethRelayerIntegrator = makeMockRelayerIntegrationFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
);
const bscRelayerIntegrator = makeMockRelayerIntegrationFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
);
// gas oracles
const ownedGasOracles = [
// eth
makeGasOracleFromForgeBroadcast(
resolvePath(ETH_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, ethProvider)
),
// bsc
makeGasOracleFromForgeBroadcast(
resolvePath(BSC_FORGE_BROADCAST),
new ethers.Wallet(DEPLOYER_PRIVATE_KEY, bscProvider)
),
];
// setup gas oracles and register if needed
{
const ethPrice = ethers.utils.parseUnits("2000.00", 8);
const bscPrice = ethers.utils.parseUnits("400.00", 8);
// now fetch gas prices from each provider
const gasPrices = await Promise.all(ownedGasOracles.map((oracle) => oracle.provider.getGasPrice()));
const updates = [
{
chainId: CHAIN_ID_ETH,
gasPrice: gasPrices.at(0)!,
nativeCurrencyPrice: ethPrice,
},
{
chainId: CHAIN_ID_BSC,
gasPrice: gasPrices.at(1)!,
nativeCurrencyPrice: bscPrice,
},
];
const oracleTxs = await Promise.all(
ownedGasOracles.map((oracle) => oracle.updatePrices(updates).then((tx: ethers.ContractTransaction) => tx.wait()))
);
// query the core relayer contracts to see if relayers have been registered
const registeredCoreRelayerOnBsc = await bscCoreRelayer.registeredRelayer(CHAIN_ID_ETH);
const registeredCoreRelayerOnEth = await ethCoreRelayer.registeredRelayer(CHAIN_ID_BSC);
// register the core relayer contracts
if (registeredCoreRelayerOnBsc == ZERO_ADDRESS_BYTES) {
const bscRegistrationTx = await bscCoreRelayer.registerChain(
CHAIN_ID_ETH,
"0x" + tryNativeToHexString(ethCoreRelayer.address, CHAIN_ID_ETH)
);
await bscRegistrationTx.wait();
}
if (registeredCoreRelayerOnEth == ZERO_ADDRESS_BYTES) {
const ethRegistrationTx = await ethCoreRelayer.registerChain(
CHAIN_ID_BSC,
"0x" + tryNativeToHexString(bscCoreRelayer.address, CHAIN_ID_BSC)
);
await ethRegistrationTx.wait();
}
// Query the mock relayer integration contracts to see if trusted mock relayer
// integration contracts have been registered.
const trustedSenderOnBsc = await bscRelayerIntegrator.trustedSender(CHAIN_ID_ETH);
const trustedSenderOnEth = await ethRelayerIntegrator.trustedSender(CHAIN_ID_BSC);
// register the trusted mock relayer integration contracts
if (trustedSenderOnBsc == ZERO_ADDRESS_BYTES) {
const bscRegistrationTx = await bscRelayerIntegrator.registerTrustedSender(
CHAIN_ID_ETH,
"0x" + tryNativeToHexString(ethRelayerIntegrator.address, CHAIN_ID_ETH)
);
await bscRegistrationTx.wait();
}
if (trustedSenderOnEth == ZERO_ADDRESS_BYTES) {
const ethRegistrationTx = await ethRelayerIntegrator.registerTrustedSender(
CHAIN_ID_BSC,
"0x" + tryNativeToHexString(bscRelayerIntegrator.address, CHAIN_ID_BSC)
);
await ethRegistrationTx.wait();
}
}
{
// batch Vaa payloads to relay to the target contract
let batchVaaPayloads: ethers.utils.BytesLike[] = [];
// REVIEW: these should be removed when the off-chain relayer is implemented
let batchToBscReceipt: ethers.ContractReceipt;
let targetDeliveryParamsOnBsc: TargetDeliveryParameters = {} as TargetDeliveryParameters;
{
// estimate the relayer cost to relay a batch to BSC
const estimatedGasCost: ethers.BigNumber = await ethRelayerIntegrator.estimateRelayCosts(
CHAIN_ID_BSC,
TARGET_GAS_LIMIT
);
// create an array of messages to deliver to the BSC target contract
batchVaaPayloads = [
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff0")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff1")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff2")),
ethers.utils.hexlify(ethers.utils.toUtf8Bytes("SuperCoolCrossChainStuff3")),
];
const batchVaaConsistencyLevels = [15, 15, 15, 15];
// create relayerArgs interface to call the mock integration contract with
const relayerArgs: RelayerArgs = {
nonce: 69,
targetChainId: CHAIN_ID_BSC,
targetAddress: bscRelayerIntegrator.address,
targetGasLimit: TARGET_GAS_LIMIT,
consistencyLevel: batchVaaConsistencyLevels[0],
deliveryListIndices: [] as number[], // no indices specified for full batch delivery
};
// call the mock integration contract and send the batch VAA
const tx = await ethRelayerIntegrator.sendBatchToTargetChain(
batchVaaPayloads,
batchVaaConsistencyLevels,
relayerArgs,
{
value: estimatedGasCost,
}
);
batchToBscReceipt = await tx.wait();
console.log("emitterChain", CHAIN_ID_ETH, "emitterAddress", ethCoreRelayer.address);
console.log("transaction", batchToBscReceipt.transactionHash);
}
{
// fetch the batch VAA with getSignedBatchVAAWithRetry
const batchVaaRes = await getSignedBatchVAAWithRetry(
WORMHOLE_RPCS,
CHAIN_ID_ETH,
batchToBscReceipt.transactionHash,
{
transport: NodeHttpTransport(),
}
);
const batchVaaFromEth: ethers.utils.BytesLike = batchVaaRes.batchVaaBytes;
console.log("vaa", Buffer.from(batchVaaFromEth as Uint8Array).toString("hex"));
// parse the batch VAA
const parsedBatch = await ethRelayerIntegrator.parseBatchVM(batchVaaFromEth);
console.log("parsed", parsedBatch);
}
}
}
main();

View File

@ -1,13 +1,29 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2020"],
"module": "CommonJS",
"target": "es2020",
"types": [
"mocha",
"chai"
],
"typeRoots": [
"./node_modules/@types"
],
"outDir": "lib",
"esModuleInterop": true,
"target": "esnext",
"module": "CommonJS",
"moduleResolution": "node",
"lib": [
"ESNext"
],
"declaration": true,
"skipLibCheck": true,
"allowJs": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"isolatedModules": true,
"resolveJsonModule": true,
"moduleResolution": "node"
}
}
"downlevelIteration": true,
"sourceMap": true
},
}