first k8s deployment w/ relayer engine and redis
This commit is contained in:
parent
4a57a6c371
commit
03f09c9a1a
|
@ -0,0 +1,17 @@
|
|||
FROM node:lts as builder
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src
|
||||
|
||||
# COPY package.json package-lock.json /src/
|
||||
|
||||
# ENV NODE_ENV=production
|
||||
# RUN npm ci
|
||||
|
||||
# Install app dependencies
|
||||
# COPY . /src
|
||||
|
||||
COPY node_modules .
|
||||
COPY . .
|
||||
|
||||
CMD [ "npm", "run", "k8s-testnet" ]
|
|
@ -0,0 +1,4 @@
|
|||
#! /usr/bin/sh
|
||||
|
||||
cp -r ../sdk ./sdk
|
||||
cp ../ethereum/ts-scripts/config/testnet/contracts.json .
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"description": "This file contains the addresses for the contracts on each chain. If useLastRun is true, this file will be ignored, and the addresses will be taken from the lastrun.json of the deployment scripts.",
|
||||
"useLastRun": false,
|
||||
"relayProviders": [
|
||||
{
|
||||
"chainId": 6,
|
||||
"address": "0x302f4D287204b8c383a79BA86Ad1fD1F81fb00E2"
|
||||
},
|
||||
{
|
||||
"chainId": 14,
|
||||
"address": "0x1A7d2aCBa5Ae7ad19e4DA7a512d369CC4aEFe66B"
|
||||
}
|
||||
],
|
||||
"coreRelayers": [
|
||||
{
|
||||
"chainId": 6,
|
||||
"address": "0xDED10060E839c497B8D71C3091f9f24dCe4110cF"
|
||||
},
|
||||
{
|
||||
"chainId": 14,
|
||||
"address": "0xDED10060E839c497B8D71C3091f9f24dCe4110cF"
|
||||
}
|
||||
],
|
||||
"mockIntegrations": [
|
||||
{
|
||||
"chainId": 6,
|
||||
"address": "0x62C4143AB8BEe162eBF6166a679A746cAE1D1385"
|
||||
},
|
||||
{
|
||||
"chainId": 14,
|
||||
"address": "0xbeD6e30Ff857944931F3eF0B26EdC0B616e92d57"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm install --set auth.enabled=false --set architecture=standalone redis bitnami/redis
|
|
@ -0,0 +1,5 @@
|
|||
bash deploy-redis.sh
|
||||
kubectl apply -f ./spy-service.yaml
|
||||
source ../../pkeys.sh
|
||||
bash inject-private-keys.sh
|
||||
kubectl apply -f ./simple-gr.yaml
|
|
@ -0,0 +1,5 @@
|
|||
#! /usr/bin/sh
|
||||
|
||||
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}
|
|
@ -0,0 +1,63 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: simple-gr
|
||||
namespace: default
|
||||
labels:
|
||||
app: simple-gr
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: simple-gr
|
||||
ports:
|
||||
- name: simple-gr
|
||||
protocol: TCP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
# Do we actually need a service for the relayer??
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: simple-gr
|
||||
namespace: default
|
||||
labels:
|
||||
app: simple-gr
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: simple-gr
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: simple-gr
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
- name: simple-gr
|
||||
image: simple-gr:latest
|
||||
imagePullPolicy: Never
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 900m
|
||||
memory: 900Mi
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: simple-gr
|
||||
env:
|
||||
- name: PRIVATE_KEYS_CHAIN_14
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: private-keys
|
||||
key: PRIVATE_KEYS_CHAIN_14
|
||||
optional: false
|
||||
- name: PRIVATE_KEYS_CHAIN_6
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: private-keys
|
||||
key: PRIVATE_KEYS_CHAIN_6
|
||||
optional: false
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: spy
|
||||
namespace: default
|
||||
labels:
|
||||
app: spy
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: spy
|
||||
ports:
|
||||
- port: 7073
|
||||
targetPort: 7073
|
||||
name: spy
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spy
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spy
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 40
|
||||
containers:
|
||||
- name: spy
|
||||
image: ghcr.io/wormhole-foundation/guardiand:latest
|
||||
args:
|
||||
- spy
|
||||
- --nodeKey
|
||||
- /node.key
|
||||
- --spyRPC
|
||||
- "[::]:7073"
|
||||
- --network
|
||||
- /wormhole/testnet/2/1
|
||||
- --bootstrap
|
||||
- /dns4/wormhole-testnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWAkB9ynDur1Jtoa97LBUp8RXdhzS5uHgAfdTquJbrbN7i
|
||||
resources:
|
||||
limits:
|
||||
memory: 256Mi
|
||||
cpu: 500m
|
||||
requests:
|
||||
memory: 128Mi
|
||||
cpu: 250m
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"mode": "BOTH",
|
||||
"logLevel": "debug",
|
||||
"storeType": "InMemory",
|
||||
"readinessPort": 2000,
|
||||
"numGuardians": 1,
|
||||
"supportedChains": [
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"mode": "BOTH",
|
||||
"logLevel": "debug",
|
||||
"storeType": "Redis",
|
||||
"redisPort": 6379,
|
||||
"redisHost": "redis-master.default.svc.cluster.local",
|
||||
"readinessPort": 2000,
|
||||
"numGuardians": 1,
|
||||
"supportedChains": [
|
||||
{
|
||||
"chainId": 6,
|
||||
"chainName": "Avalanche",
|
||||
"nodeUrl": "https://api.avax-test.network/ext/bc/C/rpc"
|
||||
},
|
||||
{
|
||||
"chainId": 14,
|
||||
"chainName": "Celo",
|
||||
"nodeUrl": "https://alfajores-forno.celo-testnet.org"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"privateKeys": {
|
||||
"6": [""],
|
||||
"14": [""]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"spyServiceHost": "spy:7073"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"mode": "BOTH",
|
||||
"storeType": "InMemory",
|
||||
"logLevel": "debug",
|
||||
"readinessPort": 2000,
|
||||
"supportedChains": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,19 +5,20 @@
|
|||
"main": "lib/main.js",
|
||||
"types": "lib/main.d.ts",
|
||||
"scripts": {
|
||||
"testnet": "ts-node src/main --testnet",
|
||||
"testnet-watch": "nodemon src/main --testnet",
|
||||
"tilt": "ts-node src/main --tilt",
|
||||
"mainnet": "ts-node src/main --mainnet",
|
||||
"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",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc",
|
||||
"build": "bash build.sh; 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": "file:../../relayer-engine",
|
||||
"@wormhole-foundation/relayer-engine": "github:wormhole-foundation/relayer-engine#f6491e6e59e905a9c9590cd8b6f62a58f730b4d6",
|
||||
"lodash": "^4.17.21",
|
||||
"ts-retry": "^4.1.1"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
lib
|
||||
node_modules
|
||||
src/ethers-contracts
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "generic-relayer-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "networks.js",
|
||||
"devDependencies": {
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"@poanet/solidity-flattener": "^3.0.8",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"chai": "^4.3.6",
|
||||
"ethers": "^5.7.1",
|
||||
"mocha": "^10.0.0",
|
||||
"ts-mocha": "^10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules src/ethers-contracts",
|
||||
"build": "bash scripts/make_ethers_types.sh",
|
||||
"test": "ts-mocha src/__tests__/*.ts --timeout 60000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.9.6",
|
||||
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"dotenv": "^16.0.2",
|
||||
"elliptic": "^6.5.4",
|
||||
"jsonfile": "^6.1.0",
|
||||
"solc": "^0.8.17",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
SRC=$(dirname $0)/../../ethereum/build
|
||||
DST=$(dirname $0)/../src/ethers-contracts
|
||||
|
||||
typechain --target=ethers-v5 --out-dir=$DST $SRC/*/*.json
|
|
@ -0,0 +1,3 @@
|
|||
lib
|
||||
node_modules
|
||||
src/ethers-contracts
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "generic-relayer-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "networks.js",
|
||||
"devDependencies": {
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"@poanet/solidity-flattener": "^3.0.8",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"chai": "^4.3.6",
|
||||
"ethers": "^5.7.1",
|
||||
"mocha": "^10.0.0",
|
||||
"ts-mocha": "^10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules src/ethers-contracts",
|
||||
"build": "bash scripts/make_ethers_types.sh",
|
||||
"test": "ts-mocha src/__tests__/*.ts --timeout 60000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.9.6",
|
||||
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"dotenv": "^16.0.2",
|
||||
"elliptic": "^6.5.4",
|
||||
"jsonfile": "^6.1.0",
|
||||
"solc": "^0.8.17",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
SRC=$(dirname $0)/../../ethereum/build
|
||||
DST=$(dirname $0)/../src/ethers-contracts
|
||||
|
||||
typechain --target=ethers-v5 --out-dir=$DST $SRC/*/*.json
|
|
@ -0,0 +1,222 @@
|
|||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
// 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)",
|
||||
];
|
|
@ -0,0 +1,24 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
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();
|
|
@ -0,0 +1,8 @@
|
|||
export type { CoreRelayer } from "./ethers-contracts/CoreRelayer"
|
||||
export { CoreRelayer__factory } from "./ethers-contracts/factories/CoreRelayer__factory"
|
||||
export type { MockRelayerIntegration } from "./ethers-contracts/MockRelayerIntegration"
|
||||
export { MockRelayerIntegration__factory } from "./ethers-contracts/factories/MockRelayerIntegration__factory"
|
||||
export type { IWormhole } from "./ethers-contracts/IWormhole"
|
||||
export { IWormhole__factory } from "./ethers-contracts/factories/IWormhole__factory"
|
||||
export type { RelayProvider } from "./ethers-contracts/RelayProvider"
|
||||
export { RelayProvider__factory } from "./ethers-contracts/factories/RelayProvider__factory"
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2020"],
|
||||
"module": "CommonJS",
|
||||
"target": "es2020",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
// 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)",
|
||||
];
|
|
@ -0,0 +1,24 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
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();
|
|
@ -0,0 +1,8 @@
|
|||
export type { CoreRelayer } from "./ethers-contracts/CoreRelayer"
|
||||
export { CoreRelayer__factory } from "./ethers-contracts/factories/CoreRelayer__factory"
|
||||
export type { MockRelayerIntegration } from "./ethers-contracts/MockRelayerIntegration"
|
||||
export { MockRelayerIntegration__factory } from "./ethers-contracts/factories/MockRelayerIntegration__factory"
|
||||
export type { IWormhole } from "./ethers-contracts/IWormhole"
|
||||
export { IWormhole__factory } from "./ethers-contracts/factories/IWormhole__factory"
|
||||
export type { RelayProvider } from "./ethers-contracts/RelayProvider"
|
||||
export { RelayProvider__factory } from "./ethers-contracts/factories/RelayProvider__factory"
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2020"],
|
||||
"module": "CommonJS",
|
||||
"target": "es2020",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
|
@ -9,14 +9,16 @@ async function main() {
|
|||
// load plugin config
|
||||
const envType = selectPluginConfig(process.argv[2] || "")
|
||||
const pluginConfig = (await relayerEngine.loadFileAndParseToObject(
|
||||
`./src/plugin/config/${envType.toLowerCase()}.json`
|
||||
`./src/plugin/config/${envType}.json`
|
||||
)) as GenericRelayerPluginConfig
|
||||
|
||||
|
||||
const contracts = await relayerEngine.loadFileAndParseToObject(
|
||||
`../ethereum/ts-scripts/config/${envType
|
||||
.toLocaleLowerCase()
|
||||
.replace("devnet", "testnet")}/contracts.json`
|
||||
`./contracts.json`
|
||||
)
|
||||
// const contracts = await relayerEngine.loadFileAndParseToObject(
|
||||
// `../ethereum/ts-scripts/config/${envType.replace("devnet", "testnet")}/contracts.json`
|
||||
// )
|
||||
const supportedChains = pluginConfig.supportedChains as unknown as Record<
|
||||
any,
|
||||
ChainInfo
|
||||
|
@ -39,16 +41,18 @@ async function main() {
|
|||
})
|
||||
}
|
||||
|
||||
function selectPluginConfig(flag: string) {
|
||||
function selectPluginConfig(flag: string): string {
|
||||
switch (flag) {
|
||||
case "--testnet":
|
||||
return relayerEngine.EnvType.DEVNET
|
||||
return relayerEngine.EnvType.DEVNET.toLowerCase()
|
||||
case "--mainnet":
|
||||
return relayerEngine.EnvType.MAINNET
|
||||
return relayerEngine.EnvType.MAINNET.toLowerCase()
|
||||
case "--tilt":
|
||||
return relayerEngine.EnvType.TILT
|
||||
return relayerEngine.EnvType.TILT.toLowerCase()
|
||||
case "--k8s-testnet":
|
||||
return "k8s-testnet"
|
||||
default:
|
||||
return relayerEngine.EnvType.TILT
|
||||
return relayerEngine.EnvType.TILT.toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"shouldSpy": true,
|
||||
"shouldRest": false,
|
||||
"logWatcherSleepMs": 300000,
|
||||
"supportedChains": {
|
||||
"6": {
|
||||
"relayerAddress": "0x932848Aed98d8af0f3eA685533966dA4551851db",
|
||||
"mockIntegrationContractAddress": "0xF5C9730B9F8B4D3a352D0cC6358896B1e56E656C"
|
||||
},
|
||||
"14": {
|
||||
"relayerAddress": "0x06ced51D388A66ff3d968818C4ea58e5CC199B0B",
|
||||
"mockIntegrationContractAddress": "0xB7078f7384d4bb353A147a1035990eD6CDAF011E"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,15 +18,14 @@ import {
|
|||
import * as wh from "@certusone/wormhole-sdk"
|
||||
import { Logger } from "winston"
|
||||
import { PluginError } from "./utils"
|
||||
import { logNearGas, parseSequencesFromLogEth, SignedVaa } from "@certusone/wormhole-sdk"
|
||||
import { CoreRelayer__factory, IWormhole, IWormhole__factory } from "../../../../sdk/src"
|
||||
import { SignedVaa } from "@certusone/wormhole-sdk"
|
||||
import { CoreRelayer__factory, IWormhole, IWormhole__factory } from "../../../sdk/src"
|
||||
import * as ethers from "ethers"
|
||||
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
|
||||
import { LogMessagePublishedEvent } from "../../../../sdk/src/ethers-contracts/IWormhole"
|
||||
import { CoreRelayerStructs } from "../../../../sdk/src/ethers-contracts/CoreRelayer"
|
||||
import { LogMessagePublishedEvent } from "../../../sdk/src/ethers-contracts/IWormhole"
|
||||
import { CoreRelayerStructs } from "../../../sdk/src/ethers-contracts/CoreRelayer"
|
||||
import * as _ from "lodash"
|
||||
import * as grpcWebNodeHttpTransport from "@improbable-eng/grpc-web-node-http-transport"
|
||||
import { retryAsync } from "ts-retry"
|
||||
|
||||
const wormholeRpc = "https://wormhole-v2-testnet-api.certus.one"
|
||||
|
||||
|
@ -335,16 +334,16 @@ export class GenericRelayerPlugin implements Plugin<WorkflowPayload> {
|
|||
allFetched: false,
|
||||
}
|
||||
|
||||
// const maybeResolvedEntry = await this.fetchEntry(hash, newEntry, this.logger)
|
||||
// if (maybeResolvedEntry.allFetched) {
|
||||
// this.logger.info("Resolved entry immediately")
|
||||
// return {
|
||||
// workflowData: {
|
||||
// coreRelayerVaaIndex: maybeResolvedEntry.deliveryVaaIdx,
|
||||
// vaas: maybeResolvedEntry.vaas.map((v) => v.bytes),
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
const maybeResolvedEntry = await this.fetchEntry(hash, newEntry, this.logger)
|
||||
if (maybeResolvedEntry.allFetched) {
|
||||
this.logger.info("Resolved entry immediately")
|
||||
return {
|
||||
workflowData: {
|
||||
coreRelayerVaaIndex: maybeResolvedEntry.deliveryVaaIdx,
|
||||
vaas: maybeResolvedEntry.vaas.map((v) => v.bytes),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(`Entry: ${JSON.stringify(newEntry, undefined, 4)}`)
|
||||
await db.withKey(
|
||||
|
|
Loading…
Reference in New Issue