Compiles w/o redelivery

This commit is contained in:
Joe Howarth 2023-04-03 13:33:02 -05:00
parent d1d0abc226
commit 246bf7c387
17 changed files with 1190 additions and 1432 deletions

View File

@ -18,9 +18,7 @@ const DEVNET = [
const MAINNET: any[] = []
type ENV = "mainnet" | "testnet"
export function getCoreRelayerAddressNative(chainId: ChainId, env: Network): string {
export function getWormholeRelayerAddress(chainId: ChainId, env: Network): string {
if (env == "TESTNET") {
const address = TESTNET.find((x) => x.chainId == chainId)?.coreRelayerAddress
if (!address) {
@ -44,12 +42,12 @@ export function getCoreRelayerAddressNative(chainId: ChainId, env: Network): str
}
}
export function getCoreRelayer(
export function getWormholeRelayer(
chainId: ChainId,
env: Network,
provider: ethers.providers.Provider
): CoreRelayer {
const thisChainsRelayer = getCoreRelayerAddressNative(chainId, env)
const thisChainsRelayer = getWormholeRelayerAddress(chainId, env)
const contract = CoreRelayer__factory.connect(thisChainsRelayer, provider)
return contract
}

View File

@ -0,0 +1,157 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
Network,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { BigNumber, ContractReceipt, ethers } from "ethers"
import { getWormholeRelayer, RPCS_BY_CHAIN } from "../consts"
import {
parseWormholeRelayerPayloadType,
RelayerPayloadId,
parseWormholeRelayerSend,
DeliveryInstructionsContainer,
DeliveryStatus,
} from "../structs"
import { DeliveryEvent } from "../ethers-contracts/CoreRelayer"
type DeliveryTargetInfo = {
status: DeliveryStatus | string
deliveryTxHash: string | null
vaaHash: string | null
sourceChain: number | null
sourceVaaSequence: BigNumber | null
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId
parsed: DeliveryInstructionsContainer | string
} {
const abi = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);",
]
const iface = new ethers.utils.Interface(abi)
const parsed = iface.parseLog(log)
const payload = Buffer.from(parsed.args.payload.substring(2), "hex")
const type = parseWormholeRelayerPayloadType(payload)
if (type == RelayerPayloadId.Delivery) {
return { type, parsed: parseWormholeRelayerSend(payload) }
} else {
throw Error("Invalid wormhole log")
}
}
export function printChain(chainId: number) {
return `${CHAIN_ID_TO_NAME[chainId as ChainId]} (Chain ${chainId})`
}
export function getDefaultProvider(network: Network, chainId: ChainId) {
return new ethers.providers.StaticJsonRpcProvider(
RPCS_BY_CHAIN[network][CHAIN_ID_TO_NAME[chainId]]
)
}
export function getBlockRange(
provider: ethers.providers.Provider,
timestamp?: number
): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
return [-2040, "latest"]
}
export async function getWormholeRelayerDeliveryEventsBySourceSequence(
environment: Network,
targetChain: ChainId,
targetChainProvider: ethers.providers.Provider,
sourceChain: number,
sourceVaaSequence: BigNumber,
blockStartNumber: ethers.providers.BlockTag,
blockEndNumber: ethers.providers.BlockTag
): Promise<DeliveryTargetInfo[]> {
const coreRelayer = getWormholeRelayer(targetChain, environment, targetChainProvider)
const deliveryEvents = coreRelayer.filters.Delivery(
null,
sourceChain,
sourceVaaSequence
)
// There is a max limit on RPCs sometimes for how many blocks to query
return await transformDeliveryEvents(
await coreRelayer.queryFilter(deliveryEvents, blockStartNumber, blockEndNumber),
targetChainProvider
)
}
export function deliveryStatus(status: number) {
switch (status) {
case 0:
return DeliveryStatus.DeliverySuccess
case 1:
return DeliveryStatus.ReceiverFailure
case 2:
return DeliveryStatus.ForwardRequestFailure
case 3:
return DeliveryStatus.ForwardRequestSuccess
case 4:
return DeliveryStatus.InvalidRedelivery
default:
return DeliveryStatus.ThisShouldNeverHappen
}
}
async function transformDeliveryEvents(
events: DeliveryEvent[],
targetProvider: ethers.providers.Provider
): Promise<DeliveryTargetInfo[]> {
return Promise.all(
events.map(async (x) => {
return {
status: deliveryStatus(x.args[4]),
deliveryTxHash: x.transactionHash,
vaaHash: x.args[3],
sourceVaaSequence: x.args[2],
sourceChain: x.args[1],
}
})
)
}
export function getWormholeRelayerLog(
receipt: ContractReceipt,
bridgeAddress: string,
emitterAddress: string,
index: number
): { log: ethers.providers.Log; sequence: string } {
const bridgeLogs = receipt.logs.filter((l) => {
return l.address === bridgeAddress
})
if (bridgeLogs.length == 0) {
throw Error("No core contract interactions found for this transaction.")
}
const parsed = bridgeLogs.map((bridgeLog) => {
const log = Implementation__factory.createInterface().parseLog(bridgeLog)
return {
sequence: log.args[1].toString(),
nonce: log.args[2].toString(),
emitterAddress: tryNativeToHexString(log.args[0].toString(), "ethereum"),
log: bridgeLog,
}
})
const filtered = parsed.filter((x) => x.emitterAddress == emitterAddress.toLowerCase())
if (filtered.length == 0) {
throw Error("No CoreRelayer contract interactions found for this transaction.")
}
if (index >= filtered.length) {
throw Error("Specified delivery index is out of range.")
} else {
return {
log: filtered[index].log,
sequence: filtered[index].sequence,
}
}
}

View File

@ -1 +1 @@
export * from "./status"
export * from "./main"

View File

@ -0,0 +1,224 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
isChain,
CONTRACTS,
tryNativeToHexString,
Network,
} from "@certusone/wormhole-sdk"
import { BigNumber, ethers } from "ethers"
import { getWormholeRelayerAddress } from "../consts"
import {
RelayerPayloadId,
DeliveryInstruction,
DeliveryInstructionsContainer,
MessageInfoType,
DeliveryStatus,
} from "../structs"
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerDeliveryEventsBySourceSequence,
} from "./helpers"
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<
number,
[ethers.providers.BlockTag, ethers.providers.BlockTag]
>
coreRelayerWhMessageIndex?: number
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId
sourceTransactionHash: string
sourceDeliverySequenceNumber: number
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export function printWormholeRelayerInfo(info: DeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info))
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo): string {
let stringifiedInfo = ""
if (info.type == RelayerPayloadId.Delivery) {
stringifiedInfo += `Found delivery request in transaction ${
info.sourceTransactionHash
} on ${printChain(info.sourceChainId)}\n`
const numMsgs = info.deliveryInstructionsContainer.messages.length
stringifiedInfo += `\nThe following ${numMsgs} messages were requested to be relayed:\n`
stringifiedInfo += info.deliveryInstructionsContainer.messages.map((msgInfo, i) => {
let result = ""
result += `\n(Message ${i}): `
if (msgInfo.payloadType == MessageInfoType.EMITTER_SEQUENCE) {
result += `Message with emitter address ${msgInfo.emitterAddress?.toString(
"hex"
)} and sequence number ${msgInfo.sequence}\n`
} else if (msgInfo.payloadType == MessageInfoType.VAAHASH) {
result += `Message with VAA Hash ${msgInfo.vaaHash?.toString("hex")}\n`
} else {
result += `Message not specified correctly\n`
}
})
const length = info.deliveryInstructionsContainer.instructions.length
stringifiedInfo += `\nMessages were requested to be sent to ${length} destination${
length == 1 ? "" : "s"
}:\n`
stringifiedInfo +=
info.deliveryInstructionsContainer.instructions
.map((instruction: DeliveryInstruction, i) => {
let result = ""
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString(
"hex"
)} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0)
? `Amount to pass into target address: ${
instruction.receiverValueTarget
} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n`
: ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString(
"hex"
)}\n`
result += info.targetChainStatuses[i].events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${e.status}${
e.transactionHash
? ` (${targetChainName} transaction hash: ${e.transactionHash})`
: ""
}`
)
.join("\n")
return result
})
.join("\n") + "\n"
}
return stringifiedInfo
}
export async function getWormholeRelayerInfo(
infoRequest: InfoRequest
): Promise<DeliveryInfo > {
const sourceChainProvider =
infoRequest.sourceChainProvider ||
getDefaultProvider(infoRequest.environment, infoRequest.sourceChain)
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getWormholeRelayerAddress(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(
`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`
)
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(
deliveryInstructionsContainer.instructions.map(
async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId
if (!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber)
const [blockStartNumber, blockEndNumber] =
infoRequest.targetChainBlockRanges?.get(targetChain) ||
getBlockRange(targetChainProvider, sourceChainBlock.timestamp)
const deliveryEvents = await getWormholeRelayerDeliveryEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(
targetChain
)} within blocks ${blockStartNumber} to ${blockEndNumber}.`
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber)
const blockEnd = await targetChainProvider.getBlock(blockEndNumber)
status = `Delivery didn't happen on ${printChain(
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) {}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e) => ({
status: e.status,
transactionHash: e.deliveryTxHash,
})),
}
}
)
)
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
deliveryInstructionsContainer,
targetChainStatuses,
}
}

View File

