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:
parent
55a3ba684e
commit
a1e9d15187
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from "./structs";
|
||||
export * from "./consts";
|
||||
export * from "./relayer/relayer";
|
||||
export * from "./relayer";
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export * from "./helpers";
|
||||
export * from "./deliver";
|
||||
export * from "./info";
|
||||
export * from "./resend";
|
||||
export * from "./send";
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue