Relayer: delivery helpers in ts sdk (#3107)

* basic delivery helpers done

* reorganize

* sdk: include manual delivery in relayer tests

* fix test

* fix

* SDK fixes

---------

Co-authored-by: derpy-duck <115193320+derpy-duck@users.noreply.github.com>
This commit is contained in:
Joe Howarth 2023-06-22 09:51:22 -05:00 committed by GitHub
parent 55a3ba684e
commit a1e9d15187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1226 additions and 767 deletions

View File

@ -1,8 +1,26 @@
import { afterAll, beforeEach, describe, expect, jest, test} from "@jest/globals";
import { ethers } from "ethers";
import { getNetwork, isCI, generateRandomString, waitForRelay, PRIVATE_KEY, getGuardianRPC, GUARDIAN_KEYS, GUARDIAN_SET_INDEX, GOVERNANCE_EMITTER_ADDRESS, getArbitraryBytes32} from "./utils/utils";
import {getAddressInfo} from "../consts"
import {getDefaultProvider} from "../relayer/helpers"
import {
afterAll,
beforeEach,
describe,
expect,
jest,
test,
} from "@jest/globals";
import { ContractReceipt, ethers } from "ethers";
import {
getNetwork,
isCI,
generateRandomString,
waitForRelay,
PRIVATE_KEY,
getGuardianRPC,
GUARDIAN_KEYS,
GUARDIAN_SET_INDEX,
GOVERNANCE_EMITTER_ADDRESS,
getArbitraryBytes32,
} from "./utils/utils";
import { getAddressInfo } from "../consts";
import { getDefaultProvider, getVAA } from "../relayer/helpers";
import {
relayer,
ethers_contracts,
@ -13,42 +31,55 @@ import {
CHAIN_ID_TO_NAME,
ChainName,
Network,
parseSequencesFromLogEth,
} from "../../../";
import { GovernanceEmitter, MockGuardians } from "../../../src/mock";
import { AddressInfo } from "net";
import {
Bridge__factory,
Implementation__factory,
} from "../../ethers-contracts";
import { Wormhole__factory } from "../../../lib/cjs/ethers-contracts";
import { getEmitterAddressEth } from "../../bridge";
import { deliver } from "../relayer";
import { env } from "process";
const network: Network = getNetwork();
const ci: boolean = isCI();
const sourceChain = network == 'DEVNET' ? "ethereum" : "avalanche";
const targetChain = network == 'DEVNET' ? "bsc" : "celo";
const sourceChain = network == "DEVNET" ? "ethereum" : "avalanche";
const targetChain = network == "DEVNET" ? "bsc" : "celo";
type TestChain = {
chainId: ChainId,
name: ChainName,
provider: ethers.providers.Provider,
wallet: ethers.Wallet,
wormholeRelayerAddress: string,
mockIntegrationAddress: string,
wormholeRelayer: ethers_contracts.WormholeRelayer,
mockIntegration: ethers_contracts.MockRelayerIntegration
}
chainId: ChainId;
name: ChainName;
provider: ethers.providers.Provider;
wallet: ethers.Wallet;
wormholeRelayerAddress: string;
mockIntegrationAddress: string;
wormholeRelayer: ethers_contracts.WormholeRelayer;
mockIntegration: ethers_contracts.MockRelayerIntegration;
};
const createTestChain = (name: ChainName) => {
const provider = getDefaultProvider(network, name, ci);
const addressInfo = getAddressInfo(name, network);
if (process.env.DEV) {
// Via ir is off -> different wormhole relayer address
addressInfo.wormholeRelayerAddress = "0x53855d4b64E9A3CF59A84bc768adA716B5536BC5"
addressInfo.wormholeRelayerAddress =
"0x53855d4b64E9A3CF59A84bc768adA716B5536BC5";
}
if(!addressInfo.wormholeRelayerAddress) throw Error(`No core relayer address for ${name}`);
if(!addressInfo.mockIntegrationAddress) throw Error(`No mock relayer integration address for ${name}`);
if (!addressInfo.wormholeRelayerAddress)
throw Error(`No core relayer address for ${name}`);
if (!addressInfo.mockIntegrationAddress)
throw Error(`No mock relayer integration address for ${name}`);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const wormholeRelayer = ethers_contracts.WormholeRelayer__factory.connect(
addressInfo.wormholeRelayerAddress,
wallet
);
const mockIntegration = ethers_contracts.MockRelayerIntegration__factory.connect(
const mockIntegration =
ethers_contracts.MockRelayerIntegration__factory.connect(
addressInfo.mockIntegrationAddress,
wallet
);
@ -60,10 +91,10 @@ import { AddressInfo } from "net";
wormholeRelayerAddress: addressInfo.wormholeRelayerAddress,
mockIntegrationAddress: addressInfo.mockIntegrationAddress,
wormholeRelayer,
mockIntegration
}
mockIntegration,
};
return result;
}
};
const source = createTestChain(sourceChain);
const target = createTestChain(targetChain);
@ -71,16 +102,17 @@ import { AddressInfo } from "net";
const myMap = new Map<ChainName, ethers.providers.Provider>();
myMap.set(sourceChain, source.provider);
myMap.set(targetChain, target.provider);
const optionalParams = {environment: network, sourceChainProvider: source.provider, targetChainProviders: myMap};
const optionalParams = {
environment: network,
sourceChainProvider: source.provider,
targetChainProviders: myMap,
};
// for signing wormhole messages
const guardians = new MockGuardians(GUARDIAN_SET_INDEX, GUARDIAN_KEYS);
// for generating governance wormhole messages
const governance = new GovernanceEmitter(
GOVERNANCE_EMITTER_ADDRESS
);
const governance = new GovernanceEmitter(GOVERNANCE_EMITTER_ADDRESS);
const guardianIndices = ci ? [0, 1] : [0];
@ -88,17 +120,33 @@ const REASONABLE_GAS_LIMIT = 500000;
const TOO_LOW_GAS_LIMIT = 10000;
const REASONABLE_GAS_LIMIT_FORWARDS = 900000;
const getStatus = async (txHash: string, _sourceChain?: ChainName): Promise<string> => {
const getStatus = async (
txHash: string,
_sourceChain?: ChainName
): Promise<string> => {
const info = (await relayer.getWormholeRelayerInfo(
_sourceChain || sourceChain,
txHash,
{environment: network, targetChainProviders: myMap, sourceChainProvider: myMap.get(_sourceChain || sourceChain)}
{
environment: network,
targetChainProviders: myMap,
sourceChainProvider: myMap.get(_sourceChain || sourceChain),
}
)) as relayer.DeliveryInfo;
return info.targetChainStatus.events[0].status;
}
};
const testSend = async (payload: string, sendToSourceChain?: boolean, notEnoughValue?: boolean): Promise<string> => {
const value = await relayer.getPrice(sourceChain, sendToSourceChain ? sourceChain : targetChain, notEnoughValue ? TOO_LOW_GAS_LIMIT : REASONABLE_GAS_LIMIT, optionalParams);
const testSend = async (
payload: string,
sendToSourceChain?: boolean,
notEnoughValue?: boolean
): Promise<ContractReceipt> => {
const value = await relayer.getPrice(
sourceChain,
sendToSourceChain ? sourceChain : targetChain,
notEnoughValue ? TOO_LOW_GAS_LIMIT : REASONABLE_GAS_LIMIT,
optionalParams
);
console.log(`Quoted gas delivery fee: ${value}`);
const tx = await source.mockIntegration.sendMessage(
payload,
@ -111,15 +159,31 @@ const testSend = async (payload: string, sendToSourceChain?: boolean, notEnoughV
await tx.wait();
console.log("Message confirmed!");
return tx.hash;
}
return tx.wait();
};
const testForward = async (payload1: string, payload2: string, notEnoughExtraForwardingValue?: boolean): Promise<string> => {
const valueNeededOnTargetChain = await relayer.getPrice(targetChain, sourceChain, notEnoughExtraForwardingValue ? TOO_LOW_GAS_LIMIT : REASONABLE_GAS_LIMIT, optionalParams);
const value = await relayer.getPrice(sourceChain, targetChain, REASONABLE_GAS_LIMIT_FORWARDS, {receiverValue: valueNeededOnTargetChain, ...optionalParams});
const testForward = async (
payload1: string,
payload2: string,
notEnoughExtraForwardingValue?: boolean
): Promise<ContractReceipt> => {
const valueNeededOnTargetChain = await relayer.getPrice(
targetChain,
sourceChain,
notEnoughExtraForwardingValue ? TOO_LOW_GAS_LIMIT : REASONABLE_GAS_LIMIT,
optionalParams
);
const value = await relayer.getPrice(
sourceChain,
targetChain,
REASONABLE_GAS_LIMIT_FORWARDS,
{ receiverValue: valueNeededOnTargetChain, ...optionalParams }
);
console.log(`Quoted gas delivery fee: ${value}`);
const tx = await source.mockIntegration["sendMessageWithForwardedResponse(bytes,bytes,uint16,uint32,uint128)"](
const tx = await source.mockIntegration[
"sendMessageWithForwardedResponse(bytes,bytes,uint16,uint32,uint128)"
](
payload1,
payload2,
target.chainId,
@ -131,42 +195,81 @@ const testForward = async (payload1: string, payload2: string, notEnoughExtraFor
await tx.wait();
console.log("Message confirmed!");
return tx.hash
}
return tx.wait();
};
describe("Wormhole Relayer Tests", () => {
test("Executes a Delivery Success", async () => {
const arbitraryPayload = getArbitraryBytes32()
const arbitraryPayload = getArbitraryBytes32();
console.log(`Sent message: ${arbitraryPayload}`);
const txHash = await testSend(arbitraryPayload);
const rx = await testSend(arbitraryPayload);
await waitForRelay();
console.log("Checking status using SDK");
const status = await getStatus(txHash);
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Delivery Success");
console.log("Checking if message was relayed");
const message = await target.mockIntegration.getMessage();
expect(message).toBe(arbitraryPayload);
});
test("Executes a Delivery Success with manual delivery", async () => {
const arbitraryPayload = getArbitraryBytes32();
console.log(`Sent message: ${arbitraryPayload}`);
const deliverySeq = await Implementation__factory.connect(
CONTRACTS[network][sourceChain].core || "",
source.provider
).nextSequence(source.wormholeRelayerAddress);
console.log(`Got delivery seq: ${deliverySeq}`);
const rx = await testSend(arbitraryPayload);
await sleep(1000);
const deliveryVaa = await getVAA(getGuardianRPC(network, ci), {
emitterAddress: Buffer.from(
tryNativeToUint8Array(source.wormholeRelayerAddress, "ethereum")
),
chainId: source.chainId,
sequence: deliverySeq,
}, true);
console.log(`Got delivery VAA: ${deliveryVaa}`);
const deliveryRx = await deliver(
deliveryVaa,
target.wallet,
getGuardianRPC(network, ci),
network
);
console.log("Manual delivery tx hash", deliveryRx.transactionHash);
console.log("Manual delivery tx status", deliveryRx.status);
console.log("Checking status using SDK");
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Delivery Success");
console.log("Checking if message was relayed");
const message = await target.mockIntegration.getMessage();
expect(message).toBe(arbitraryPayload);
});
test("Executes a Forward Request Success", async () => {
const arbitraryPayload1 = getArbitraryBytes32()
const arbitraryPayload2 = getArbitraryBytes32()
console.log(`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded`);
const arbitraryPayload1 = getArbitraryBytes32();
const arbitraryPayload2 = getArbitraryBytes32();
console.log(
`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded`
);
const txHash = await testForward(arbitraryPayload1, arbitraryPayload2);
const rx = await testForward(arbitraryPayload1, arbitraryPayload2);
await waitForRelay(2);
console.log("Checking status using SDK");
const status = await getStatus(txHash);
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Forward Request Success");
console.log("Checking if message was relayed");
@ -176,22 +279,40 @@ describe("Wormhole Relayer Tests", () => {
console.log("Checking if forward message was relayed back");
const message2 = await source.mockIntegration.getMessage();
expect(message2).toBe(arbitraryPayload2);
});
test("Executes multiple forwards", async () => {
const arbitraryPayload1 = getArbitraryBytes32()
const arbitraryPayload2 = getArbitraryBytes32()
console.log(`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded`);
const valueNeededOnTargetChain1 = await relayer.getPrice(targetChain, sourceChain, REASONABLE_GAS_LIMIT, optionalParams);
const valueNeededOnTargetChain2 = await relayer.getPrice(targetChain, targetChain, REASONABLE_GAS_LIMIT, optionalParams);
const arbitraryPayload1 = getArbitraryBytes32();
const arbitraryPayload2 = getArbitraryBytes32();
console.log(
`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded`
);
const valueNeededOnTargetChain1 = await relayer.getPrice(
targetChain,
sourceChain,
REASONABLE_GAS_LIMIT,
optionalParams
);
const valueNeededOnTargetChain2 = await relayer.getPrice(
targetChain,
targetChain,
REASONABLE_GAS_LIMIT,
optionalParams
);
const value = await relayer.getPrice(sourceChain, targetChain, REASONABLE_GAS_LIMIT_FORWARDS, {receiverValue: valueNeededOnTargetChain1.add(valueNeededOnTargetChain2), ...optionalParams});
const value = await relayer.getPrice(
sourceChain,
targetChain,
REASONABLE_GAS_LIMIT_FORWARDS,
{
receiverValue: valueNeededOnTargetChain1.add(valueNeededOnTargetChain2),
...optionalParams,
}
);
console.log(`Quoted gas delivery fee: ${value}`);
const tx = await source.mockIntegration.sendMessageWithMultiForwardedResponse(
const tx =
await source.mockIntegration.sendMessageWithMultiForwardedResponse(
arbitraryPayload1,
arbitraryPayload2,
target.chainId,
@ -206,7 +327,7 @@ describe("Wormhole Relayer Tests", () => {
await waitForRelay(2);
const status = await getStatus(tx.hash);
console.log(`Status of forward: ${status}`)
console.log(`Status of forward: ${status}`);
console.log("Checking if first forward was relayed");
const message1 = await source.mockIntegration.getMessage();
@ -218,15 +339,17 @@ describe("Wormhole Relayer Tests", () => {
});
test("Executes a Forward Request Failure", async () => {
const arbitraryPayload1 = getArbitraryBytes32()
const arbitraryPayload2 = getArbitraryBytes32()
console.log(`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded (but should fail)`);
const arbitraryPayload1 = getArbitraryBytes32();
const arbitraryPayload2 = getArbitraryBytes32();
console.log(
`Sent message: ${arbitraryPayload1}, expecting ${arbitraryPayload2} to be forwarded (but should fail)`
);
const txHash = await testForward(arbitraryPayload1, arbitraryPayload2, true);
const rx = await testForward(arbitraryPayload1, arbitraryPayload2, true);
await waitForRelay();
const status = await getStatus(txHash);
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Forward Request Failure");
console.log("Checking if message was relayed (it shouldn't have been!");
@ -238,18 +361,20 @@ describe("Wormhole Relayer Tests", () => {
);
const message2 = await source.mockIntegration.getMessage();
expect(message2).not.toBe(arbitraryPayload2);
});
test("Test getPrice in Typescript SDK", async () => {
const price = (await relayer.getPrice(sourceChain, targetChain, 200000, optionalParams));
const price = await relayer.getPrice(
sourceChain,
targetChain,
200000,
optionalParams
);
expect(price.toString()).toBe("165000000000000000");
});
test("Executes a delivery with a Cross Chain Refund", async () => {
const arbitraryPayload = getArbitraryBytes32()
const arbitraryPayload = getArbitraryBytes32();
console.log(`Sent message: ${arbitraryPayload}`);
const value = await relayer.getPrice(
sourceChain,
@ -268,7 +393,7 @@ describe("Wormhole Relayer Tests", () => {
Buffer.from("hi!"),
REASONABLE_GAS_LIMIT,
{ value, gasLimit: REASONABLE_GAS_LIMIT },
optionalParams,
optionalParams
);
console.log("Sent delivery request!");
await tx.wait();
@ -281,7 +406,11 @@ describe("Wormhole Relayer Tests", () => {
const status = await getStatus(tx.hash);
expect(status).toBe("Receiver Failure");
const info = (await relayer.getWormholeRelayerInfo(sourceChain, tx.hash, optionalParams)) as relayer.DeliveryInfo;
const info = (await relayer.getWormholeRelayerInfo(
sourceChain,
tx.hash,
optionalParams
)) as relayer.DeliveryInfo;
await waitForRelay();
@ -289,7 +418,10 @@ describe("Wormhole Relayer Tests", () => {
console.log("Checking status of refund using SDK");
console.log(relayer.stringifyWormholeRelayerInfo(info));
const statusOfRefund = await getStatus(info.targetChainStatus.events[0].transactionHash || "", targetChain);
const statusOfRefund = await getStatus(
info.targetChainStatus.events[0].transactionHash || "",
targetChain
);
expect(statusOfRefund).toBe("Delivery Success");
console.log(`Quoted gas delivery fee: ${value}`);
@ -310,25 +442,25 @@ describe("Wormhole Relayer Tests", () => {
});
test("Executes a Receiver Failure", async () => {
const arbitraryPayload = getArbitraryBytes32()
const arbitraryPayload = getArbitraryBytes32();
console.log(`Sent message: ${arbitraryPayload}`);
const txHash = await testSend(arbitraryPayload, false, true);
const rx = await testSend(arbitraryPayload, false, true);
await waitForRelay();
const message = await target.mockIntegration.getMessage();
expect(message).not.toBe(arbitraryPayload);
const status = await getStatus(txHash);
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Receiver Failure");
});
test("Executes a receiver failure and then redelivery through SDK", async () => {
const arbitraryPayload = getArbitraryBytes32()
const arbitraryPayload = getArbitraryBytes32();
console.log(`Sent message: ${arbitraryPayload}`);
const txHash = await testSend(arbitraryPayload, false, true);
const rx = await testSend(arbitraryPayload, false, true);
await waitForRelay();
@ -336,12 +468,21 @@ describe("Wormhole Relayer Tests", () => {
expect(message).not.toBe(arbitraryPayload);
console.log("Checking status using SDK");
const status = await getStatus(txHash);
const status = await getStatus(rx.transactionHash);
expect(status).toBe("Receiver Failure");
const value = await relayer.getPrice(sourceChain, targetChain, REASONABLE_GAS_LIMIT, optionalParams);
const value = await relayer.getPrice(
sourceChain,
targetChain,
REASONABLE_GAS_LIMIT,
optionalParams
);
const info = (await relayer.getWormholeRelayerInfo(sourceChain, txHash, optionalParams)) as relayer.DeliveryInfo;
const info = (await relayer.getWormholeRelayerInfo(
sourceChain,
rx.transactionHash,
optionalParams
)) as relayer.DeliveryInfo;
console.log("Redelivering message");
const redeliveryReceipt = await relayer.resend(
@ -381,80 +522,146 @@ describe("Wormhole Relayer Tests", () => {
// GOVERNANCE TESTS
test("Governance: Test Registering Chain", async () => {
const chain = 24;
const currentAddress = await source.wormholeRelayer.getRegisteredWormholeRelayerContract(chain);
console.log(`For Chain ${source.chainId}, registered chain ${chain} address: ${currentAddress}`);
const currentAddress =
await source.wormholeRelayer.getRegisteredWormholeRelayerContract(chain);
console.log(
`For Chain ${source.chainId}, registered chain ${chain} address: ${currentAddress}`
);
const expectedNewRegisteredAddress = "0x0000000000000000000000001234567890123456789012345678901234567892";
const expectedNewRegisteredAddress =
"0x0000000000000000000000001234567890123456789012345678901234567892";
const timestamp = (await source.wallet.provider.getBlock("latest")).timestamp;
const timestamp = (await source.wallet.provider.getBlock("latest"))
.timestamp;
const firstMessage = governance.publishWormholeRelayerRegisterChain(timestamp, chain, expectedNewRegisteredAddress)
const firstSignedVaa = guardians.addSignatures(firstMessage, guardianIndices);
const firstMessage = governance.publishWormholeRelayerRegisterChain(
timestamp,
chain,
expectedNewRegisteredAddress
);
const firstSignedVaa = guardians.addSignatures(
firstMessage,
guardianIndices
);
let tx = await source.wormholeRelayer.registerWormholeRelayerContract(firstSignedVaa, {gasLimit: REASONABLE_GAS_LIMIT});
let tx = await source.wormholeRelayer.registerWormholeRelayerContract(
firstSignedVaa,
{ gasLimit: REASONABLE_GAS_LIMIT }
);
await tx.wait();
const newRegisteredAddress = (await source.wormholeRelayer.getRegisteredWormholeRelayerContract(chain));
const newRegisteredAddress =
await source.wormholeRelayer.getRegisteredWormholeRelayerContract(chain);
expect(newRegisteredAddress).toBe(expectedNewRegisteredAddress);
})
});
test("Governance: Test Setting Default Relay Provider", async () => {
const currentAddress =
await source.wormholeRelayer.getDefaultDeliveryProvider();
console.log(
`For Chain ${source.chainId}, default relay provider: ${currentAddress}`
);
const currentAddress = await source.wormholeRelayer.getDefaultDeliveryProvider();
console.log(`For Chain ${source.chainId}, default relay provider: ${currentAddress}`);
const expectedNewDefaultDeliveryProvider =
"0x1234567890123456789012345678901234567892";
const expectedNewDefaultDeliveryProvider = "0x1234567890123456789012345678901234567892";
const timestamp = (await source.wallet.provider.getBlock("latest")).timestamp;
const timestamp = (await source.wallet.provider.getBlock("latest"))
.timestamp;
const chain = source.chainId;
const firstMessage = governance.publishWormholeRelayerSetDefaultDeliveryProvider(timestamp, chain, expectedNewDefaultDeliveryProvider);
const firstSignedVaa = guardians.addSignatures(firstMessage, guardianIndices);
const firstMessage =
governance.publishWormholeRelayerSetDefaultDeliveryProvider(
timestamp,
chain,
expectedNewDefaultDeliveryProvider
);
const firstSignedVaa = guardians.addSignatures(
firstMessage,
guardianIndices
);
let tx = await source.wormholeRelayer.setDefaultDeliveryProvider(firstSignedVaa);
let tx = await source.wormholeRelayer.setDefaultDeliveryProvider(
firstSignedVaa
);
await tx.wait();
const newDefaultDeliveryProvider = (await source.wormholeRelayer.getDefaultDeliveryProvider());
const newDefaultDeliveryProvider =
await source.wormholeRelayer.getDefaultDeliveryProvider();
expect(newDefaultDeliveryProvider).toBe(expectedNewDefaultDeliveryProvider);
const inverseFirstMessage = governance.publishWormholeRelayerSetDefaultDeliveryProvider(timestamp, chain, currentAddress)
const inverseFirstSignedVaa = guardians.addSignatures(inverseFirstMessage, guardianIndices);
const inverseFirstMessage =
governance.publishWormholeRelayerSetDefaultDeliveryProvider(
timestamp,
chain,
currentAddress
);
const inverseFirstSignedVaa = guardians.addSignatures(
inverseFirstMessage,
guardianIndices
);
tx = await source.wormholeRelayer.setDefaultDeliveryProvider(inverseFirstSignedVaa);
tx = await source.wormholeRelayer.setDefaultDeliveryProvider(
inverseFirstSignedVaa
);
await tx.wait();
const originalDefaultDeliveryProvider = (await source.wormholeRelayer.getDefaultDeliveryProvider());
const originalDefaultDeliveryProvider =
await source.wormholeRelayer.getDefaultDeliveryProvider();
expect(originalDefaultDeliveryProvider).toBe(currentAddress);
});
test("Governance: Test Upgrading Contract", async () => {
const IMPLEMENTATION_STORAGE_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
const IMPLEMENTATION_STORAGE_SLOT =
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
const getImplementationAddress = () => source.provider.getStorageAt(source.wormholeRelayer.address, IMPLEMENTATION_STORAGE_SLOT);
const getImplementationAddress = () =>
source.provider.getStorageAt(
source.wormholeRelayer.address,
IMPLEMENTATION_STORAGE_SLOT
);
console.log(`Current Implementation address: ${(await getImplementationAddress())}`);
console.log(
`Current Implementation address: ${await getImplementationAddress()}`
);
const wormholeAddress = CONTRACTS[network][sourceChain].core || "";
const newWormholeRelayerImplementationAddress = (await new ethers_contracts.WormholeRelayer__factory(source.wallet).deploy(wormholeAddress).then((x)=>x.deployed())).address;
const newWormholeRelayerImplementationAddress = (
await new ethers_contracts.WormholeRelayer__factory(source.wallet)
.deploy(wormholeAddress)
.then((x) => x.deployed())
).address;
console.log(`Deployed!`);
console.log(`New core relayer implementation: ${newWormholeRelayerImplementationAddress}`);
console.log(
`New core relayer implementation: ${newWormholeRelayerImplementationAddress}`
);
const timestamp = (await source.wallet.provider.getBlock("latest")).timestamp;
const timestamp = (await source.wallet.provider.getBlock("latest"))
.timestamp;
const chain = source.chainId;
const firstMessage = governance.publishWormholeRelayerUpgradeContract(timestamp, chain, newWormholeRelayerImplementationAddress);
const firstSignedVaa = guardians.addSignatures(firstMessage, guardianIndices);
const firstMessage = governance.publishWormholeRelayerUpgradeContract(
timestamp,
chain,
newWormholeRelayerImplementationAddress
);
const firstSignedVaa = guardians.addSignatures(
firstMessage,
guardianIndices
);
let tx = await source.wormholeRelayer.submitContractUpgrade(firstSignedVaa);
expect(ethers.utils.getAddress((await getImplementationAddress()).substring(26))).toBe(ethers.utils.getAddress(newWormholeRelayerImplementationAddress));
expect(
ethers.utils.getAddress((await getImplementationAddress()).substring(26))
).toBe(ethers.utils.getAddress(newWormholeRelayerImplementationAddress));
});
});
function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(() => r(), ms));
}

View File

@ -6,19 +6,29 @@ type AddressInfo = {wormholeRelayerAddress?: string, mockDeliveryProviderAddress
const TESTNET: {[K in ChainName]?: AddressInfo} = {
bsc: {
wormholeRelayerAddress: "0x6Bf598B0eb6aef9B163565763Fe50e54d230eD4E",
wormholeRelayerAddress: "0x80aC94316391752A193C1c47E27D382b507c93F3",
mockDeliveryProviderAddress: "0x813AB43ab264362c55BF35A1448d0fd8135049a6",
mockIntegrationAddress: "0xb6A04D6672F005787147472Be20d39741929Aa03",
},
polygon: {
wormholeRelayerAddress: "0x0c97Ef9C224b7EB0BA5e4A9fd2740EC3AeAfc9c3",
wormholeRelayerAddress: "0x0591C25ebd0580E0d4F27A82Fc2e24E7489CB5e0",
mockDeliveryProviderAddress: "0xBF684878906629E72079D4f07D75Ef7165238092",
mockIntegrationAddress: "0x3bF0c43d88541BBCF92bE508ec41e540FbF28C56",
},
avalanche: {
wormholeRelayerAddress: "0xf4e844a9B75BB532e67E654F7F80C6232e5Ea7a0",
wormholeRelayerAddress: "0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB",
mockDeliveryProviderAddress: "0xd5903a063f604D4615E5c2760b7b80D491564BBe",
mockIntegrationAddress: "0x5E52f3eB0774E5e5f37760BD3Fca64951D8F74Ae",
},
celo: {
wormholeRelayerAddress: "0xF08B7c0CFf448174a7007CF5f12023C72C0e84f0",
wormholeRelayerAddress: "0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84",
mockDeliveryProviderAddress: "0x93d56f29542c156B3e36f10dE41124B499664ff7",
mockIntegrationAddress: "0x7f1d8E809aBB3F6Dc9B90F0131C3E8308046E190",
},
moonbeam: {
wormholeRelayerAddress: "0xd20d484eC6c57448d6871F91F4527260FD4aC141",
wormholeRelayerAddress: "0x0591C25ebd0580E0d4F27A82Fc2e24E7489CB5e0",
mockDeliveryProviderAddress: "0xBF684878906629E72079D4f07D75Ef7165238092",
mockIntegrationAddress: "0x3bF0c43d88541BBCF92bE508ec41e540FbF28C56",
},
}

View File

@ -1,3 +1,3 @@
export * from "./structs";
export * from "./consts";
export * from "./relayer/relayer";
export * from "./relayer";

View File

@ -0,0 +1,127 @@
import { BigNumber, ethers, ContractReceipt } from "ethers";
import { IWormholeRelayer__factory } from "../../ethers-contracts";
import { ChainName, toChainName, ChainId, Network } from "../../utils";
import { SignedVaa, parseVaa } from "../../vaa";
import { getWormholeRelayerAddress } from "../consts";
import {
RelayerPayloadId,
DeliveryInstruction,
DeliveryOverrideArgs,
packOverrides,
parseEVMExecutionInfoV1,
parseWormholeRelayerPayloadType,
parseWormholeRelayerSend,
VaaKey,
} from "../structs";
import { DeliveryTargetInfo, getVAA } from "./helpers";
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery;
sourceChain: ChainName;
sourceTransactionHash: string;
sourceDeliverySequenceNumber: number;
deliveryInstruction: DeliveryInstruction;
targetChainStatus: {
chain: ChainName;
events: DeliveryTargetInfo[];
};
};
export type DeliveryArguments = {
budget: BigNumber;
deliveryInstruction: DeliveryInstruction;
deliveryHash: string;
};
export async function deliver(
deliveryVaa: SignedVaa,
signer: ethers.Signer,
wormholeRPCs: string | string[],
environment: Network = "MAINNET",
overrides?: DeliveryOverrideArgs
): Promise<ContractReceipt> {
const { budget, deliveryInstruction, deliveryHash } =
extractDeliveryArguments(deliveryVaa, overrides);
const additionalVaas = await fetchAdditionalVaas(
wormholeRPCs,
deliveryInstruction.vaaKeys
);
const wormholeRelayerAddress = getWormholeRelayerAddress(
toChainName(deliveryInstruction.targetChainId as ChainId),
environment
);
const wormholeRelayer = IWormholeRelayer__factory.connect(
wormholeRelayerAddress,
signer
);
const gasEstimate = await wormholeRelayer.estimateGas.deliver(
additionalVaas,
deliveryVaa,
signer.getAddress(),
overrides ? packOverrides(overrides) : new Uint8Array(),
{ value: budget }
);
const tx = await wormholeRelayer.deliver(
additionalVaas,
deliveryVaa,
signer.getAddress(),
overrides ? packOverrides(overrides) : new Uint8Array(),
{ value: budget, gasLimit: gasEstimate.mul(2) }
);
const rx = await tx.wait();
console.log(`Delivered ${deliveryHash} on ${rx.blockNumber}`);
return rx;
}
export function deliveryBudget(
delivery: DeliveryInstruction,
overrides?: DeliveryOverrideArgs
): BigNumber {
const receiverValue = overrides?.newReceiverValue
? overrides.newReceiverValue
: delivery.requestedReceiverValue.add(delivery.extraReceiverValue);
const getMaxRefund = (encodedDeliveryInfo: Buffer) => {
const [deliveryInfo] = parseEVMExecutionInfoV1(encodedDeliveryInfo, 0);
return deliveryInfo.targetChainRefundPerGasUnused.mul(
deliveryInfo.gasLimit
);
};
const maxRefund = getMaxRefund(
overrides?.newExecutionInfo
? overrides.newExecutionInfo
: delivery.encodedExecutionInfo
);
return receiverValue.add(maxRefund);
}
export function extractDeliveryArguments(
vaa: SignedVaa,
overrides?: DeliveryOverrideArgs
): DeliveryArguments {
const parsedVaa = parseVaa(vaa);
const payloadType = parseWormholeRelayerPayloadType(parsedVaa.payload);
if (payloadType !== RelayerPayloadId.Delivery) {
throw new Error(
`Expected delivery payload type, got ${RelayerPayloadId[payloadType]}`
);
}
const deliveryInstruction = parseWormholeRelayerSend(parsedVaa.payload);
const budget = deliveryBudget(deliveryInstruction, overrides);
return {
budget,
deliveryInstruction: deliveryInstruction,
deliveryHash: parsedVaa.hash.toString("hex"),
};
}
export async function fetchAdditionalVaas(
wormholeRPCs: string | string[],
additionalVaaKeys: VaaKey[]
): Promise<SignedVaa[]> {
return Promise.all(
additionalVaaKeys.map(async (vaaKey) => getVAA(wormholeRPCs, vaaKey))
);
}

View File

@ -6,7 +6,9 @@ import {
Network,
tryNativeToHexString,
isChain,
CONTRACTS
CONTRACTS,
getSignedVAAWithRetry,
SignedVaa,
} from "../../";
import { BigNumber, ContractReceipt, ethers } from "ethers";
import { getWormholeRelayer, RPCS_BY_CHAIN } from "../consts";
@ -20,11 +22,17 @@ import {
RefundStatus,
VaaKey,
DeliveryOverrideArgs,
parseForwardFailureError
parseForwardFailureError,
} from "../structs";
import { DeliveryProvider, DeliveryProvider__factory, Implementation__factory, IWormholeRelayerDelivery__factory } from "../../ethers-contracts/";
import {DeliveryEvent} from "../../ethers-contracts/WormholeRelayer"
import {
DeliveryProvider,
DeliveryProvider__factory,
Implementation__factory,
IWormholeRelayerDelivery__factory,
} from "../../ethers-contracts/";
import { DeliveryEvent } from "../../ethers-contracts/WormholeRelayer";
import { VaaKeyStruct } from "../../ethers-contracts/IWormholeRelayer.sol/IWormholeRelayer";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
export type DeliveryTargetInfo = {
status: DeliveryStatus | string;
@ -38,6 +46,31 @@ export type DeliveryTargetInfo = {
overrides?: DeliveryOverrideArgs;
};
export async function getVAA(
wormholeRPCs: string[] | string,
vaaKey: VaaKey,
isNode?: boolean
): Promise<SignedVaa> {
if (typeof wormholeRPCs === "string") {
wormholeRPCs = [wormholeRPCs];
}
const vaa = await getSignedVAAWithRetry(
wormholeRPCs,
vaaKey.chainId! as ChainId,
vaaKey.emitterAddress!.toString("hex"),
vaaKey.sequence!.toBigInt().toString(),
isNode
? {
transport: NodeHttpTransport(),
}
: {},
2000,
4
);
return Buffer.from(vaa.vaaBytes);
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId;
parsed: DeliveryInstruction | string;
@ -57,16 +90,21 @@ export function parseWormholeLog(log: ethers.providers.Log): {
}
export function printChain(chainId: number) {
if(!(chainId in CHAIN_ID_TO_NAME)) throw Error(`Invalid Chain ID: ${chainId}`);
if (!(chainId in CHAIN_ID_TO_NAME))
throw Error(`Invalid Chain ID: ${chainId}`);
return `${CHAIN_ID_TO_NAME[chainId as ChainId]} (Chain ${chainId})`;
}
export function getDefaultProvider(network: Network, chain: ChainName, ci?: boolean) {
export function getDefaultProvider(
network: Network,
chain: ChainName,
ci?: boolean
) {
let rpc: string | undefined = "";
if (ci) {
if (chain == "ethereum") rpc = "http://eth-devnet:8545";
else if (chain == "bsc") rpc = "http://eth-devnet2:8545";
else throw Error(`This chain isn't in CI for relayers: ${chain}`)
else throw Error(`This chain isn't in CI for relayers: ${chain}`);
} else {
rpc = RPCS_BY_CHAIN[network][chain];
}
@ -100,7 +138,7 @@ export async function getWormholeRelayerInfoBySourceSequence(
blockStartNumber: ethers.providers.BlockTag,
blockEndNumber: ethers.providers.BlockTag,
targetWormholeRelayerAddress: string
): Promise<{chain: ChainName, events: DeliveryTargetInfo[]}> {
): Promise<{ chain: ChainName; events: DeliveryTargetInfo[] }> {
const deliveryEvents = await getWormholeRelayerDeliveryEventsBySourceSequence(
environment,
targetChain,
@ -116,9 +154,9 @@ export async function getWormholeRelayerInfoBySourceSequence(
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber);
const blockEnd = await targetChainProvider.getBlock(blockEndNumber);
status = `Delivery didn't happen on ${targetChain} within blocks ${blockStart.number} to ${
blockEnd.number
} (within times ${new Date(
status = `Delivery didn't happen on ${targetChain} within blocks ${
blockStart.number
} to ${blockEnd.number} (within times ${new Date(
blockStart.timestamp * 1000
).toString()} to ${new Date(blockEnd.timestamp * 1000).toString()})`;
} catch (e) {}
@ -129,12 +167,12 @@ export async function getWormholeRelayerInfoBySourceSequence(
sourceChain: sourceChain,
sourceVaaSequence,
gasUsed: BigNumber.from(0),
refundStatus: RefundStatus.RefundFail
refundStatus: RefundStatus.RefundFail,
});
}
const targetChainStatus = {
chain: targetChain,
events: deliveryEvents
events: deliveryEvents,
};
return targetChainStatus;
@ -151,7 +189,7 @@ export async function getWormholeRelayerDeliveryEventsBySourceSequence(
targetWormholeRelayerAddress: string
): Promise<DeliveryTargetInfo[]> {
const sourceChainId = CHAINS[sourceChain];
if(!sourceChainId) throw Error(`Invalid source chain: ${sourceChain}`)
if (!sourceChainId) throw Error(`Invalid source chain: ${sourceChain}`);
const wormholeRelayer = getWormholeRelayer(
targetChain,
environment,
@ -165,13 +203,23 @@ export async function getWormholeRelayerDeliveryEventsBySourceSequence(
sourceVaaSequence
);
const deliveryEventsPreFilter: DeliveryEvent[] = await wormholeRelayer.queryFilter(
const deliveryEventsPreFilter: DeliveryEvent[] =
await wormholeRelayer.queryFilter(
deliveryEvents,
blockStartNumber,
blockEndNumber
);
const isValid: boolean[] = await Promise.all(deliveryEventsPreFilter.map((deliveryEvent) => areSignaturesValid(deliveryEvent.getTransaction(), targetChain, targetChainProvider, environment)));
const isValid: boolean[] = await Promise.all(
deliveryEventsPreFilter.map((deliveryEvent) =>
areSignaturesValid(
deliveryEvent.getTransaction(),
targetChain,
targetChainProvider,
environment
)
)
);
// There is a max limit on RPCs sometimes for how many blocks to query
return await transformDeliveryEvents(
@ -180,18 +228,32 @@ export async function getWormholeRelayerDeliveryEventsBySourceSequence(
);
}
async function areSignaturesValid(transaction: Promise<ethers.Transaction>, targetChain: ChainName, targetChainProvider: ethers.providers.Provider, environment: Network) {
async function areSignaturesValid(
transaction: Promise<ethers.Transaction>,
targetChain: ChainName,
targetChainProvider: ethers.providers.Provider,
environment: Network
) {
const coreAddress = CONTRACTS[environment][targetChain].core;
if(!coreAddress) throw Error(`No Wormhole Address for chain ${targetChain}, network ${environment}`);
if (!coreAddress)
throw Error(
`No Wormhole Address for chain ${targetChain}, network ${environment}`
);
const wormhole = Implementation__factory.connect(coreAddress, targetChainProvider);
const decodedData = IWormholeRelayerDelivery__factory.createInterface().parseTransaction(await transaction);
const wormhole = Implementation__factory.connect(
coreAddress,
targetChainProvider
);
const decodedData =
IWormholeRelayerDelivery__factory.createInterface().parseTransaction(
await transaction
);
const vaaIsValid = async (vaa: ethers.utils.BytesLike): Promise<boolean> => {
const [, result, reason] = await wormhole.parseAndVerifyVM(vaa);
if (!result) console.log(`Invalid vaa! Reason: ${reason}`);
return result;
}
};
const vaas = decodedData.args[0];
for (let i = 0; i < vaas.length; i++) {
@ -222,12 +284,11 @@ async function transformDeliveryEvents(
events: DeliveryEvent[],
targetProvider: ethers.providers.Provider
): Promise<DeliveryTargetInfo[]> {
return Promise.all(
events.map(async (x) => {
const status = deliveryStatus(x.args[4]);
if(!isChain(x.args[1])) throw Error(`Invalid source chain id: ${x.args[1]}`);
if (!isChain(x.args[1]))
throw Error(`Invalid source chain id: ${x.args[1]}`);
const sourceChain = CHAIN_ID_TO_NAME[x.args[1] as ChainId];
return {
status,
@ -237,8 +298,19 @@ async function transformDeliveryEvents(
sourceChain,
gasUsed: BigNumber.from(x.args[5]),
refundStatus: x.args[6],
revertString: (status == DeliveryStatus.ReceiverFailure) ? x.args[7] : (status == DeliveryStatus.ForwardRequestFailure ? parseForwardFailureError(Buffer.from(x.args[7].substring(2), "hex")): undefined),
overridesInfo: (Buffer.from(x.args[8].substring(2), "hex").length > 0) && parseOverrideInfoFromDeliveryEvent(Buffer.from(x.args[8].substring(2), "hex"))
revertString:
status == DeliveryStatus.ReceiverFailure
? x.args[7]
: status == DeliveryStatus.ForwardRequestFailure
? parseForwardFailureError(
Buffer.from(x.args[7].substring(2), "hex")
)
: undefined,
overridesInfo:
Buffer.from(x.args[8].substring(2), "hex").length > 0 &&
parseOverrideInfoFromDeliveryEvent(
Buffer.from(x.args[8].substring(2), "hex")
),
};
})
);
@ -288,9 +360,7 @@ export function getWormholeRelayerLog(
}
}
export function vaaKeyToVaaKeyStruct(
vaaKey: VaaKey
): VaaKeyStruct {
export function vaaKeyToVaaKeyStruct(vaaKey: VaaKey): VaaKeyStruct {
return {
chainId: vaaKey.chainId || 0,
emitterAddress:

View File

@ -0,0 +1,5 @@
export * from "./helpers";
export * from "./deliver";
export * from "./info";
export * from "./resend";
export * from "./send";

View File

@ -0,0 +1,306 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
ChainName,
isChain,
CONTRACTS,
CHAINS,
tryNativeToHexString,
Network,
ethers_contracts,
} from "../..";
import { BigNumber, ethers } from "ethers";
import { getWormholeRelayerAddress } from "../consts";
import {
RelayerPayloadId,
DeliveryInstruction,
RefundStatus,
parseEVMExecutionInfoV1,
} from "../structs";
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerInfoBySourceSequence,
} from "./helpers";
import { DeliveryInfo } from "./deliver";
export type InfoRequestParams = {
environment?: Network;
sourceChainProvider?: ethers.providers.Provider;
targetChainProviders?: Map<ChainName, ethers.providers.Provider>;
targetChainBlockRanges?: Map<
ChainName,
[ethers.providers.BlockTag, ethers.providers.BlockTag]
>;
wormholeRelayerWhMessageIndex?: number;
wormholeRelayerAddresses?: Map<ChainName, string>;
};
export type GetPriceOptParams = {
environment?: Network;
receiverValue?: ethers.BigNumberish;
deliveryProviderAddress?: string;
sourceChainProvider?: ethers.providers.Provider;
};
export async function getPriceAndRefundInfo(
sourceChain: ChainName,
targetChain: ChainName,
gasAmount: ethers.BigNumberish,
optionalParams?: GetPriceOptParams
): Promise<[ethers.BigNumber, ethers.BigNumber]> {
const environment = optionalParams?.environment || "MAINNET";
const sourceChainProvider =
optionalParams?.sourceChainProvider ||
getDefaultProvider(environment, sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
);
const wormholeRelayerAddress = getWormholeRelayerAddress(
sourceChain,
environment
);
const sourceWormholeRelayer =
ethers_contracts.IWormholeRelayer__factory.connect(
wormholeRelayerAddress,
sourceChainProvider
);
const deliveryProviderAddress =
optionalParams?.deliveryProviderAddress ||
(await sourceWormholeRelayer.getDefaultDeliveryProvider());
const targetChainId = CHAINS[targetChain];
const priceAndRefundInfo = await sourceWormholeRelayer[
"quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"
](
targetChainId,
optionalParams?.receiverValue || 0,
gasAmount,
deliveryProviderAddress
);
return priceAndRefundInfo;
}
export async function getPrice(
sourceChain: ChainName,
targetChain: ChainName,
gasAmount: ethers.BigNumberish,
optionalParams?: GetPriceOptParams
): Promise<ethers.BigNumber> {
const priceAndRefundInfo = await getPriceAndRefundInfo(
sourceChain,
targetChain,
gasAmount,
optionalParams
);
return priceAndRefundInfo[0];
}
export async function getWormholeRelayerInfo(
sourceChain: ChainName,
sourceTransaction: string,
infoRequest?: InfoRequestParams
): Promise<DeliveryInfo> {
const environment = infoRequest?.environment || "MAINNET";
const sourceChainProvider =
infoRequest?.sourceChainProvider ||
getDefaultProvider(environment, sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
);
const receipt = await sourceChainProvider.getTransactionReceipt(
sourceTransaction
);
if (!receipt) throw Error("Transaction has not been mined");
const bridgeAddress = CONTRACTS[environment][sourceChain].core;
const wormholeRelayerAddress =
infoRequest?.wormholeRelayerAddresses?.get(sourceChain) ||
getWormholeRelayerAddress(sourceChain, environment);
if (!bridgeAddress || !wormholeRelayerAddress) {
throw Error(
`Invalid chain ID or network: Chain ${sourceChain}, ${environment}`
);
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(wormholeRelayerAddress, "ethereum"),
infoRequest?.wormholeRelayerWhMessageIndex
? infoRequest.wormholeRelayerWhMessageIndex
: 0
);
const { type, parsed } = parseWormholeLog(deliveryLog.log);
const instruction = parsed as DeliveryInstruction;
const targetChainId = instruction.targetChainId as ChainId;
if (!isChain(targetChainId)) throw Error(`Invalid Chain: ${targetChainId}`);
const targetChain = CHAIN_ID_TO_NAME[targetChainId];
const targetChainProvider =
infoRequest?.targetChainProviders?.get(targetChain) ||
getDefaultProvider(environment, targetChain);
if (!targetChainProvider) {
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
);
}
const [blockStartNumber, blockEndNumber] =
infoRequest?.targetChainBlockRanges?.get(targetChain) ||
getBlockRange(targetChainProvider);
const targetChainStatus = await getWormholeRelayerInfoBySourceSequence(
environment,
targetChain,
targetChainProvider,
sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber,
infoRequest?.wormholeRelayerAddresses?.get(targetChain) ||
getWormholeRelayerAddress(targetChain, environment)
);
return {
type: RelayerPayloadId.Delivery,
sourceChain: sourceChain,
sourceTransactionHash: sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(
deliveryLog.sequence
).toNumber(),
deliveryInstruction: instruction,
targetChainStatus,
};
}
export function printWormholeRelayerInfo(info: DeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info));
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo): string {
let stringifiedInfo = "";
if (
info.type == RelayerPayloadId.Delivery &&
info.deliveryInstruction.targetAddress.toString("hex") !==
"0000000000000000000000000000000000000000000000000000000000000000"
) {
stringifiedInfo += `Found delivery request in transaction ${info.sourceTransactionHash} on ${info.sourceChain}\n`;
const numMsgs = info.deliveryInstruction.vaaKeys.length;
const payload = info.deliveryInstruction.payload.toString("hex");
if (payload.length > 0) {
stringifiedInfo += `\nPayload to be relayed (as hex string): 0x${payload}`;
}
if (numMsgs > 0) {
stringifiedInfo += `\nThe following ${numMsgs} wormhole messages (VAAs) were ${
payload.length > 0 ? "also " : ""
}requested to be relayed:\n`;
stringifiedInfo += info.deliveryInstruction.vaaKeys
.map((msgInfo, i) => {
let result = "";
result += `(VAA ${i}): `;
result += `Message from ${
msgInfo.chainId ? printChain(msgInfo.chainId) : ""
}, with emitter address ${msgInfo.emitterAddress?.toString(
"hex"
)} and sequence number ${msgInfo.sequence}`;
return result;
})
.join(",\n");
}
if (payload.length == 0 && numMsgs == 0) {
stringifiedInfo += `\nAn empty payload was requested to be sent`;
}
const instruction = info.deliveryInstruction;
const targetChainName =
CHAIN_ID_TO_NAME[instruction.targetChainId as ChainId];
stringifiedInfo += `${
numMsgs == 0
? payload.length == 0
? ""
: "\n\nPayload was requested to be relayed"
: "\n\nThese were requested to be sent"
} to 0x${instruction.targetAddress.toString("hex")} on ${printChain(
instruction.targetChainId
)}\n`;
const totalReceiverValue = instruction.requestedReceiverValue.add(
instruction.extraReceiverValue
);
stringifiedInfo += totalReceiverValue.gt(0)
? `Amount to pass into target address: ${totalReceiverValue} wei of ${targetChainName} currency ${
instruction.extraReceiverValue.gt(0)
? `${instruction.requestedReceiverValue} requested, ${instruction.extraReceiverValue} additionally paid for`
: ""
}\n`
: ``;
const [executionInfo] = parseEVMExecutionInfoV1(
instruction.encodedExecutionInfo,
0
);
stringifiedInfo += `Gas limit: ${executionInfo.gasLimit} ${targetChainName} gas\n\n`;
stringifiedInfo += `Refund rate: ${executionInfo.targetChainRefundPerGasUnused} of ${targetChainName} wei per unit of gas unused\n\n`;
stringifiedInfo += info.targetChainStatus.events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${
e.transactionHash
? ` ${targetChainName} transaction hash: ${e.transactionHash}`
: ""
}\nStatus: ${e.status}\n${
e.revertString
? `Failure reason: ${
e.gasUsed.eq(executionInfo.gasLimit)
? "Gas limit hit"
: e.revertString
}\n`
: ""
}Gas used: ${e.gasUsed.toString()}\nTransaction fee used: ${executionInfo.targetChainRefundPerGasUnused
.mul(e.gasUsed)
.toString()} wei of ${targetChainName} currency\n}`
)
.join("\n");
} else if (
info.type == RelayerPayloadId.Delivery &&
info.deliveryInstruction.targetAddress.toString("hex") ===
"0000000000000000000000000000000000000000000000000000000000000000"
) {
stringifiedInfo += `Found delivery request in transaction ${info.sourceTransactionHash} on ${info.sourceChain}\n`;
const instruction = info.deliveryInstruction;
const targetChainName =
CHAIN_ID_TO_NAME[instruction.targetChainId as ChainId];
stringifiedInfo += `\nA refund of ${
instruction.extraReceiverValue
} ${targetChainName} wei was requested to be sent to ${targetChainName}, address 0x${info.deliveryInstruction.refundAddress.toString(
"hex"
)}`;
stringifiedInfo += info.targetChainStatus.events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${
e.transactionHash
? ` ${targetChainName} transaction hash: ${e.transactionHash}`
: ""
}\nStatus: ${
e.refundStatus == RefundStatus.RefundSent
? "Refund Successful"
: "Refund Failed"
}`
)
.join("\n");
}
return stringifiedInfo;
}

View File

@ -1,508 +0,0 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
ChainName,
isChain,
CONTRACTS,
CHAINS,
tryNativeToHexString,
tryHexToNativeString,
Network,
ethers_contracts,
getSignedVAAWithRetry,
parseVaa,
} from "../../";
import { BigNumber, ethers } from "ethers";
import { getWormholeRelayer, getWormholeRelayerAddress } from "../consts";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import {
RelayerPayloadId,
DeliveryInstruction,
VaaKeyType,
DeliveryStatus,
VaaKey,
parseWormholeRelayerSend,
RefundStatus,
parseEVMExecutionInfoV1
} from "../structs";
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerInfoBySourceSequence,
vaaKeyToVaaKeyStruct,
getDeliveryProvider,
DeliveryTargetInfo
} from "./helpers";
import { VaaKeyStruct } from "../../ethers-contracts/IWormholeRelayer.sol/IWormholeRelayer";
export type InfoRequestParams = {
environment?: Network;
sourceChainProvider?: ethers.providers.Provider;
targetChainProviders?: Map<ChainName, ethers.providers.Provider>;
targetChainBlockRanges?: Map<
ChainName,
[ethers.providers.BlockTag, ethers.providers.BlockTag]
>;
wormholeRelayerWhMessageIndex?: number;
wormholeRelayerAddresses?: Map<ChainName, string>
};
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery;
sourceChain: ChainName;
sourceTransactionHash: string;
sourceDeliverySequenceNumber: number;
deliveryInstruction: DeliveryInstruction;
targetChainStatus: {
chain: ChainName;
events: DeliveryTargetInfo[];
};
};
export function printWormholeRelayerInfo(info: DeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info));
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo): string {
let stringifiedInfo = "";
if (info.type == RelayerPayloadId.Delivery && info.deliveryInstruction.targetAddress.toString("hex") !== "0000000000000000000000000000000000000000000000000000000000000000") {
stringifiedInfo += `Found delivery request in transaction ${
info.sourceTransactionHash
} on ${info.sourceChain}\n`;
const numMsgs = info.deliveryInstruction.vaaKeys.length;
const payload = info.deliveryInstruction.payload.toString("hex");
if(payload.length > 0) {
stringifiedInfo += `\nPayload to be relayed (as hex string): 0x${payload}`
}
if(numMsgs > 0) {
stringifiedInfo += `\nThe following ${numMsgs} wormhole messages (VAAs) were ${payload.length > 0 ? 'also ' : ''}requested to be relayed:\n`;
stringifiedInfo += info.deliveryInstruction.vaaKeys.map((msgInfo, i) => {
let result = "";
result += `(VAA ${i}): `;
result += `Message from ${
msgInfo.chainId ? printChain(msgInfo.chainId) : ""
}, with emitter address ${msgInfo.emitterAddress?.toString(
"hex"
)} and sequence number ${msgInfo.sequence}`;
return result;
}).join(",\n");
}
if(payload.length == 0 && numMsgs == 0) {
stringifiedInfo += `\nAn empty payload was requested to be sent`
}
const instruction = info.deliveryInstruction;
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChainId as ChainId];
stringifiedInfo += `${numMsgs == 0 ? (payload.length == 0 ? '' : '\n\nPayload was requested to be relayed') : '\n\nThese were requested to be sent'} to 0x${instruction.targetAddress.toString(
"hex"
)} on ${printChain(instruction.targetChainId)}\n`;
const totalReceiverValue = (instruction.requestedReceiverValue.add(instruction.extraReceiverValue));
stringifiedInfo += totalReceiverValue.gt(0)
? `Amount to pass into target address: ${totalReceiverValue} wei of ${targetChainName} currency ${instruction.extraReceiverValue.gt(0) ? `${instruction.requestedReceiverValue} requested, ${instruction.extraReceiverValue} additionally paid for` : ""}\n`
: ``;
const [executionInfo,] = parseEVMExecutionInfoV1(instruction.encodedExecutionInfo, 0);
stringifiedInfo += `Gas limit: ${executionInfo.gasLimit} ${targetChainName} gas\n\n`;
stringifiedInfo += `Refund rate: ${executionInfo.targetChainRefundPerGasUnused} of ${targetChainName} wei per unit of gas unused\n\n`;
stringifiedInfo += info.targetChainStatus.events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${
e.transactionHash
? ` ${targetChainName} transaction hash: ${e.transactionHash}`
: ""
}\nStatus: ${e.status}\n${e.revertString ? `Failure reason: ${e.gasUsed.eq(executionInfo.gasLimit) ? "Gas limit hit" : e.revertString}\n`: ""}Gas used: ${e.gasUsed.toString()}\nTransaction fee used: ${executionInfo.targetChainRefundPerGasUnused.mul(e.gasUsed).toString()} wei of ${targetChainName} currency\n}`
)
.join("\n");
} else if (info.type == RelayerPayloadId.Delivery && info.deliveryInstruction.targetAddress.toString("hex") === "0000000000000000000000000000000000000000000000000000000000000000") {
stringifiedInfo += `Found delivery request in transaction ${
info.sourceTransactionHash
} on ${info.sourceChain}\n`;
const instruction = info.deliveryInstruction;
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChainId as ChainId];
stringifiedInfo += `\nA refund of ${instruction.extraReceiverValue} ${targetChainName} wei was requested to be sent to ${targetChainName}, address 0x${info.deliveryInstruction.refundAddress.toString("hex")}`
stringifiedInfo += info.targetChainStatus.events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${
e.transactionHash
? ` ${targetChainName} transaction hash: ${e.transactionHash}`
: ""
}\nStatus: ${e.refundStatus == RefundStatus.RefundSent ? "Refund Successful" : "Refund Failed"}`
)
.join("\n");
}
return stringifiedInfo;
}
export type SendOptionalParams = {
environment?: Network;
receiverValue?: ethers.BigNumberish;
paymentForExtraReceiverValue?: ethers.BigNumberish;
additionalVaas?: [
{
chainId?: ChainId;
emitterAddress: string;
sequenceNumber: ethers.BigNumberish;
}
];
deliveryProviderAddress?: string;
consistencyLevel?: ethers.BigNumberish;
refundChainId?: ChainId;
refundAddress?: string;
relayParameters?: ethers.BytesLike;
};
export async function sendToEvm(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
targetAddress: string,
payload: ethers.BytesLike,
gasLimit: BigNumber | number,
overrides?: ethers.PayableOverrides,
sendOptionalParams?: SendOptionalParams,
): Promise<ethers.providers.TransactionResponse> {
const sourceChainId = CHAINS[sourceChain];
const targetChainId = CHAINS[targetChain];
const environment = sendOptionalParams?.environment || "MAINNET";
const wormholeRelayerAddress = getWormholeRelayerAddress(
sourceChain,
environment
);
const sourceWormholeRelayer = ethers_contracts.IWormholeRelayer__factory.connect(
wormholeRelayerAddress,
signer
);
const refundLocationExists =
sendOptionalParams?.refundChainId!== undefined &&
sendOptionalParams?.refundAddress !== undefined;
const defaultDeliveryProviderAddress =
await sourceWormholeRelayer.getDefaultDeliveryProvider();
// Using the most general 'send' function in IWormholeRelayer
// Inputs:
// targetChainId, targetAddress, refundChainId, refundAddress, maxTransactionFee, receiverValue, payload, vaaKeys,
// consistencyLevel, deliveryProviderAddress, relayParameters
const [deliveryPrice,]: [BigNumber, BigNumber] = await sourceWormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"](targetChainId, sendOptionalParams?.receiverValue || 0, gasLimit, sendOptionalParams?.deliveryProviderAddress || defaultDeliveryProviderAddress);
const value = await (overrides?.value || 0);
const totalPrice = deliveryPrice.add(sendOptionalParams?.paymentForExtraReceiverValue || 0);
if(!totalPrice.eq(value)) {
throw new Error(`Expected a payment of ${totalPrice.toString()} wei; received ${value.toString()} wei`);
}
const tx = sourceWormholeRelayer.sendToEvm(
targetChainId, // targetChainId
targetAddress, // targetAddress
payload,
sendOptionalParams?.receiverValue || 0, // receiverValue
sendOptionalParams?.paymentForExtraReceiverValue || 0, // payment for extra receiverValue
gasLimit,
(refundLocationExists && sendOptionalParams?.refundChainId) || sourceChainId, // refundChainId
refundLocationExists &&
sendOptionalParams?.refundAddress &&
sendOptionalParams?.refundAddress ||
signer.getAddress(), // refundAddress
sendOptionalParams?.deliveryProviderAddress || defaultDeliveryProviderAddress, // deliveryProviderAddress
sendOptionalParams?.additionalVaas
? sendOptionalParams.additionalVaas.map(
(additionalVaa): VaaKeyStruct => ({
chainId: additionalVaa.chainId || sourceChainId,
emitterAddress: Buffer.from(tryNativeToHexString(
additionalVaa.emitterAddress,
"ethereum"
), "hex"),
sequence: BigNumber.from(additionalVaa.sequenceNumber || 0)
})
)
: [], // vaaKeys
sendOptionalParams?.consistencyLevel || 15, // consistencyLevel
overrides);
return tx;
}
export type GetPriceOptParams = {
environment?: Network;
receiverValue?: ethers.BigNumberish;
deliveryProviderAddress?: string;
sourceChainProvider?: ethers.providers.Provider;
};
export async function getPriceAndRefundInfo(
sourceChain: ChainName,
targetChain: ChainName,
gasAmount: ethers.BigNumberish,
optionalParams?: GetPriceOptParams
): Promise<[ethers.BigNumber, ethers.BigNumber]> {
const environment = optionalParams?.environment || "MAINNET";
const sourceChainProvider =
optionalParams?.sourceChainProvider ||
getDefaultProvider(environment, sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
);
const wormholeRelayerAddress = getWormholeRelayerAddress(
sourceChain,
environment
);
const sourceWormholeRelayer = ethers_contracts.IWormholeRelayer__factory.connect(
wormholeRelayerAddress,
sourceChainProvider
);
const deliveryProviderAddress =
optionalParams?.deliveryProviderAddress ||
(await sourceWormholeRelayer.getDefaultDeliveryProvider());
const targetChainId = CHAINS[targetChain];
const priceAndRefundInfo = (
await sourceWormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"](
targetChainId,
optionalParams?.receiverValue || 0,
gasAmount,
deliveryProviderAddress
)
)
return priceAndRefundInfo;
}
export async function getPrice(
sourceChain: ChainName,
targetChain: ChainName,
gasAmount: ethers.BigNumberish,
optionalParams?: GetPriceOptParams
): Promise<ethers.BigNumber> {
const priceAndRefundInfo = await getPriceAndRefundInfo(sourceChain, targetChain, gasAmount, optionalParams);
return priceAndRefundInfo[0];
}
export async function getWormholeRelayerInfo(
sourceChain: ChainName,
sourceTransaction: string,
infoRequest?: InfoRequestParams
): Promise<DeliveryInfo> {
const environment = infoRequest?.environment || "MAINNET";
const sourceChainProvider =
infoRequest?.sourceChainProvider ||
getDefaultProvider(environment, sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
);
const receipt = await sourceChainProvider.getTransactionReceipt(
sourceTransaction
);
if (!receipt) throw Error("Transaction has not been mined");
const bridgeAddress =
CONTRACTS[environment][sourceChain].core;
const wormholeRelayerAddress = infoRequest?.wormholeRelayerAddresses?.get(sourceChain) || getWormholeRelayerAddress(
sourceChain,
environment
);
if (!bridgeAddress || !wormholeRelayerAddress) {
throw Error(
`Invalid chain ID or network: Chain ${sourceChain}, ${environment}`
);
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(wormholeRelayerAddress, "ethereum"),
infoRequest?.wormholeRelayerWhMessageIndex
? infoRequest.wormholeRelayerWhMessageIndex
: 0
);
const { type, parsed } = parseWormholeLog(deliveryLog.log);
const instruction = parsed as DeliveryInstruction;
const targetChainId = instruction.targetChainId as ChainId;
if (!isChain(targetChainId)) throw Error(`Invalid Chain: ${targetChainId}`);
const targetChain = CHAIN_ID_TO_NAME[targetChainId];
const targetChainProvider =
infoRequest?.targetChainProviders?.get(targetChain) ||
getDefaultProvider(environment, targetChain);
if (!targetChainProvider) {
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
);
}
const [blockStartNumber, blockEndNumber] =
infoRequest?.targetChainBlockRanges?.get(targetChain) ||
getBlockRange(targetChainProvider);
const targetChainStatus = await getWormholeRelayerInfoBySourceSequence(
environment,
targetChain,
targetChainProvider,
sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber,
infoRequest?.wormholeRelayerAddresses?.get(targetChain) || getWormholeRelayerAddress(
targetChain,
environment
)
);
return {
type: RelayerPayloadId.Delivery,
sourceChain: sourceChain,
sourceTransactionHash: sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(
deliveryLog.sequence
).toNumber(),
deliveryInstruction: instruction,
targetChainStatus,
};
}
export async function resendRaw(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
environment: Network,
vaaKey: VaaKey,
newGasLimit: BigNumber | number,
newReceiverValue: BigNumber | number,
deliveryProviderAddress: string,
overrides?: ethers.PayableOverrides
) {
const provider = signer.provider;
if (!provider) throw Error("No provider on signer");
const wormholeRelayer = getWormholeRelayer(sourceChain, environment, signer);
return wormholeRelayer.resendToEvm(
vaaKeyToVaaKeyStruct(vaaKey),
CHAINS[targetChain],
newReceiverValue,
newGasLimit,
deliveryProviderAddress,
overrides
);
}
export async function resend(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
environment: Network,
vaaKey: VaaKey,
newGasLimit: BigNumber | number,
newReceiverValue: BigNumber | number,
deliveryProviderAddress: string,
wormholeRPCs: string[],
overrides: ethers.PayableOverrides,
isNode?: boolean,
) {
const sourceChainId = CHAINS[sourceChain];
const targetChainId = CHAINS[targetChain];
const originalVAA = await getVAA(wormholeRPCs, vaaKey, isNode);
if (!originalVAA) throw Error("orignal VAA not found");
const originalVAAparsed = parseWormholeRelayerSend(
parseVaa(Buffer.from(originalVAA)).payload
);
if (!originalVAAparsed) throw Error("orignal VAA not a valid delivery VAA.");
const [originalExecutionInfo,] = parseEVMExecutionInfoV1(originalVAAparsed.encodedExecutionInfo, 0);
const originalGasLimit = originalExecutionInfo.gasLimit;
const originalRefund = originalExecutionInfo.targetChainRefundPerGasUnused;
const originalReceiverValue = originalVAAparsed.requestedReceiverValue;
const originalTargetChain = originalVAAparsed.targetChainId;
if (originalTargetChain != targetChainId) {
throw Error(
`Target chain of original VAA (${originalTargetChain}) does not match target chain of resend (${targetChainId})`
);
}
if (newReceiverValue < originalReceiverValue) {
throw Error(
`New receiver value too low. Minimum is ${originalReceiverValue.toString()}`
);
}
if (newGasLimit < originalGasLimit) {
throw Error(
`New gas limit too low. Minimum is ${originalReceiverValue.toString()}`
);
}
const wormholeRelayer = getWormholeRelayer(sourceChain, environment, signer);
const deliveryProvider = getDeliveryProvider(
deliveryProviderAddress,
signer.provider!
);
const [deliveryPrice, refundPerUnitGas]: [BigNumber, BigNumber] = await wormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"](targetChainId, newReceiverValue || 0, newGasLimit, deliveryProviderAddress);
const value = await (overrides?.value || 0);
if(!deliveryPrice.eq(value)) {
throw new Error(`Expected a payment of ${deliveryPrice.toString()} wei; received ${value.toString()} wei`);
}
if (refundPerUnitGas < originalRefund) {
throw Error(
`New refund per unit gas too low. Minimum is ${originalRefund.toString()}.`
);
}
return resendRaw(
signer,
sourceChain,
targetChain,
environment,
vaaKey,
newGasLimit,
newReceiverValue,
deliveryProviderAddress,
overrides
);
}
export async function getVAA(
wormholeRPCs: string[],
vaaKey: VaaKey,
isNode?: boolean
): Promise<Uint8Array> {
const vaa = await getSignedVAAWithRetry(
wormholeRPCs,
vaaKey.chainId! as ChainId,
vaaKey.emitterAddress!.toString("hex"),
vaaKey.sequence!.toBigInt().toString(),
isNode
? {
transport: NodeHttpTransport(),
}
: {},
2000,
4
);
return vaa.vaaBytes;
}

View File

@ -0,0 +1,128 @@
import { ethers, BigNumber } from "ethers";
import { ChainName, CHAINS, Network } from "../../utils";
import { parseVaa } from "../../vaa";
import { getWormholeRelayer } from "../consts";
import {
VaaKey,
parseWormholeRelayerSend,
parseEVMExecutionInfoV1,
} from "../structs";
import { vaaKeyToVaaKeyStruct, getDeliveryProvider, getVAA } from "./helpers";
export async function resendRaw(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
environment: Network,
vaaKey: VaaKey,
newGasLimit: BigNumber | number,
newReceiverValue: BigNumber | number,
deliveryProviderAddress: string,
overrides?: ethers.PayableOverrides
) {
const provider = signer.provider;
if (!provider) throw Error("No provider on signer");
const wormholeRelayer = getWormholeRelayer(sourceChain, environment, signer);
return wormholeRelayer.resendToEvm(
vaaKeyToVaaKeyStruct(vaaKey),
CHAINS[targetChain],
newReceiverValue,
newGasLimit,
deliveryProviderAddress,
overrides
);
}
export async function resend(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
environment: Network,
vaaKey: VaaKey,
newGasLimit: BigNumber | number,
newReceiverValue: BigNumber | number,
deliveryProviderAddress: string,
wormholeRPCs: string[],
overrides: ethers.PayableOverrides,
isNode?: boolean
) {
const targetChainId = CHAINS[targetChain];
const originalVAA = await getVAA(wormholeRPCs, vaaKey, isNode);
if (!originalVAA) throw Error("orignal VAA not found");
const originalVAAparsed = parseWormholeRelayerSend(
parseVaa(Buffer.from(originalVAA)).payload
);
if (!originalVAAparsed) throw Error("orignal VAA not a valid delivery VAA.");
const [originalExecutionInfo] = parseEVMExecutionInfoV1(
originalVAAparsed.encodedExecutionInfo,
0
);
const originalGasLimit = originalExecutionInfo.gasLimit;
const originalRefund = originalExecutionInfo.targetChainRefundPerGasUnused;
const originalReceiverValue = originalVAAparsed.requestedReceiverValue;
const originalTargetChain = originalVAAparsed.targetChainId;
if (originalTargetChain != targetChainId) {
throw Error(
`Target chain of original VAA (${originalTargetChain}) does not match target chain of resend (${targetChainId})`
);
}
if (newReceiverValue < originalReceiverValue) {
throw Error(
`New receiver value too low. Minimum is ${originalReceiverValue.toString()}`
);
}
if (newGasLimit < originalGasLimit) {
throw Error(
`New gas limit too low. Minimum is ${originalReceiverValue.toString()}`
);
}
const wormholeRelayer = getWormholeRelayer(sourceChain, environment, signer);
const deliveryProvider = getDeliveryProvider(
deliveryProviderAddress,
signer.provider!
);
const [deliveryPrice, refundPerUnitGas]: [BigNumber, BigNumber] =
await wormholeRelayer[
"quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"
](
targetChainId,
newReceiverValue || 0,
newGasLimit,
deliveryProviderAddress
);
const value = await (overrides?.value || 0);
if (!deliveryPrice.eq(value)) {
throw new Error(
`Expected a payment of ${deliveryPrice.toString()} wei; received ${value.toString()} wei`
);
}
if (refundPerUnitGas < originalRefund) {
throw Error(
`New refund per unit gas too low. Minimum is ${originalRefund.toString()}.`
);
}
return resendRaw(
signer,
sourceChain,
targetChain,
environment,
vaaKey,
newGasLimit,
newReceiverValue,
deliveryProviderAddress,
overrides
);
}

View File

@ -0,0 +1,114 @@
import { ethers, BigNumber } from "ethers";
import { ethers_contracts } from "../..";
import { VaaKeyStruct } from "../../ethers-contracts/MockRelayerIntegration";
import {
ChainId,
ChainName,
CHAINS,
Network,
tryNativeToHexString,
} from "../../utils";
import { getWormholeRelayerAddress } from "../consts";
export type SendOptionalParams = {
environment?: Network;
receiverValue?: ethers.BigNumberish;
paymentForExtraReceiverValue?: ethers.BigNumberish;
additionalVaas?: [
{
chainId?: ChainId;
emitterAddress: string;
sequenceNumber: ethers.BigNumberish;
}
];
deliveryProviderAddress?: string;
consistencyLevel?: ethers.BigNumberish;
refundChainId?: ChainId;
refundAddress?: string;
relayParameters?: ethers.BytesLike;
};
export async function sendToEvm(
signer: ethers.Signer,
sourceChain: ChainName,
targetChain: ChainName,
targetAddress: string,
payload: ethers.BytesLike,
gasLimit: BigNumber | number,
overrides?: ethers.PayableOverrides,
sendOptionalParams?: SendOptionalParams
): Promise<ethers.providers.TransactionResponse> {
const sourceChainId = CHAINS[sourceChain];
const targetChainId = CHAINS[targetChain];
const environment = sendOptionalParams?.environment || "MAINNET";
const wormholeRelayerAddress = getWormholeRelayerAddress(
sourceChain,
environment
);
const sourceWormholeRelayer =
ethers_contracts.IWormholeRelayer__factory.connect(
wormholeRelayerAddress,
signer
);
const refundLocationExists =
sendOptionalParams?.refundChainId !== undefined &&
sendOptionalParams?.refundAddress !== undefined;
const defaultDeliveryProviderAddress =
await sourceWormholeRelayer.getDefaultDeliveryProvider();
// Using the most general 'send' function in IWormholeRelayer
// Inputs:
// targetChainId, targetAddress, refundChainId, refundAddress, maxTransactionFee, receiverValue, payload, vaaKeys,
// consistencyLevel, deliveryProviderAddress, relayParameters
const [deliveryPrice]: [BigNumber, BigNumber] = await sourceWormholeRelayer[
"quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"
](
targetChainId,
sendOptionalParams?.receiverValue || 0,
gasLimit,
sendOptionalParams?.deliveryProviderAddress ||
defaultDeliveryProviderAddress
);
const value = await (overrides?.value || 0);
const totalPrice = deliveryPrice.add(
sendOptionalParams?.paymentForExtraReceiverValue || 0
);
if (!totalPrice.eq(value)) {
throw new Error(
`Expected a payment of ${totalPrice.toString()} wei; received ${value.toString()} wei`
);
}
const tx = sourceWormholeRelayer.sendToEvm(
targetChainId, // targetChainId
targetAddress, // targetAddress
payload,
sendOptionalParams?.receiverValue || 0, // receiverValue
sendOptionalParams?.paymentForExtraReceiverValue || 0, // payment for extra receiverValue
gasLimit,
(refundLocationExists && sendOptionalParams?.refundChainId) ||
sourceChainId, // refundChainId
(refundLocationExists &&
sendOptionalParams?.refundAddress &&
sendOptionalParams?.refundAddress) ||
signer.getAddress(), // refundAddress
sendOptionalParams?.deliveryProviderAddress ||
defaultDeliveryProviderAddress, // deliveryProviderAddress
sendOptionalParams?.additionalVaas
? sendOptionalParams.additionalVaas.map(
(additionalVaa): VaaKeyStruct => ({
chainId: additionalVaa.chainId || sourceChainId,
emitterAddress: Buffer.from(
tryNativeToHexString(additionalVaa.emitterAddress, "ethereum"),
"hex"
),
sequence: BigNumber.from(additionalVaa.sequenceNumber || 0),
})
)
: [], // vaaKeys
sendOptionalParams?.consistencyLevel || 15, // consistencyLevel
overrides
);
return tx;
}