@ -1,355 +0,0 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
CHAINS,
isChain,
CONTRACTS,
getSignedVAAWithRetry,
Network,
parseVaa,
ParsedVaa,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-web/lib/cjs/publicrpc/v1/publicrpc"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { BigNumber, ContractReceipt, ethers, providers } from "ethers"
import {
getCoreRelayer,
getCoreRelayerAddressNative,
RPCS_BY_CHAIN,
GUARDIAN_RPC_HOSTS,
} from "../consts"
import {
parsePayloadType,
RelayerPayloadId,
parseDeliveryInstructionsContainer,
parseRedeliveryByTxHashInstruction,
DeliveryInstruction,
DeliveryInstructionsContainer,
RedeliveryByTxHashInstruction,
ExecutionParameters,
} from "../structs"
import { DeliveryEvent } from "../ethers-contracts/CoreRelayer"
enum DeliveryStatus {
WaitingForVAA = "Waiting for VAA",
PendingDelivery = "Pending Delivery",
DeliverySuccess = "Delivery Success",
ReceiverFailure = "Receiver Failure",
InvalidRedelivery = "Invalid Redelivery",
ForwardRequestSuccess = "Forward Request Success",
ForwardRequestFailure = "Forward Request Failure",
ThisShouldNeverHappen = "This should never happen. Contact Support.",
DeliveryDidntHappenWithinRange = "Delivery didn't happen within given block range",
}
type DeliveryTargetInfo = {
status: DeliveryStatus | string
deliveryTxHash: string | null
vaaHash: string | null
sourceChain: number | null
sourceVaaSequence: BigNumber | null
}
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<number, [ethers.providers.BlockTag, ethers.providers.BlockTag]>
coreRelayerWhMessageIndex?: number
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId
parsed: DeliveryInstructionsContainer | RedeliveryByTxHashInstruction | string
} {
const abi = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);",
]
const iface = new ethers.utils.Interface(abi)
const parsed = iface.parseLog(log)
const payload = Buffer.from(parsed.args.payload.substring(2), "hex")
const type = parsePayloadType(payload)
if (type == RelayerPayloadId.Delivery) {
return { type, parsed: parseDeliveryInstructionsContainer(payload) }
} else if (type == RelayerPayloadId.Redelivery) {
return { type, parsed: parseRedeliveryByTxHashInstruction(payload) }
} else {
throw Error("Invalid wormhole log");
}
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId,
sourceTransactionHash: string
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export type RedeliveryInfo = {
type: RelayerPayloadId.Redelivery
redeliverySourceChainId: ChainId,
redeliverySourceTransactionHash: string
redeliveryInstruction: RedeliveryByTxHashInstruction
}
export function printChain(chainId: number) {
return `${CHAIN_ID_TO_NAME[chainId as ChainId]} (Chain ${chainId})`
}
export function printInfo(info: DeliveryInfo | RedeliveryInfo) {
console.log(stringifyInfo(info));
}
export function stringifyInfo(info: DeliveryInfo | RedeliveryInfo): string {
let stringifiedInfo = "";
if(info.type==RelayerPayloadId.Redelivery) {
stringifiedInfo += (`Found Redelivery request in transaction ${info.redeliverySourceTransactionHash} on ${printChain(info.redeliverySourceChainId)}\n`)
stringifiedInfo += (`Original Delivery Source Chain: ${printChain(info.redeliveryInstruction.sourceChain)}\n`)
stringifiedInfo += (`Original Delivery Source Transaction Hash: 0x${info.redeliveryInstruction.sourceTxHash.toString("hex")}\n`)
//stringifiedInfo += (`Original Delivery Source Nonce: ${info.redeliveryInstruction.sourceNonce}\n`)
stringifiedInfo += (`Target Chain: ${printChain(info.redeliveryInstruction.targetChain)}\n`)
stringifiedInfo += (`multisendIndex: ${info.redeliveryInstruction.multisendIndex}\n`)
//stringifiedInfo += (`deliveryIndex: ${info.redeliveryInstruction.deliveryIndex}\n`)
stringifiedInfo += (`New max amount (in target chain currency) to use for gas: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New amount (in target chain currency) to pass into target address: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New target chain gas limit: ${info.redeliveryInstruction.executionParameters.gasLimit}\n`)
stringifiedInfo += (`Relay Provider Delivery Address: 0x${info.redeliveryInstruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`)
} else if(info.type==RelayerPayloadId.Delivery) {
stringifiedInfo += (`Found delivery request in transaction ${info.sourceTransactionHash} on ${printChain(info.sourceChainId)}\n`)
stringifiedInfo += ((info.deliveryInstructionsContainer.sufficientlyFunded ? "The delivery was funded\n" : "** NOTE: The delivery was NOT sufficiently funded. You did not have enough leftover funds to perform the forward **\n"))
const length = info.deliveryInstructionsContainer.instructions.length;
stringifiedInfo += (`\nMessages were requested to be sent to ${length} destination${length == 1 ? "" : "s"}:\n`)
stringifiedInfo += (info.deliveryInstructionsContainer.instructions.map((instruction: DeliveryInstruction, i) => {
let result = "";
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId];
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString("hex")} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0) ? `Amount to pass into target address: ${instruction.receiverValueTarget} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n` : ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`
result += info.targetChainStatuses[i].events.map((e, i) => (`Delivery attempt ${i+1}: ${e.status}${e.transactionHash ? ` (${targetChainName} transaction hash: ${e.transactionHash})` : ""}`)).join("\n")
return result;
}).join("\n")) + "\n"
}
return stringifiedInfo
}
function getDefaultProvider(network: Network, chainId: ChainId) {
return new ethers.providers.StaticJsonRpcProvider(
RPCS_BY_CHAIN[network][CHAIN_ID_TO_NAME[chainId]]
)
}
export async function getDeliveryInfoBySourceTx(
infoRequest: InfoRequest
): Promise<DeliveryInfo | RedeliveryInfo> {
const sourceChainProvider =
infoRequest.sourceChainProvider || getDefaultProvider(infoRequest.environment, infoRequest.sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getCoreRelayerAddressNative(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`)
}
const deliveryLog = findLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0,
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
if (type == RelayerPayloadId.Redelivery) {
const redeliveryInstruction = parsed as RedeliveryByTxHashInstruction
return {
type,
redeliverySourceChainId: infoRequest.sourceChain,
redeliverySourceTransactionHash: infoRequest.sourceTransaction,
redeliveryInstruction,
}
}
/* Potentially use 'guardianRPCHosts' to get status of VAA; code in comments at end [1] */
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(deliveryInstructionsContainer.instructions.map(async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId;
if(!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber);
const [blockStartNumber, blockEndNumber] = infoRequest.targetChainBlockRanges?.get(targetChain) || getBlockRange(targetChainProvider, sourceChainBlock.timestamp);
const deliveryEvents = await pullEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(targetChain)} within blocks ${blockStartNumber} to ${blockEndNumber}.`;
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber);
const blockEnd = await targetChainProvider.getBlock(blockEndNumber);
status = `Delivery didn't happen on ${printChain(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) {
}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e)=>({status: e.status, transactionHash: e.deliveryTxHash}))
}
}))
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
deliveryInstructionsContainer,
targetChainStatuses
}
}
function getBlockRange(provider: ethers.providers.Provider, timestamp?: number): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
return [-2040, "latest"]
}
async function pullEventsBySourceSequence(
environment: Network,
targetChain: ChainId,
targetChainProvider: ethers.providers.Provider,
sourceChain: number,
sourceVaaSequence: BigNumber,
blockStartNumber: ethers.providers.BlockTag,
blockEndNumber: ethers.providers.BlockTag
): Promise<DeliveryTargetInfo[]> {
const coreRelayer = getCoreRelayer(targetChain, environment, targetChainProvider)
//TODO These compile errors on sourceChain look like an ethers bug
const deliveryEvents = coreRelayer.filters.Delivery(
null,
sourceChain,
sourceVaaSequence
)
// There is a max limit on RPCs sometimes for how many blocks to query
return await transformDeliveryEvents(
await coreRelayer.queryFilter(deliveryEvents, blockStartNumber, blockEndNumber),
targetChainProvider
)
}
function deliveryStatus(status: number) {
switch (status) {
case 0:
return DeliveryStatus.DeliverySuccess
case 1:
return DeliveryStatus.ReceiverFailure
case 2:
return DeliveryStatus.ForwardRequestFailure
case 3:
return DeliveryStatus.ForwardRequestSuccess
case 4:
return DeliveryStatus.InvalidRedelivery
default:
return DeliveryStatus.ThisShouldNeverHappen
}
}
async function transformDeliveryEvents(
events: DeliveryEvent[],
targetProvider: ethers.providers.Provider
): Promise<DeliveryTargetInfo[]> {
return Promise.all(
events.map(async (x) => {
return {
status: deliveryStatus(x.args[4]),
deliveryTxHash: x.transactionHash,
vaaHash: x.args[3],
sourceVaaSequence: x.args[2],
sourceChain: x.args[1],
}
})
)
}
export function findLog(
receipt: ContractReceipt,
bridgeAddress: string,
emitterAddress: string,
index: number,
): { log: ethers.providers.Log; sequence: string } {
const bridgeLogs = receipt.logs.filter((l) => {
return l.address === bridgeAddress
})
if (bridgeLogs.length == 0) {
throw Error("No core contract interactions found for this transaction.")
}
const parsed = bridgeLogs.map((bridgeLog) => {
const log = Implementation__factory.createInterface().parseLog(bridgeLog)
return {
sequence: log.args[1].toString(),
nonce: log.args[2].toString(),
emitterAddress: tryNativeToHexString(log.args[0].toString(), "ethereum"),
log: bridgeLog,
}
})
const filtered = parsed.filter(
(x) =>
x.emitterAddress == emitterAddress.toLowerCase()
)
if (filtered.length == 0) {
throw Error("No CoreRelayer contract interactions found for this transaction.")
}
if (index >= filtered.length) {
throw Error("Specified delivery index is out of range.")
} else {
return {
log: filtered[index].log,
sequence: filtered[index].sequence,
}
}
}

View File

@ -3,20 +3,25 @@ import { arrayify } from "ethers/lib/utils"
export enum RelayerPayloadId {
Delivery = 1,
Redelivery = 2,
// DeliveryStatus = 3,
}
export enum DeliveryStatus {
WaitingForVAA = "Waiting for VAA",
PendingDelivery = "Pending Delivery",
DeliverySuccess = "Delivery Success",
ReceiverFailure = "Receiver Failure",
InvalidRedelivery = "Invalid Redelivery",
ForwardRequestSuccess = "Forward Request Success",
ForwardRequestFailure = "Forward Request Failure",
ThisShouldNeverHappen = "This should never happen. Contact Support.",
DeliveryDidntHappenWithinRange = "Delivery didn't happen within given block range",
}
export interface DeliveryInstructionsContainer {
payloadId: number // 1
sufficientlyFunded: boolean
instructions: DeliveryInstruction[]
messages: MessageInfo[]
}
export enum MessageInfoType {
EmitterSequence = 0,
VaaHash = 1,
instructions: DeliveryInstruction[]
}
export interface MessageInfo {
@ -41,19 +46,12 @@ export interface ExecutionParameters {
providerDeliveryAddress: Buffer
}
export interface RedeliveryByTxHashInstruction {
payloadId: number //2
sourceChain: number
sourceTxHash: Buffer
deliveryVaaSequence: BigNumber
targetChain: number
multisendIndex: number
newMaximumRefundTarget: BigNumber
newReceiverValueTarget: BigNumber
executionParameters: ExecutionParameters
export enum MessageInfoType {
EMITTER_SEQUENCE = 0,
VAAHASH = 1,
}
export function parsePayloadType(
export function parseWormholeRelayerPayloadType(
stringPayload: string | Buffer | Uint8Array
): RelayerPayloadId {
const payload =
@ -64,9 +62,7 @@ export function parsePayloadType(
return payload[0]
}
export function parseDeliveryInstructionsContainer(
bytes: Buffer
): DeliveryInstructionsContainer {
export function parseWormholeRelayerSend(bytes: Buffer): DeliveryInstructionsContainer {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Delivery) {
@ -76,11 +72,19 @@ export function parseDeliveryInstructionsContainer(
}
idx += 1
const sufficientlyFunded = Boolean(bytes.readUInt8(idx))
const numMessages = bytes.readUInt8(idx)
idx += 1
let messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
const res = parseMessageInfo(bytes, idx)
idx = res[1]
messages.push(res[0])
}
const numInstructions = bytes.readUInt8(idx)
idx += 1
let instructions = [] as DeliveryInstruction[]
for (let i = 0; i < numInstructions; ++i) {
const targetChain = bytes.readUInt16BE(idx)
@ -97,8 +101,9 @@ export function parseDeliveryInstructionsContainer(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 32)
)
idx += 32
const executionParameters = parseExecutionParameters(bytes, [idx])
idx += 37
let res = parseWormholeRelayerExecutionParameters(bytes, idx)
const executionParameters = res[0]
idx = res[1]
instructions.push(
// dumb typechain format
{
@ -112,106 +117,59 @@ export function parseDeliveryInstructionsContainer(
)
}
// parse the manifest
const numMessages = bytes.readUInt8(idx)
idx += 1
const idxPtr: [number] = [idx]
const messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
messages.push(parseMessageInfo(bytes, idxPtr))
}
return {
payloadId,
sufficientlyFunded,
instructions,
messages,
instructions,
}
}
function parseMessageInfo(bytes: Buffer, idxPtr: [number]): MessageInfo {
let idx = idxPtr[0]
const payloadType = bytes.readUInt8(idx) as MessageInfoType
switch (payloadType) {
case MessageInfoType.EmitterSequence:
const emitterAddress = bytes.slice(idx, idx + 32)
idx += 32
const sequence = ethers.BigNumber.from(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 32)
)
idx += 8
idxPtr[0] = idx
return { emitterAddress, sequence, payloadType }
case MessageInfoType.VaaHash:
const vaaHash = bytes.slice(idx, idx + 32)
idx += 32
idxPtr[0] = idx
return { vaaHash, payloadType }
}
}
export function parseRedeliveryByTxHashInstruction(
bytes: Buffer
): RedeliveryByTxHashInstruction {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Redelivery) {
throw new Error(
`Expected Delivery payload type (${RelayerPayloadId.Redelivery}), found: ${payloadId}`
function parseMessageInfo(bytes: Buffer, idx: number): [MessageInfo, number] {
const payloadId = bytes.readUint8(idx)
idx += 1
const payloadType = bytes.readUint8(idx) as MessageInfoType
idx += 1
if (payloadType == MessageInfoType.EMITTER_SEQUENCE) {
const emitterAddress = bytes.slice(idx, idx + 32)
idx += 32
const sequence = ethers.BigNumber.from(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 8)
)
}
idx += 1
const sourceChain = bytes.readUInt16BE(idx)
idx += 2
const sourceTxHash = bytes.slice(idx, idx + 32)
idx += 32
const deliveryVaaSequence = BigNumber.from(bytes.slice(idx, idx + 8))
idx += 8
const targetChain = bytes.readUInt16BE(idx)
idx += 2
const multisendIndex = bytes.readUint8(idx)
idx += 1
// note: confirmed that BigNumber.from(<Buffer>) assumes big endian
const newMaximumRefundTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const newReceiverValueTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const executionParameters = parseExecutionParameters(bytes, [idx])
idx += 37
return {
payloadId,
sourceChain,
sourceTxHash,
deliveryVaaSequence,
targetChain,
multisendIndex,
newMaximumRefundTarget,
newReceiverValueTarget,
executionParameters,
idx += 8
return [
{
payloadType,
emitterAddress,
sequence,
},
idx,
]
} else if (payloadType == MessageInfoType.VAAHASH) {
const vaaHash = bytes.slice(idx, idx + 32)
idx += 32
return [
{
payloadType,
vaaHash,
},
idx,
]
} else {
throw new Error("Unexpected MessageInfo payload type")
}
}
function parseExecutionParameters(
function parseWormholeRelayerExecutionParameters(
bytes: Buffer,
idxPtr: [number] = [0]
): ExecutionParameters {
let idx = idxPtr[0]
idx: number
): [ExecutionParameters, number] {
const version = bytes.readUInt8(idx)
idx += 1
const gasLimit = bytes.readUint32BE(idx)
idx += 4
const providerDeliveryAddress = bytes.slice(idx, idx + 32)
idx += 32
idxPtr[0] = idx
return { version, gasLimit, providerDeliveryAddress }
return [{ version, gasLimit, providerDeliveryAddress }, idx]
}
/*

View File

@ -3,39 +3,31 @@ import { Next } from "wormhole-relayer"
import {
IDelivery,
MessageInfoType,
parseDeliveryInstructionsContainer,
parsePayloadType,
parseRedeliveryByTxHashInstruction,
RelayerPayloadId,
RelayProvider__factory,
parseWormholeRelayerPayloadType,
parseWormholeRelayerSend,
} from "../pkgs/sdk/src"
import { EVMChainId } from "@certusone/wormhole-sdk"
import { GRContext } from "./app"
export async function processGenericRelayerVaa(ctx: GRContext, next: Next) {
const payloadId = parsePayloadType(ctx.vaa!.payload)
const payloadId = parseWormholeRelayerPayloadType(ctx.vaa!.payload)
// route payload types
switch (payloadId) {
case RelayerPayloadId.Delivery:
await processDelivery(ctx)
break
case RelayerPayloadId.Redelivery:
await processRedelivery(ctx)
break
if (payloadId != RelayerPayloadId.Delivery) {
ctx.logger.error(`Expected GR Delivery payload type, found ${payloadId}`)
throw new Error("Expected GR Delivery payload type")
}
await processDelivery(ctx)
await next()
}
async function processDelivery(ctx: GRContext) {
const chainId = ctx.vaa!.emitterChain as wh.EVMChainId
const payload = parseDeliveryInstructionsContainer(ctx.vaa!.payload)
if (payload.sufficientlyFunded) {
ctx.logger.info("Insufficiently funded delivery request, skipping")
return
}
const payload = parseWormholeRelayerSend(ctx.vaa!.payload)
if (
payload.messages.findIndex((m) => m.payloadType !== MessageInfoType.EmitterSequence)
payload.messages.findIndex((m) => m.payloadType !== MessageInfoType.EMITTER_SEQUENCE)
) {
throw new Error(`Only supports EmitterSequence MessageInfoType`)
}
@ -66,7 +58,8 @@ async function processDelivery(ctx: GRContext) {
relayerRefundAddress: wallet.address,
}
if (!(await relayProvider.approvedSender(wallet.address))) {
const isApprovedSender = await relayProvider.approvedSender(wallet.address)
if (!isApprovedSender) {
ctx.logger.warn(
`Approved sender not set correctly for chain ${chainId}, should be ${wallet.address}`
)
@ -86,63 +79,3 @@ async function processDelivery(ctx: GRContext) {
})
}
}
async function processRedelivery(ctx: GRContext) {
const chainId = ctx.vaa!.emitterChain as wh.EVMChainId
const redelivery = parseRedeliveryByTxHashInstruction(ctx.vaa!.payload)
const deliveryVAA = await ctx.fetchVaa(
chainId,
ctx.wormholeRelayer[chainId],
// @ts-ignore
redelivery.sequence
)
const deliveryInstructionsContainer = parseDeliveryInstructionsContainer(
deliveryVAA.payload
)
if (
deliveryInstructionsContainer.messages.findIndex(
(m) => m.payloadType !== MessageInfoType.EmitterSequence
)
) {
throw new Error(`Only supports EmitterSequence MessageInfoType`)
}
const fetchedVaas = await ctx.fetchVaas({
ids: deliveryInstructionsContainer.messages.map((m) => ({
emitterAddress: m.emitterAddress!,
emitterChain: chainId,
sequence: m.sequence!.toBigInt(),
})),
txHash: redelivery.sourceTxHash.toString("hex"), // todo: confirm this works
})
await ctx.wallets.onEVM(chainId, async ({ wallet }) => {
const relayProvider = RelayProvider__factory.connect(
ctx.relayProviders[chainId],
wallet
)
if (!(await relayProvider.approvedSender(wallet.address))) {
ctx.logger.warn(
`Approved sender not set correctly for chain ${chainId}, should be ${wallet.address}`
)
return
}
const { newReceiverValueTarget, newMaximumRefundTarget } = redelivery
const budget = newReceiverValueTarget.add(newMaximumRefundTarget).add(100)
const input: IDelivery.TargetRedeliveryByTxHashParamsSingleStruct = {
sourceEncodedVMs: [...fetchedVaas.map((v) => v.bytes), deliveryVAA.bytes],
originalEncodedDeliveryVAA: deliveryVAA.bytes,
redeliveryVM: ctx.vaaBytes!,
relayerRefundAddress: wallet.address,
}
await relayProvider
.redeliverSingle(input, { value: budget, gasLimit: 3000000 })
.then((x) => x.wait())
ctx.logger.info(`Redelivered instruction to chain ${chainId}`)
})
}

View File

@ -18,9 +18,7 @@ const DEVNET = [
const MAINNET: any[] = []
type ENV = "mainnet" | "testnet"
export function getCoreRelayerAddressNative(chainId: ChainId, env: Network): string {
export function getWormholeRelayerAddress(chainId: ChainId, env: Network): string {
if (env == "TESTNET") {
const address = TESTNET.find((x) => x.chainId == chainId)?.coreRelayerAddress
if (!address) {
@ -44,12 +42,12 @@ export function getCoreRelayerAddressNative(chainId: ChainId, env: Network): str
}
}
export function getCoreRelayer(
export function getWormholeRelayer(
chainId: ChainId,
env: Network,
provider: ethers.providers.Provider
): CoreRelayer {
const thisChainsRelayer = getCoreRelayerAddressNative(chainId, env)
const thisChainsRelayer = getWormholeRelayerAddress(chainId, env)
const contract = CoreRelayer__factory.connect(thisChainsRelayer, provider)
return contract
}

View File

@ -0,0 +1,157 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
Network,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { BigNumber, ContractReceipt, ethers } from "ethers"
import { getWormholeRelayer, RPCS_BY_CHAIN } from "../consts"
import {
parseWormholeRelayerPayloadType,
RelayerPayloadId,
parseWormholeRelayerSend,
DeliveryInstructionsContainer,
DeliveryStatus,
} from "../structs"
import { DeliveryEvent } from "../ethers-contracts/CoreRelayer"
type DeliveryTargetInfo = {
status: DeliveryStatus | string
deliveryTxHash: string | null
vaaHash: string | null
sourceChain: number | null
sourceVaaSequence: BigNumber | null
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId
parsed: DeliveryInstructionsContainer | string
} {
const abi = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);",
]
const iface = new ethers.utils.Interface(abi)
const parsed = iface.parseLog(log)
const payload = Buffer.from(parsed.args.payload.substring(2), "hex")
const type = parseWormholeRelayerPayloadType(payload)
if (type == RelayerPayloadId.Delivery) {
return { type, parsed: parseWormholeRelayerSend(payload) }
} else {
throw Error("Invalid wormhole log")
}
}
export function printChain(chainId: number) {
return `${CHAIN_ID_TO_NAME[chainId as ChainId]} (Chain ${chainId})`
}
export function getDefaultProvider(network: Network, chainId: ChainId) {
return new ethers.providers.StaticJsonRpcProvider(
RPCS_BY_CHAIN[network][CHAIN_ID_TO_NAME[chainId]]
)
}
export function getBlockRange(
provider: ethers.providers.Provider,
timestamp?: number
): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
return [-2040, "latest"]
}
export async function getWormholeRelayerDeliveryEventsBySourceSequence(
environment: Network,
targetChain: ChainId,
targetChainProvider: ethers.providers.Provider,
sourceChain: number,
sourceVaaSequence: BigNumber,
blockStartNumber: ethers.providers.BlockTag,
blockEndNumber: ethers.providers.BlockTag
): Promise<DeliveryTargetInfo[]> {
const coreRelayer = getWormholeRelayer(targetChain, environment, targetChainProvider)
const deliveryEvents = coreRelayer.filters.Delivery(
null,
sourceChain,
sourceVaaSequence
)
// There is a max limit on RPCs sometimes for how many blocks to query
return await transformDeliveryEvents(
await coreRelayer.queryFilter(deliveryEvents, blockStartNumber, blockEndNumber),
targetChainProvider
)
}
export function deliveryStatus(status: number) {
switch (status) {
case 0:
return DeliveryStatus.DeliverySuccess
case 1:
return DeliveryStatus.ReceiverFailure
case 2:
return DeliveryStatus.ForwardRequestFailure
case 3:
return DeliveryStatus.ForwardRequestSuccess
case 4:
return DeliveryStatus.InvalidRedelivery
default:
return DeliveryStatus.ThisShouldNeverHappen
}
}
async function transformDeliveryEvents(
events: DeliveryEvent[],
targetProvider: ethers.providers.Provider
): Promise<DeliveryTargetInfo[]> {
return Promise.all(
events.map(async (x) => {
return {
status: deliveryStatus(x.args[4]),
deliveryTxHash: x.transactionHash,
vaaHash: x.args[3],
sourceVaaSequence: x.args[2],
sourceChain: x.args[1],
}
})
)
}
export function getWormholeRelayerLog(
receipt: ContractReceipt,
bridgeAddress: string,
emitterAddress: string,
index: number
): { log: ethers.providers.Log; sequence: string } {
const bridgeLogs = receipt.logs.filter((l) => {
return l.address === bridgeAddress
})
if (bridgeLogs.length == 0) {
throw Error("No core contract interactions found for this transaction.")
}
const parsed = bridgeLogs.map((bridgeLog) => {
const log = Implementation__factory.createInterface().parseLog(bridgeLog)
return {
sequence: log.args[1].toString(),
nonce: log.args[2].toString(),
emitterAddress: tryNativeToHexString(log.args[0].toString(), "ethereum"),
log: bridgeLog,
}
})
const filtered = parsed.filter((x) => x.emitterAddress == emitterAddress.toLowerCase())
if (filtered.length == 0) {
throw Error("No CoreRelayer contract interactions found for this transaction.")
}
if (index >= filtered.length) {
throw Error("Specified delivery index is out of range.")
} else {
return {
log: filtered[index].log,
sequence: filtered[index].sequence,
}
}
}

View File

@ -1 +1 @@
export * from "./status"
export * from "./main"

View File

@ -0,0 +1,224 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
isChain,
CONTRACTS,
tryNativeToHexString,
Network,
} from "@certusone/wormhole-sdk"
import { BigNumber, ethers } from "ethers"
import { getWormholeRelayerAddress } from "../consts"
import {
RelayerPayloadId,
DeliveryInstruction,
DeliveryInstructionsContainer,
MessageInfoType,
DeliveryStatus,
} from "../structs"
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerDeliveryEventsBySourceSequence,
} from "./helpers"
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<
number,
[ethers.providers.BlockTag, ethers.providers.BlockTag]
>
coreRelayerWhMessageIndex?: number
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId
sourceTransactionHash: string
sourceDeliverySequenceNumber: number
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export function printWormholeRelayerInfo(info: DeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info))
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo): string {
let stringifiedInfo = ""
if (info.type == RelayerPayloadId.Delivery) {
stringifiedInfo += `Found delivery request in transaction ${
info.sourceTransactionHash
} on ${printChain(info.sourceChainId)}\n`
const numMsgs = info.deliveryInstructionsContainer.messages.length
stringifiedInfo += `\nThe following ${numMsgs} messages were requested to be relayed:\n`
stringifiedInfo += info.deliveryInstructionsContainer.messages.map((msgInfo, i) => {
let result = ""
result += `\n(Message ${i}): `
if (msgInfo.payloadType == MessageInfoType.EMITTER_SEQUENCE) {
result += `Message with emitter address ${msgInfo.emitterAddress?.toString(
"hex"
)} and sequence number ${msgInfo.sequence}\n`
} else if (msgInfo.payloadType == MessageInfoType.VAAHASH) {
result += `Message with VAA Hash ${msgInfo.vaaHash?.toString("hex")}\n`
} else {
result += `Message not specified correctly\n`
}
})
const length = info.deliveryInstructionsContainer.instructions.length
stringifiedInfo += `\nMessages were requested to be sent to ${length} destination${
length == 1 ? "" : "s"
}:\n`
stringifiedInfo +=
info.deliveryInstructionsContainer.instructions
.map((instruction: DeliveryInstruction, i) => {
let result = ""
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString(
"hex"
)} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0)
? `Amount to pass into target address: ${
instruction.receiverValueTarget
} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n`
: ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString(
"hex"
)}\n`
result += info.targetChainStatuses[i].events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${e.status}${
e.transactionHash
? ` (${targetChainName} transaction hash: ${e.transactionHash})`
: ""
}`
)
.join("\n")
return result
})
.join("\n") + "\n"
}
return stringifiedInfo
}
export async function getWormholeRelayerInfo(
infoRequest: InfoRequest
): Promise<DeliveryInfo > {
const sourceChainProvider =
infoRequest.sourceChainProvider ||
getDefaultProvider(infoRequest.environment, infoRequest.sourceChain)
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getWormholeRelayerAddress(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(
`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`
)
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(
deliveryInstructionsContainer.instructions.map(
async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId
if (!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber)
const [blockStartNumber, blockEndNumber] =
infoRequest.targetChainBlockRanges?.get(targetChain) ||
getBlockRange(targetChainProvider, sourceChainBlock.timestamp)
const deliveryEvents = await getWormholeRelayerDeliveryEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(
targetChain
)} within blocks ${blockStartNumber} to ${blockEndNumber}.`
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber)
const blockEnd = await targetChainProvider.getBlock(blockEndNumber)
status = `Delivery didn't happen on ${printChain(
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) {}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e) => ({
status: e.status,
transactionHash: e.deliveryTxHash,
})),
}
}
)
)
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
deliveryInstructionsContainer,
targetChainStatuses,
}
}

View File

@ -1,373 +0,0 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
CHAINS,
isChain,
CONTRACTS,
getSignedVAAWithRetry,
Network,
parseVaa,
ParsedVaa,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-web/lib/cjs/publicrpc/v1/publicrpc"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { BigNumber, ContractReceipt, ethers, providers } from "ethers"
import {
getCoreRelayer,
getCoreRelayerAddressNative,
RPCS_BY_CHAIN,
GUARDIAN_RPC_HOSTS,
} from "../consts"
import {
parsePayloadType,
RelayerPayloadId,
parseDeliveryInstructionsContainer,
parseRedeliveryByTxHashInstruction,
DeliveryInstruction,
DeliveryInstructionsContainer,
RedeliveryByTxHashInstruction,
ExecutionParameters,
MessageInfoType,
} from "../structs"
import { DeliveryEvent } from "../ethers-contracts/CoreRelayer"
enum DeliveryStatus {
WaitingForVAA = "Waiting for VAA",
PendingDelivery = "Pending Delivery",
DeliverySuccess = "Delivery Success",
ReceiverFailure = "Receiver Failure",
InvalidRedelivery = "Invalid Redelivery",
ForwardRequestSuccess = "Forward Request Success",
ForwardRequestFailure = "Forward Request Failure",
ThisShouldNeverHappen = "This should never happen. Contact Support.",
DeliveryDidntHappenWithinRange = "Delivery didn't happen within given block range",
}
type DeliveryTargetInfo = {
status: DeliveryStatus | string
deliveryTxHash: string | null
vaaHash: string | null
sourceChain: number | null
sourceVaaSequence: BigNumber | null
}
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<number, [ethers.providers.BlockTag, ethers.providers.BlockTag]>
coreRelayerWhMessageIndex?: number
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId
parsed: DeliveryInstructionsContainer | RedeliveryByTxHashInstruction | string
} {
const abi = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);",
]
const iface = new ethers.utils.Interface(abi)
const parsed = iface.parseLog(log)
const payload = Buffer.from(parsed.args.payload.substring(2), "hex")
const type = parsePayloadType(payload)
if (type == RelayerPayloadId.Delivery) {
return { type, parsed: parseDeliveryInstructionsContainer(payload) }
} else if (type == RelayerPayloadId.Redelivery) {
return { type, parsed: parseRedeliveryByTxHashInstruction(payload) }
} else {
throw Error("Invalid wormhole log");
}
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId,
sourceTransactionHash: string,
sourceDeliverySequenceNumber: number,
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export type RedeliveryInfo = {
type: RelayerPayloadId.Redelivery
redeliverySourceChainId: ChainId,
redeliverySourceTransactionHash: string
redeliveryInstruction: RedeliveryByTxHashInstruction
}
export function printChain(chainId: number) {
return `${CHAIN_ID_TO_NAME[chainId as ChainId]} (Chain ${chainId})`
}
export function printInfo(info: DeliveryInfo | RedeliveryInfo) {
console.log(stringifyInfo(info));
}
export function stringifyInfo(info: DeliveryInfo | RedeliveryInfo): string {
let stringifiedInfo = "";
if(info.type==RelayerPayloadId.Redelivery) {
stringifiedInfo += (`Found Redelivery request in transaction ${info.redeliverySourceTransactionHash} on ${printChain(info.redeliverySourceChainId)}\n`)
stringifiedInfo += (`Original Delivery Source Chain: ${printChain(info.redeliveryInstruction.sourceChain)}\n`)
stringifiedInfo += (`Original Delivery Source Transaction Hash: 0x${info.redeliveryInstruction.sourceTxHash.toString("hex")}\n`)
stringifiedInfo += (`Original Delivery sequence number: ${info.redeliveryInstruction.deliveryVAASequence}\n`)
stringifiedInfo += (`Target Chain: ${printChain(info.redeliveryInstruction.targetChain)}\n`)
stringifiedInfo += (`multisendIndex: ${info.redeliveryInstruction.multisendIndex}\n`)
stringifiedInfo += (`New max amount (in target chain currency) to use for gas: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New amount (in target chain currency) to pass into target address: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New target chain gas limit: ${info.redeliveryInstruction.executionParameters.gasLimit}\n`)
stringifiedInfo += (`Relay Provider Delivery Address: 0x${info.redeliveryInstruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`)
} else if(info.type==RelayerPayloadId.Delivery) {
stringifiedInfo += (`Found delivery request in transaction ${info.sourceTransactionHash} on ${printChain(info.sourceChainId)}\n`)
stringifiedInfo += ((info.deliveryInstructionsContainer.sufficientlyFunded ? "The delivery was funded\n" : "** NOTE: The delivery was NOT sufficiently funded. You did not have enough leftover funds to perform the forward **\n"))
const numMsgs = info.deliveryInstructionsContainer.messages.length;
stringifiedInfo += `\nThe following ${numMsgs} messages were requested to be relayed:\n`
stringifiedInfo += info.deliveryInstructionsContainer.messages.map((msgInfo, i) => {
let result = "";
result += `\n(Message ${i}): `
if(msgInfo.infoType == MessageInfoType.EMITTER_SEQUENCE) {
result += `Message with emitter address ${msgInfo.emitterAddress.toString("hex")} and sequence number ${msgInfo.sequence}\n`
} else if(msgInfo.infoType == MessageInfoType.VAAHASH) {
result += `Message with VAA Hash ${msgInfo.vaaHash.toString("hex")}\n`
} else {
result += `Message not specified correctly\n`
}
})
const length = info.deliveryInstructionsContainer.instructions.length;
stringifiedInfo += (`\nMessages were requested to be sent to ${length} destination${length == 1 ? "" : "s"}:\n`)
stringifiedInfo += (info.deliveryInstructionsContainer.instructions.map((instruction: DeliveryInstruction, i) => {
let result = "";
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId];
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString("hex")} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0) ? `Amount to pass into target address: ${instruction.receiverValueTarget} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n` : ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`
result += info.targetChainStatuses[i].events.map((e, i) => (`Delivery attempt ${i+1}: ${e.status}${e.transactionHash ? ` (${targetChainName} transaction hash: ${e.transactionHash})` : ""}`)).join("\n")
return result;
}).join("\n")) + "\n"
}
return stringifiedInfo
}
function getDefaultProvider(network: Network, chainId: ChainId) {
return new ethers.providers.StaticJsonRpcProvider(
RPCS_BY_CHAIN[network][CHAIN_ID_TO_NAME[chainId]]
)
}
export async function getDeliveryInfoBySourceTx(
infoRequest: InfoRequest
): Promise<DeliveryInfo | RedeliveryInfo> {
const sourceChainProvider =
infoRequest.sourceChainProvider || getDefaultProvider(infoRequest.environment, infoRequest.sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getCoreRelayerAddressNative(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`)
}
const deliveryLog = findLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0,
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
if (type == RelayerPayloadId.Redelivery) {
const redeliveryInstruction = parsed as RedeliveryByTxHashInstruction
return {
type,
redeliverySourceChainId: infoRequest.sourceChain,
redeliverySourceTransactionHash: infoRequest.sourceTransaction,
redeliveryInstruction,
}
}
/* Potentially use 'guardianRPCHosts' to get status of VAA; code in comments at end [1] */
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(deliveryInstructionsContainer.instructions.map(async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId;
if(!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber);
const [blockStartNumber, blockEndNumber] = infoRequest.targetChainBlockRanges?.get(targetChain) || getBlockRange(targetChainProvider, sourceChainBlock.timestamp);
const deliveryEvents = await pullEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(targetChain)} within blocks ${blockStartNumber} to ${blockEndNumber}.`;
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber);
const blockEnd = await targetChainProvider.getBlock(blockEndNumber);
status = `Delivery didn't happen on ${printChain(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) {
}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e)=>({status: e.status, transactionHash: e.deliveryTxHash}))
}
}))
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
deliveryInstructionsContainer,
targetChainStatuses
}
}
function getBlockRange(provider: ethers.providers.Provider, timestamp?: number): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
return [-2040, "latest"]
}
async function pullEventsBySourceSequence(
environment: Network,
targetChain: ChainId,
targetChainProvider: ethers.providers.Provider,
sourceChain: number,
sourceVaaSequence: BigNumber,
blockStartNumber: ethers.providers.BlockTag,
blockEndNumber: ethers.providers.BlockTag
): Promise<DeliveryTargetInfo[]> {
const coreRelayer = getCoreRelayer(targetChain, environment, targetChainProvider)
//TODO These compile errors on sourceChain look like an ethers bug
const deliveryEvents = coreRelayer.filters.Delivery(
null,
sourceChain,
sourceVaaSequence
)
// There is a max limit on RPCs sometimes for how many blocks to query
return await transformDeliveryEvents(
await coreRelayer.queryFilter(deliveryEvents, blockStartNumber, blockEndNumber),
targetChainProvider
)
}
function deliveryStatus(status: number) {
switch (status) {
case 0:
return DeliveryStatus.DeliverySuccess
case 1:
return DeliveryStatus.ReceiverFailure
case 2:
return DeliveryStatus.ForwardRequestFailure
case 3:
return DeliveryStatus.ForwardRequestSuccess
case 4:
return DeliveryStatus.InvalidRedelivery
default:
return DeliveryStatus.ThisShouldNeverHappen
}
}
async function transformDeliveryEvents(
events: DeliveryEvent[],
targetProvider: ethers.providers.Provider
): Promise<DeliveryTargetInfo[]> {
return Promise.all(
events.map(async (x) => {
return {
status: deliveryStatus(x.args[4]),
deliveryTxHash: x.transactionHash,
vaaHash: x.args[3],
sourceVaaSequence: x.args[2],
sourceChain: x.args[1],
}
})
)
}
export function findLog(
receipt: ContractReceipt,
bridgeAddress: string,
emitterAddress: string,
index: number,
): { log: ethers.providers.Log; sequence: string } {
const bridgeLogs = receipt.logs.filter((l) => {
return l.address === bridgeAddress
})
if (bridgeLogs.length == 0) {
throw Error("No core contract interactions found for this transaction.")
}
const parsed = bridgeLogs.map((bridgeLog) => {
const log = Implementation__factory.createInterface().parseLog(bridgeLog)
return {
sequence: log.args[1].toString(),
nonce: log.args[2].toString(),
emitterAddress: tryNativeToHexString(log.args[0].toString(), "ethereum"),
log: bridgeLog,
}
})
const filtered = parsed.filter(
(x) =>
x.emitterAddress == emitterAddress.toLowerCase()
)
if (filtered.length == 0) {
throw Error("No CoreRelayer contract interactions found for this transaction.")
}
if (index >= filtered.length) {
throw Error("Specified delivery index is out of range.")
} else {
return {
log: filtered[index].log,
sequence: filtered[index].sequence,
}
}
}

View File

@ -3,21 +3,25 @@ import { arrayify } from "ethers/lib/utils"
export enum RelayerPayloadId {
Delivery = 1,
Redelivery = 2,
// DeliveryStatus = 3,
}
export enum DeliveryStatus {
WaitingForVAA = "Waiting for VAA",
PendingDelivery = "Pending Delivery",
DeliverySuccess = "Delivery Success",
ReceiverFailure = "Receiver Failure",
InvalidRedelivery = "Invalid Redelivery",
ForwardRequestSuccess = "Forward Request Success",
ForwardRequestFailure = "Forward Request Failure",
ThisShouldNeverHappen = "This should never happen. Contact Support.",
DeliveryDidntHappenWithinRange = "Delivery didn't happen within given block range",
}
export interface DeliveryInstructionsContainer {
payloadId: number // 1
sufficientlyFunded: boolean
messages: MessageInfo[]
instructions: DeliveryInstruction[]
messages: MessageInfo[]
}
export enum MessageInfoType {
EmitterSequence = 0,
VaaHash = 1,
}
export interface MessageInfo {
@ -42,28 +46,12 @@ export interface ExecutionParameters {
providerDeliveryAddress: Buffer
}
export interface RedeliveryByTxHashInstruction {
payloadId: number //2
sourceChain: number
sourceTxHash: Buffer
deliveryVaaSequence: BigNumber
targetChain: number
multisendIndex: number
newMaximumRefundTarget: BigNumber
newReceiverValueTarget: BigNumber
executionParameters: ExecutionParameters
export enum MessageInfoType {
EMITTER_SEQUENCE = 0,
VAAHASH = 1,
}
export enum MessageInfoType {EMITTER_SEQUENCE, VAAHASH}
export interface MessageInfo {
infoType: MessageInfoType,
emitterAddress: Buffer,
sequence: number,
vaaHash: Buffer
}
export function parsePayloadType(
export function parseWormholeRelayerPayloadType(
stringPayload: string | Buffer | Uint8Array
): RelayerPayloadId {
const payload =
@ -74,9 +62,7 @@ export function parsePayloadType(
return payload[0]
}
export function parseDeliveryInstructionsContainer(
bytes: Buffer
): DeliveryInstructionsContainer {
export function parseWormholeRelayerSend(bytes: Buffer): DeliveryInstructionsContainer {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Delivery) {
@ -86,41 +72,19 @@ export function parseDeliveryInstructionsContainer(
}
idx += 1
const sufficientlyFunded = Boolean(bytes.readUInt8(idx))
idx += 1
const numMessages = bytes.readUInt8(idx)
idx += 1
let messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
const res = parseMessageInfo(bytes, idx)
idx = res[1]
messages.push(res[0])
}
const numInstructions = bytes.readUInt8(idx)
idx += 1
let messages = [] as MessageInfo[]
for(let i=0; i < numMessages; ++i) {
const payloadId = bytes.readUint8(idx);
idx += 1;
const infoType = bytes.readUint8(idx) as MessageInfoType;
idx += 1;
let emitterAddress = Buffer.from([]);
let sequence = 0;
let vaaHash = Buffer.from([]);
if(infoType == MessageInfoType.EMITTER_SEQUENCE) {
emitterAddress = bytes.slice(idx, idx+32);
idx += 32;
sequence = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx+8)).toNumber();
idx += 8;
} else if(infoType == MessageInfoType.VAAHASH) {
vaaHash = bytes.slice(idx, idx + 32);
idx += 32;
}
messages.push({
infoType,
emitterAddress,
sequence,
vaaHash
})
}
let instructions = [] as DeliveryInstruction[]
for (let i = 0; i < numInstructions; ++i) {
const targetChain = bytes.readUInt16BE(idx)
@ -137,8 +101,9 @@ export function parseDeliveryInstructionsContainer(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 32)
)
idx += 32
const executionParameters = parseExecutionParameters(bytes, [idx])
idx += 37
let res = parseWormholeRelayerExecutionParameters(bytes, idx)
const executionParameters = res[0]
idx = res[1]
instructions.push(
// dumb typechain format
{
@ -152,107 +117,59 @@ export function parseDeliveryInstructionsContainer(
)
}
// parse the manifest
const numMessages = bytes.readUInt8(idx)
idx += 1
const idxPtr: [number] = [idx]
const messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
messages.push(parseMessageInfo(bytes, idxPtr))
}
return {
payloadId,
sufficientlyFunded,
messages,
instructions,
messages,
}
}
function parseMessageInfo(bytes: Buffer, idxPtr: [number]): MessageInfo {
let idx = idxPtr[0]
const payloadType = bytes.readUInt8(idx) as MessageInfoType
switch (payloadType) {
case MessageInfoType.EmitterSequence:
const emitterAddress = bytes.slice(idx, idx + 32)
idx += 32
const sequence = ethers.BigNumber.from(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 32)
)
idx += 8
idxPtr[0] = idx
return { emitterAddress, sequence, payloadType }
case MessageInfoType.VaaHash:
const vaaHash = bytes.slice(idx, idx + 32)
idx += 32
idxPtr[0] = idx
return { vaaHash, payloadType }
}
}
export function parseRedeliveryByTxHashInstruction(
bytes: Buffer
): RedeliveryByTxHashInstruction {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Redelivery) {
throw new Error(
`Expected Delivery payload type (${RelayerPayloadId.Redelivery}), found: ${payloadId}`
function parseMessageInfo(bytes: Buffer, idx: number): [MessageInfo, number] {
const payloadId = bytes.readUint8(idx)
idx += 1
const payloadType = bytes.readUint8(idx) as MessageInfoType
idx += 1
if (payloadType == MessageInfoType.EMITTER_SEQUENCE) {
const emitterAddress = bytes.slice(idx, idx + 32)
idx += 32
const sequence = ethers.BigNumber.from(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 8)
)
}
idx += 1
const sourceChain = bytes.readUInt16BE(idx)
idx += 2
const sourceTxHash = bytes.slice(idx, idx + 32)
idx += 32
const deliveryVaaSequence = BigNumber.from(bytes.slice(idx, idx + 8))
idx += 8
const targetChain = bytes.readUInt16BE(idx)
idx += 2
const multisendIndex = bytes.readUint8(idx)
idx += 1
// note: confirmed that BigNumber.from(<Buffer>) assumes big endian
const newMaximumRefundTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const newReceiverValueTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const executionParameters = parseExecutionParameters(bytes, [idx])
idx += 37
return {
payloadId,
sourceChain,
sourceTxHash,
deliveryVaaSequence,
targetChain,
multisendIndex,
newMaximumRefundTarget,
newReceiverValueTarget,
executionParameters,
idx += 8
return [
{
payloadType,
emitterAddress,
sequence,
},
idx,
]
} else if (payloadType == MessageInfoType.VAAHASH) {
const vaaHash = bytes.slice(idx, idx + 32)
idx += 32
return [
{
payloadType,
vaaHash,
},
idx,
]
} else {
throw new Error("Unexpected MessageInfo payload type")
}
}
function parseExecutionParameters(
function parseWormholeRelayerExecutionParameters(
bytes: Buffer,
idxPtr: [number] = [0]
): ExecutionParameters {
let idx = idxPtr[0]
idx: number
): [ExecutionParameters, number] {
const version = bytes.readUInt8(idx)
idx += 1
const gasLimit = bytes.readUint32BE(idx)
idx += 4
const providerDeliveryAddress = bytes.slice(idx, idx + 32)
idx += 32
idxPtr[0] = idx
return { version, gasLimit, providerDeliveryAddress }
return [{ version, gasLimit, providerDeliveryAddress }, idx]
}
/*

View File

@ -1,35 +1,18 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
CHAINS,
isChain,
CONTRACTS,
getSignedVAAWithRetry,
Network,
parseVaa,
ParsedVaa,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-web/lib/cjs/publicrpc/v1/publicrpc"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { BigNumber, ContractReceipt, ethers, providers } from "ethers"
import {
getWormholeRelayer,
getWormholeRelayerAddress,
RPCS_BY_CHAIN,
GUARDIAN_RPC_HOSTS,
} from "../consts"
import { BigNumber, ContractReceipt, ethers } from "ethers"
import { getWormholeRelayer, RPCS_BY_CHAIN } from "../consts"
import {
parseWormholeRelayerPayloadType,
RelayerPayloadId,
parseWormholeRelayerSend,
parseWormholeRelayerResend,
DeliveryInstruction,
DeliveryInstructionsContainer,
RedeliveryByTxHashInstruction,
ExecutionParameters,
MessageInfoType,
DeliveryStatus
DeliveryStatus,
} from "../structs"
import { DeliveryEvent } from "../ethers-contracts/CoreRelayer"
type DeliveryTargetInfo = {
@ -40,10 +23,9 @@ type DeliveryTargetInfo = {
sourceVaaSequence: BigNumber | null
}
export function parseWormholeLog(log: ethers.providers.Log): {
type: RelayerPayloadId
parsed: DeliveryInstructionsContainer | RedeliveryByTxHashInstruction | string
parsed: DeliveryInstructionsContainer | string
} {
const abi = [
"event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);",
@ -54,10 +36,8 @@ export function parseWormholeLog(log: ethers.providers.Log): {
const type = parseWormholeRelayerPayloadType(payload)
if (type == RelayerPayloadId.Delivery) {
return { type, parsed: parseWormholeRelayerSend(payload) }
} else if (type == RelayerPayloadId.Redelivery) {
return { type, parsed: parseWormholeRelayerResend(payload) }
} else {
throw Error("Invalid wormhole log");
throw Error("Invalid wormhole log")
}
}
@ -71,8 +51,10 @@ export function getDefaultProvider(network: Network, chainId: ChainId) {
)
}
export function getBlockRange(provider: ethers.providers.Provider, timestamp?: number): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
export function getBlockRange(
provider: ethers.providers.Provider,
timestamp?: number
): [ethers.providers.BlockTag, ethers.providers.BlockTag] {
return [-2040, "latest"]
}
@ -138,7 +120,7 @@ export function getWormholeRelayerLog(
receipt: ContractReceipt,
bridgeAddress: string,
emitterAddress: string,
index: number,
index: number
): { log: ethers.providers.Log; sequence: string } {
const bridgeLogs = receipt.logs.filter((l) => {
return l.address === bridgeAddress
@ -158,10 +140,7 @@ export function getWormholeRelayerLog(
}
})
const filtered = parsed.filter(
(x) =>
x.emitterAddress == emitterAddress.toLowerCase()
)
const filtered = parsed.filter((x) => x.emitterAddress == emitterAddress.toLowerCase())
if (filtered.length == 0) {
throw Error("No CoreRelayer contract interactions found for this transaction.")

View File

@ -1 +1 @@
export * from "./status"
export * from "./main"

View File

@ -1,213 +1,224 @@
import {
ChainId,
CHAIN_ID_TO_NAME,
isChain,
CONTRACTS,
Network,
tryNativeToHexString,
} from "@certusone/wormhole-sdk"
import { BigNumber, ethers, } from "ethers"
import {
getWormholeRelayerAddress,
} from "../consts"
import {
RelayerPayloadId,
DeliveryInstruction,
DeliveryInstructionsContainer,
RedeliveryByTxHashInstruction,
MessageInfoType,
DeliveryStatus
} from "../structs"
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerDeliveryEventsBySourceSequence,
} from "./helpers"
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<number, [ethers.providers.BlockTag, ethers.providers.BlockTag]>
coreRelayerWhMessageIndex?: number
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId,
sourceTransactionHash: string,
sourceDeliverySequenceNumber: number,
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export type RedeliveryInfo = {
type: RelayerPayloadId.Redelivery
redeliverySourceChainId: ChainId,
redeliverySourceTransactionHash: string
redeliveryInstruction: RedeliveryByTxHashInstruction
}
export function printWormholeRelayerInfo(info: DeliveryInfo | RedeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info));
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo | RedeliveryInfo): string {
let stringifiedInfo = "";
if(info.type==RelayerPayloadId.Redelivery) {
stringifiedInfo += (`Found Redelivery request in transaction ${info.redeliverySourceTransactionHash} on ${printChain(info.redeliverySourceChainId)}\n`)
stringifiedInfo += (`Original Delivery Source Chain: ${printChain(info.redeliveryInstruction.sourceChain)}\n`)
stringifiedInfo += (`Original Delivery Source Transaction Hash: 0x${info.redeliveryInstruction.sourceTxHash.toString("hex")}\n`)
stringifiedInfo += (`Original Delivery sequence number: ${info.redeliveryInstruction.deliveryVAASequence}\n`)
stringifiedInfo += (`Target Chain: ${printChain(info.redeliveryInstruction.targetChain)}\n`)
stringifiedInfo += (`multisendIndex: ${info.redeliveryInstruction.multisendIndex}\n`)
stringifiedInfo += (`New max amount (in target chain currency) to use for gas: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New amount (in target chain currency) to pass into target address: ${info.redeliveryInstruction.newMaximumRefundTarget}\n`)
stringifiedInfo += (`New target chain gas limit: ${info.redeliveryInstruction.executionParameters.gasLimit}\n`)
stringifiedInfo += (`Relay Provider Delivery Address: 0x${info.redeliveryInstruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`)
} else if(info.type==RelayerPayloadId.Delivery) {
stringifiedInfo += (`Found delivery request in transaction ${info.sourceTransactionHash} on ${printChain(info.sourceChainId)}\n`)
stringifiedInfo += ((info.deliveryInstructionsContainer.sufficientlyFunded ? "The delivery was funded\n" : "** NOTE: The delivery was NOT sufficiently funded. You did not have enough leftover funds to perform the forward **\n"))
const numMsgs = info.deliveryInstructionsContainer.messages.length;
stringifiedInfo += `\nThe following ${numMsgs} messages were requested to be relayed:\n`
stringifiedInfo += info.deliveryInstructionsContainer.messages.map((msgInfo, i) => {
let result = "";
result += `\n(Message ${i}): `
if(msgInfo.infoType == MessageInfoType.EMITTER_SEQUENCE) {
result += `Message with emitter address ${msgInfo.emitterAddress.toString("hex")} and sequence number ${msgInfo.sequence}\n`
} else if(msgInfo.infoType == MessageInfoType.VAAHASH) {
result += `Message with VAA Hash ${msgInfo.vaaHash.toString("hex")}\n`
} else {
result += `Message not specified correctly\n`
}
})
const length = info.deliveryInstructionsContainer.instructions.length;
stringifiedInfo += (`\nMessages were requested to be sent to ${length} destination${length == 1 ? "" : "s"}:\n`)
stringifiedInfo += (info.deliveryInstructionsContainer.instructions.map((instruction: DeliveryInstruction, i) => {
let result = "";
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId];
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString("hex")} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0) ? `Amount to pass into target address: ${instruction.receiverValueTarget} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n` : ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString("hex")}\n`
result += info.targetChainStatuses[i].events.map((e, i) => (`Delivery attempt ${i+1}: ${e.status}${e.transactionHash ? ` (${targetChainName} transaction hash: ${e.transactionHash})` : ""}`)).join("\n")
return result;
}).join("\n")) + "\n"
}
return stringifiedInfo
}
ChainId,
CHAIN_ID_TO_NAME,
isChain,
CONTRACTS,
tryNativeToHexString,
Network,
} from "@certusone/wormhole-sdk"
import { BigNumber, ethers } from "ethers"
import { getWormholeRelayerAddress } from "../consts"
import {
RelayerPayloadId,
DeliveryInstruction,
DeliveryInstructionsContainer,
MessageInfoType,
DeliveryStatus,
} from "../structs"
import {
getDefaultProvider,
printChain,
getWormholeRelayerLog,
parseWormholeLog,
getBlockRange,
getWormholeRelayerDeliveryEventsBySourceSequence,
} from "./helpers"
export async function getWormholeRelayerInfo(
infoRequest: InfoRequest
): Promise<DeliveryInfo | RedeliveryInfo> {
const sourceChainProvider =
infoRequest.sourceChainProvider || getDefaultProvider(infoRequest.environment, infoRequest.sourceChain);
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getWormholeRelayerAddress(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`)
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0,
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
if (type == RelayerPayloadId.Redelivery) {
const redeliveryInstruction = parsed as RedeliveryByTxHashInstruction
return {
type,
redeliverySourceChainId: infoRequest.sourceChain,
redeliverySourceTransactionHash: infoRequest.sourceTransaction,
redeliveryInstruction,
type InfoRequest = {
environment: Network
sourceChain: ChainId
sourceTransaction: string
sourceChainProvider?: ethers.providers.Provider
targetChainProviders?: Map<number, ethers.providers.Provider>
targetChainBlockRanges?: Map<
number,
[ethers.providers.BlockTag, ethers.providers.BlockTag]
>
coreRelayerWhMessageIndex?: number
}
export type DeliveryInfo = {
type: RelayerPayloadId.Delivery
sourceChainId: ChainId
sourceTransactionHash: string
sourceDeliverySequenceNumber: number
deliveryInstructionsContainer: DeliveryInstructionsContainer
targetChainStatuses: {
chainId: ChainId
events: { status: DeliveryStatus | string; transactionHash: string | null }[]
}[]
}
export function printWormholeRelayerInfo(info: DeliveryInfo) {
console.log(stringifyWormholeRelayerInfo(info))
}
export function stringifyWormholeRelayerInfo(info: DeliveryInfo): string {
let stringifiedInfo = ""
if (info.type == RelayerPayloadId.Delivery) {
stringifiedInfo += `Found delivery request in transaction ${
info.sourceTransactionHash
} on ${printChain(info.sourceChainId)}\n`
const numMsgs = info.deliveryInstructionsContainer.messages.length
stringifiedInfo += `\nThe following ${numMsgs} messages were requested to be relayed:\n`
stringifiedInfo += info.deliveryInstructionsContainer.messages.map((msgInfo, i) => {
let result = ""
result += `\n(Message ${i}): `
if (msgInfo.payloadType == MessageInfoType.EMITTER_SEQUENCE) {
result += `Message with emitter address ${msgInfo.emitterAddress?.toString(
"hex"
)} and sequence number ${msgInfo.sequence}\n`
} else if (msgInfo.payloadType == MessageInfoType.VAAHASH) {
result += `Message with VAA Hash ${msgInfo.vaaHash?.toString("hex")}\n`
} else {
result += `Message not specified correctly\n`
}
}
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(deliveryInstructionsContainer.instructions.map(async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId;
if(!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber);
const [blockStartNumber, blockEndNumber] = infoRequest.targetChainBlockRanges?.get(targetChain) || getBlockRange(targetChainProvider, sourceChainBlock.timestamp);
const deliveryEvents = await getWormholeRelayerDeliveryEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(targetChain)} within blocks ${blockStartNumber} to ${blockEndNumber}.`;
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber);
const blockEnd = await targetChainProvider.getBlock(blockEndNumber);
status = `Delivery didn't happen on ${printChain(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) {
}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
const length = info.deliveryInstructionsContainer.instructions.length
stringifiedInfo += `\nMessages were requested to be sent to ${length} destination${
length == 1 ? "" : "s"
}:\n`
stringifiedInfo +=
info.deliveryInstructionsContainer.instructions
.map((instruction: DeliveryInstruction, i) => {
let result = ""
const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]
result += `\n(Destination ${i}): Target address is 0x${instruction.targetAddress.toString(
"hex"
)} on ${printChain(instruction.targetChain)}\n`
result += `Max amount to use for gas: ${instruction.maximumRefundTarget} of ${targetChainName} currency\n`
result += instruction.receiverValueTarget.gt(0)
? `Amount to pass into target address: ${
instruction.receiverValueTarget
} of ${CHAIN_ID_TO_NAME[instruction.targetChain as ChainId]} currency\n`
: ``
result += `Gas limit: ${instruction.executionParameters.gasLimit} ${targetChainName} gas\n`
result += `Relay Provider Delivery Address: 0x${instruction.executionParameters.providerDeliveryAddress.toString(
"hex"
)}\n`
result += info.targetChainStatuses[i].events
.map(
(e, i) =>
`Delivery attempt ${i + 1}: ${e.status}${
e.transactionHash
? ` (${targetChainName} transaction hash: ${e.transactionHash})`
: ""
}`
)
.join("\n")
return result
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e)=>({status: e.status, transactionHash: e.deliveryTxHash}))
}
}))
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
deliveryInstructionsContainer,
targetChainStatuses
}
.join("\n") + "\n"
}
return stringifiedInfo
}
export async function getWormholeRelayerInfo(
infoRequest: InfoRequest
): Promise<DeliveryInfo > {
const sourceChainProvider =
infoRequest.sourceChainProvider ||
getDefaultProvider(infoRequest.environment, infoRequest.sourceChain)
if (!sourceChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as sourceChainProvider)"
)
const receipt = await sourceChainProvider.getTransactionReceipt(
infoRequest.sourceTransaction
)
if (!receipt) throw Error("Transaction has not been mined")
const bridgeAddress =
CONTRACTS[infoRequest.environment][CHAIN_ID_TO_NAME[infoRequest.sourceChain]].core
const coreRelayerAddress = getWormholeRelayerAddress(
infoRequest.sourceChain,
infoRequest.environment
)
if (!bridgeAddress || !coreRelayerAddress) {
throw Error(
`Invalid chain ID or network: Chain ID ${infoRequest.sourceChain}, ${infoRequest.environment}`
)
}
const deliveryLog = getWormholeRelayerLog(
receipt,
bridgeAddress,
tryNativeToHexString(coreRelayerAddress, "ethereum"),
infoRequest.coreRelayerWhMessageIndex ? infoRequest.coreRelayerWhMessageIndex : 0
)
const { type, parsed } = parseWormholeLog(deliveryLog.log)
const deliveryInstructionsContainer = parsed as DeliveryInstructionsContainer
const targetChainStatuses = await Promise.all(
deliveryInstructionsContainer.instructions.map(
async (instruction: DeliveryInstruction) => {
const targetChain = instruction.targetChain as ChainId
if (!isChain(targetChain)) throw Error(`Invalid Chain: ${targetChain}`)
const targetChainProvider =
infoRequest.targetChainProviders?.get(targetChain) ||
getDefaultProvider(infoRequest.environment, targetChain)
if (!targetChainProvider)
throw Error(
"No default RPC for this chain; pass in your own provider (as targetChainProvider)"
)
const sourceChainBlock = await sourceChainProvider.getBlock(receipt.blockNumber)
const [blockStartNumber, blockEndNumber] =
infoRequest.targetChainBlockRanges?.get(targetChain) ||
getBlockRange(targetChainProvider, sourceChainBlock.timestamp)
const deliveryEvents = await getWormholeRelayerDeliveryEventsBySourceSequence(
infoRequest.environment,
targetChain,
targetChainProvider,
infoRequest.sourceChain,
BigNumber.from(deliveryLog.sequence),
blockStartNumber,
blockEndNumber
)
if (deliveryEvents.length == 0) {
let status = `Delivery didn't happen on ${printChain(
targetChain
)} within blocks ${blockStartNumber} to ${blockEndNumber}.`
try {
const blockStart = await targetChainProvider.getBlock(blockStartNumber)
const blockEnd = await targetChainProvider.getBlock(blockEndNumber)
status = `Delivery didn't happen on ${printChain(
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) {}
deliveryEvents.push({
status,
deliveryTxHash: null,
vaaHash: null,
sourceChain: infoRequest.sourceChain,
sourceVaaSequence: BigNumber.from(deliveryLog.sequence),
})
}
return {
chainId: targetChain,
events: deliveryEvents.map((e) => ({
status: e.status,
transactionHash: e.deliveryTxHash,
})),
}
}
)
)
return {
type,
sourceChainId: infoRequest.sourceChain,
sourceTransactionHash: infoRequest.sourceTransaction,
sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
deliveryInstructionsContainer,
targetChainStatuses,
}
}

View File

@ -3,7 +3,6 @@ import { arrayify } from "ethers/lib/utils"
export enum RelayerPayloadId {
Delivery = 1,
Redelivery = 2,
// DeliveryStatus = 3,
}
@ -19,18 +18,10 @@ export enum DeliveryStatus {
DeliveryDidntHappenWithinRange = "Delivery didn't happen within given block range",
}
export interface DeliveryInstructionsContainer {
payloadId: number // 1
sufficientlyFunded: boolean
messages: MessageInfo[]
instructions: DeliveryInstruction[]
messages: MessageInfo[]
}
export enum MessageInfoType {
EmitterSequence = 0,
VaaHash = 1,
}
export interface MessageInfo {
@ -55,25 +46,9 @@ export interface ExecutionParameters {
providerDeliveryAddress: Buffer
}
export interface RedeliveryByTxHashInstruction {
payloadId: number //2
sourceChain: number
sourceTxHash: Buffer
deliveryVaaSequence: BigNumber
targetChain: number
multisendIndex: number
newMaximumRefundTarget: BigNumber
newReceiverValueTarget: BigNumber
executionParameters: ExecutionParameters
}
export enum MessageInfoType {EMITTER_SEQUENCE, VAAHASH}
export interface MessageInfo {
infoType: MessageInfoType,
emitterAddress: Buffer,
sequence: number,
vaaHash: Buffer
export enum MessageInfoType {
EMITTER_SEQUENCE = 0,
VAAHASH = 1,
}
export function parseWormholeRelayerPayloadType(
@ -87,9 +62,7 @@ export function parseWormholeRelayerPayloadType(
return payload[0]
}
export function parseWormholeRelayerSend(
bytes: Buffer
): DeliveryInstructionsContainer {
export function parseWormholeRelayerSend(bytes: Buffer): DeliveryInstructionsContainer {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Delivery) {
@ -99,41 +72,19 @@ export function parseWormholeRelayerSend(
}
idx += 1
const sufficientlyFunded = Boolean(bytes.readUInt8(idx))
idx += 1
const numMessages = bytes.readUInt8(idx)
idx += 1
let messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
const res = parseMessageInfo(bytes, idx)
idx = res[1]
messages.push(res[0])
}
const numInstructions = bytes.readUInt8(idx)
idx += 1
let messages = [] as MessageInfo[]
for(let i=0; i < numMessages; ++i) {
const payloadId = bytes.readUint8(idx);
idx += 1;
const infoType = bytes.readUint8(idx) as MessageInfoType;
idx += 1;
let emitterAddress = Buffer.from([]);
let sequence = 0;
let vaaHash = Buffer.from([]);
if(infoType == MessageInfoType.EMITTER_SEQUENCE) {
emitterAddress = bytes.slice(idx, idx+32);
idx += 32;
sequence = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx+8)).toNumber();
idx += 8;
} else if(infoType == MessageInfoType.VAAHASH) {
vaaHash = bytes.slice(idx, idx + 32);
idx += 32;
}
messages.push({
infoType,
emitterAddress,
sequence,
vaaHash
})
}
let instructions = [] as DeliveryInstruction[]
for (let i = 0; i < numInstructions; ++i) {
const targetChain = bytes.readUInt16BE(idx)
@ -150,8 +101,9 @@ export function parseWormholeRelayerSend(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 32)
)
idx += 32
const executionParameters = parseWormholeRelayerExecutionParameters(bytes, idx)
idx += 37
let res = parseWormholeRelayerExecutionParameters(bytes, idx)
const executionParameters = res[0]
idx = res[1]
instructions.push(
// dumb typechain format
{
@ -165,81 +117,59 @@ export function parseWormholeRelayerSend(
)
}
// parse the manifest
const numMessages = bytes.readUInt8(idx)
idx += 1
const idxPtr: [number] = [idx]
const messages = [] as MessageInfo[]
for (let i = 0; i < numMessages; ++i) {
messages.push(parseMessageInfo(bytes, idxPtr))
}
return {
payloadId,
sufficientlyFunded,
messages,
instructions,
messages,
}
}
export function parseWormholeRelayerResend(
bytes: Buffer
): RedeliveryByTxHashInstruction {
let idx = 0
const payloadId = bytes.readUInt8(idx)
if (payloadId !== RelayerPayloadId.Redelivery) {
throw new Error(
`Expected Delivery payload type (${RelayerPayloadId.Redelivery}), found: ${payloadId}`
function parseMessageInfo(bytes: Buffer, idx: number): [MessageInfo, number] {
const payloadId = bytes.readUint8(idx)
idx += 1
const payloadType = bytes.readUint8(idx) as MessageInfoType
idx += 1
if (payloadType == MessageInfoType.EMITTER_SEQUENCE) {
const emitterAddress = bytes.slice(idx, idx + 32)
idx += 32
const sequence = ethers.BigNumber.from(
Uint8Array.prototype.subarray.call(bytes, idx, idx + 8)
)
}
idx += 1
const sourceChain = bytes.readUInt16BE(idx)
idx += 2
const sourceTxHash = bytes.slice(idx, idx + 32)
idx += 32
const deliveryVAASequence = BigNumber.from(bytes.slice(idx, idx + 8)).toNumber()
idx += 8
const targetChain = bytes.readUInt16BE(idx)
idx += 2
const multisendIndex = bytes.readUint8(idx)
idx += 1
const newMaximumRefundTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const newReceiverValueTarget = BigNumber.from(bytes.slice(idx, idx + 32))
idx += 32
const executionParameters = parseWormholeRelayerExecutionParameters(bytes, idx)
idx += 37
return {
payloadId,
sourceChain,
sourceTxHash,
deliveryVAASequence,
targetChain,
multisendIndex,
newMaximumRefundTarget,
newReceiverValueTarget,
executionParameters,
idx += 8
return [
{
payloadType,
emitterAddress,
sequence,
},
idx,
]
} else if (payloadType == MessageInfoType.VAAHASH) {
const vaaHash = bytes.slice(idx, idx + 32)
idx += 32
return [
{
payloadType,
vaaHash,
},
idx,
]
} else {
throw new Error("Unexpected MessageInfo payload type")
}
}
function parseWormholeRelayerExecutionParameters(bytes: Buffer, idx: number = 0): ExecutionParameters {
function parseWormholeRelayerExecutionParameters(
bytes: Buffer,
idx: number
): [ExecutionParameters, number] {
const version = bytes.readUInt8(idx)
idx += 1
const gasLimit = bytes.readUint32BE(idx)
idx += 4
const providerDeliveryAddress = bytes.slice(idx, idx + 32)
idx += 32
idxPtr[0] = idx
return { version, gasLimit, providerDeliveryAddress }
return [{ version, gasLimit, providerDeliveryAddress }, idx]
}
/*