Real integration test :)

This commit is contained in:
derpy-duck 2023-02-10 16:01:18 +00:00 committed by chase-45
parent dee6ba7081
commit 59f1f4439f
2 changed files with 62 additions and 347 deletions

View File

@ -5,6 +5,7 @@ import {
ChainInfo,
RELAYER_DEPLOYER_PRIVATE_KEY,
} from "./helpers/consts";
import { generateRandomString } from "./helpers/utils";
import { CoreRelayer__factory, IWormhole__factory, MockRelayerIntegration__factory } from "../../sdk/src";
import { init, loadChains, loadCoreRelayers, loadMockIntegrations } from "../ts-scripts/helpers/env";
@ -15,30 +16,36 @@ const chains = loadChains();
const coreRelayers = loadCoreRelayers();
const mockIntegrations = loadMockIntegrations();
describe("Core Relayer Integration Test - Two Chains", () => {
const provider = new ethers.providers.StaticJsonRpcProvider(chains[0].rpc);
describe("Core Relayer Integration Test - Two Chains", () => {
// signers
const wallet = new ethers.Wallet(RELAYER_DEPLOYER_PRIVATE_KEY, provider);
const sourceChain = chains.find((c)=>(c.chainId == 2)) as ChainInfo;
const targetChain = chains.find((c)=>(c.chainId == 4)) as ChainInfo;
const providerSource = new ethers.providers.StaticJsonRpcProvider(sourceChain.rpc);
const providerTarget = new ethers.providers.StaticJsonRpcProvider(targetChain.rpc);
const walletSource = new ethers.Wallet(RELAYER_DEPLOYER_PRIVATE_KEY, providerSource);
const walletTarget = new ethers.Wallet(RELAYER_DEPLOYER_PRIVATE_KEY, providerTarget);
const sourceCoreRelayerAddress = coreRelayers.find((p)=>(p.chainId==sourceChain.chainId))?.address as string
const sourceMockIntegrationAddress = mockIntegrations.find((p)=>(p.chainId==sourceChain.chainId))?.address as string
const targetCoreRelayerAddress = coreRelayers.find((p)=>(p.chainId==targetChain.chainId))?.address as string
const targetMockIntegrationAddress = mockIntegrations.find((p)=>(p.chainId==targetChain.chainId))?.address as string
const sourceCoreRelayer = CoreRelayer__factory.connect(sourceCoreRelayerAddress, wallet);
const sourceMockIntegration = MockRelayerIntegration__factory.connect(sourceMockIntegrationAddress, wallet);
const targetCoreRelayer = CoreRelayer__factory.connect(targetCoreRelayerAddress, wallet);
const targetMockIntegration = MockRelayerIntegration__factory.connect(targetMockIntegrationAddress, wallet);
const sourceCoreRelayer = CoreRelayer__factory.connect(sourceCoreRelayerAddress, walletSource);
const sourceMockIntegration = MockRelayerIntegration__factory.connect(sourceMockIntegrationAddress, walletSource);
const targetCoreRelayer = CoreRelayer__factory.connect(targetCoreRelayerAddress, walletTarget);
const targetMockIntegration = MockRelayerIntegration__factory.connect(targetMockIntegrationAddress, walletTarget);
it("Executes a delivery", async (done) => {
it("Executes a delivery", async () => {
try {
const originalMessage = await targetMockIntegration.getMessage();
console.log(`Sent message: ${originalMessage}`);
const arbitraryPayload = ethers.utils.hexlify(ethers.utils.toUtf8Bytes((Math.random()*1e32).toString(36)))
const arbitraryPayload = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(generateRandomString(32)))
console.log(`Sent message: ${arbitraryPayload}`);
const value = await sourceCoreRelayer.quoteGasDeliveryFee(targetChain.chainId, 1000000, await sourceCoreRelayer.getDefaultRelayProvider());
console.log(`Quoted gas delivery fee: ${value}`)
const tx = await sourceMockIntegration.sendMessage(arbitraryPayload, targetChain.chainId, targetMockIntegrationAddress, targetMockIntegrationAddress, {value, gasLimit: 500000});
@ -46,22 +53,44 @@ describe("Core Relayer Integration Test - Two Chains", () => {
const rx = await tx.wait();
console.log("Message confirmed!");
setTimeout(async () => {
try {
console.log("Checking if message was relayed")
const message = await targetMockIntegration.getMessage();
console.log(`Sent message: ${arbitraryPayload}`);
console.log(`Received message: ${message}`)
expect(message).to.equal(arbitraryPayload);
done()
} catch (e) {
done(e)
}
}, 1000 * 30)
} catch (e) {
done(e);
}
await new Promise((resolve) => {
setTimeout(() => {
resolve(0);
}, 5000)
})
console.log("Checking if message was relayed")
const message = await targetMockIntegration.getMessage();
console.log(`Sent message: ${arbitraryPayload}`);
console.log(`Received message: ${message}`)
expect(message).to.equal(arbitraryPayload);
});
it("Executes a forward", async () => {
const arbitraryPayload = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(generateRandomString(32)))
console.log(`Sent message: ${arbitraryPayload}`);
const value = await sourceCoreRelayer.quoteGasDeliveryFee(targetChain.chainId, 1000000, await sourceCoreRelayer.getDefaultRelayProvider());
console.log(`Quoted gas delivery fee: ${value}`)
const tx = await sourceMockIntegration.sendMessage(arbitraryPayload, targetChain.chainId, targetMockIntegrationAddress, targetMockIntegrationAddress, {value, gasLimit: 500000});
console.log("Sent delivery request!");
const rx = await tx.wait();
console.log("Message confirmed!");
await new Promise((resolve) => {
setTimeout(() => {
resolve(0);
}, 5000)
})
console.log("Checking if message was relayed")
const message = await targetMockIntegration.getMessage();
console.log(`Sent message: ${arbitraryPayload}`);
console.log(`Received message: ${message}`)
expect(message).to.equal(arbitraryPayload);
});
});

View File

@ -1,330 +1,16 @@
import { BigNumber, ethers } from "ethers"
import { ChainId, tryNativeToHexString } from "@certusone/wormhole-sdk"
import { WORMHOLE_MESSAGE_EVENT_ABI, GUARDIAN_PRIVATE_KEY } from "./consts"
const elliptic = require("elliptic")
export async function parseWormholeEventsFromReceipt(
receipt: ethers.ContractReceipt
): Promise<ethers.utils.LogDescription[]> {
// create the wormhole message interface
const wormholeMessageInterface = new ethers.utils.Interface(WORMHOLE_MESSAGE_EVENT_ABI)
// loop through the logs and parse the events that were emitted
const logDescriptions: ethers.utils.LogDescription[] = await Promise.all(
receipt.logs.map(async (log) => {
return wormholeMessageInterface.parseLog(log)
})
)
const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return logDescriptions
}
export function doubleKeccak256(body: ethers.BytesLike) {
return ethers.utils.keccak256(ethers.utils.keccak256(body))
}
function zeroPadBytes(value: string, length: number): string {
while (value.length < 2 * length) {
value = "0" + value
}
return value
}
export async function getSignedBatchVaaFromReceiptOnEth(
receipt: ethers.ContractReceipt,
emitterChainId: ChainId,
guardianSetIndex: number
): Promise<ethers.BytesLike> {
// grab each message from the transaction logs
const messageEvents = await parseWormholeEventsFromReceipt(receipt)
// create a timestamp for the
const timestamp = Math.floor(+new Date() / 1000)
let observationHashes = ""
let encodedObservationsWithLengthPrefix = ""
for (let i = 0; i < messageEvents.length; i++) {
const event = messageEvents[i]
const emitterAddress: ethers.utils.BytesLike = ethers.utils.hexlify(
"0x" + tryNativeToHexString(event.args.sender, emitterChainId)
)
// encode the observation
const encodedObservation = ethers.utils.solidityPack(
["uint32", "uint32", "uint16", "bytes32", "uint64", "uint8", "bytes"],
[
timestamp,
event.args.nonce,
emitterChainId,
emitterAddress,
event.args.sequence,
event.args.consistencyLevel,
event.args.payload,
]
)
// compute the hash of the observation
const hash = doubleKeccak256(encodedObservation)
observationHashes += hash.substring(2)
// grab the index, and length of the observation and add them to the observation bytestring
// divide observationBytes by two to convert string representation length to bytes
const observationElements = [
ethers.utils.solidityPack(["uint8"], [i]).substring(2),
ethers.utils
.solidityPack(["uint32"], [encodedObservation.substring(2).length / 2])
.substring(2),
encodedObservation.substring(2),
]
encodedObservationsWithLengthPrefix += observationElements.join("")
}
// compute the has of batch hashes - hash(hash(VAA1), hash(VAA2), ...)
const batchHash = doubleKeccak256("0x" + observationHashes)
// sign the batchHash
const ec = new elliptic.ec("secp256k1")
const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY)
const signature = key.sign(batchHash.substring(2), { canonical: true })
// create the signature
const packSig = [
ethers.utils.solidityPack(["uint8"], [0]).substring(2),
zeroPadBytes(signature.r.toString(16), 32),
zeroPadBytes(signature.s.toString(16), 32),
ethers.utils.solidityPack(["uint8"], [signature.recoveryParam]).substring(2),
]
const signatures = packSig.join("")
const vm = [
// this is a type 2 VAA since it's a batch
ethers.utils.solidityPack(["uint8"], [2]).substring(2),
ethers.utils.solidityPack(["uint32"], [guardianSetIndex]).substring(2), // guardianSetIndex
ethers.utils.solidityPack(["uint8"], [1]).substring(2), // number of signers
signatures,
ethers.utils.solidityPack(["uint8"], [messageEvents.length]).substring(2),
observationHashes,
ethers.utils.solidityPack(["uint8"], [messageEvents.length]).substring(2),
encodedObservationsWithLengthPrefix,
].join("")
return "0x" + vm
}
export async function getSignedVaaFromReceiptOnEth(
receipt: ethers.ContractReceipt,
emitterChainId: ChainId,
guardianSetIndex: number
) {
//: Promise<ethers.BytesLike> {
// parse the wormhole message logs
const messageEvents = await parseWormholeEventsFromReceipt(receipt)
// find the VAA event
let event
if (messageEvents.length == 1) {
event = messageEvents[0]
} else {
throw new Error("More than one message emitted!")
}
// create a timestamp and find the emitter address
const timestamp = Math.floor(+new Date() / 1000)
const emitterAddress: ethers.utils.BytesLike = ethers.utils.hexlify(
"0x" + tryNativeToHexString(event.args.sender, emitterChainId)
)
// encode the observation
const encodedObservation = ethers.utils.solidityPack(
["uint32", "uint32", "uint16", "bytes32", "uint64", "uint8", "bytes"],
[
timestamp,
event.args.nonce,
emitterChainId,
emitterAddress,
event.args.sequence,
event.args.consistencyLevel,
event.args.payload,
]
)
// compute the hash of the observation
const hash = doubleKeccak256(encodedObservation)
// sign the batchHash
const ec = new elliptic.ec("secp256k1")
const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY)
const signature = key.sign(hash.substring(2), { canonical: true })
// create the signature
const packSig = [
ethers.utils.solidityPack(["uint8"], [0]).substring(2),
zeroPadBytes(signature.r.toString(16), 32),
zeroPadBytes(signature.s.toString(16), 32),
ethers.utils.solidityPack(["uint8"], [signature.recoveryParam]).substring(2),
]
const signatures = packSig.join("")
const vm = [
// this is a type 1 VAA
ethers.utils.solidityPack(["uint8"], [1]).substring(2),
ethers.utils.solidityPack(["uint32"], [guardianSetIndex]).substring(2), // guardianSetIndex
ethers.utils.solidityPack(["uint8"], [1]).substring(2), // number of signers
signatures,
encodedObservation.substring(2),
].join("")
return "0x" + vm
}
export function removeObservationFromBatch(
indexToRemove: number,
encodedVM: ethers.BytesLike
): ethers.BytesLike {
// index of the signature count (number of signers for the VM)
let index: number = 5
// grab the signature count
const sigCount: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
index += 1
// skip the signatures
index += 66 * sigCount
// hash count
const hashCount: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
index += 1
// skip the hashes
index += 32 * hashCount
// observation count
const observationCount: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
const observationCountIndex: number = index // save the index
index += 1
// find the index of the observation that will be removed
let bytesRangeToRemove: number[] = [0, 0]
for (let i = 0; i < observationCount; i++) {
const observationStartIndex = index
// parse the observation index and the observation length
const observationIndex: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
index += 1
const observationLen: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 4)
)
index += 4
// save the index of the observation we want to remove
if (observationIndex == indexToRemove) {
bytesRangeToRemove[0] = observationStartIndex
bytesRangeToRemove[1] = observationStartIndex + 5 + observationLen
export const generateRandomString = (length: number) => {
let randomString = '';
for ( let i = 0; i < length; i++ ) {
randomString += characters.charAt(Math.floor(Math.random() * characters.length));
}
index += observationLen
}
// remove the observation by slicing the original byte array
const newEncodedVMByteArray: ethers.BytesLike[] = [
ethers.utils.hexDataSlice(encodedVM, 0, observationCountIndex),
ethers.utils.hexlify([observationCount - 1]),
ethers.utils.hexDataSlice(
encodedVM,
observationCountIndex + 1,
bytesRangeToRemove[0]
),
ethers.utils.hexDataSlice(encodedVM, bytesRangeToRemove[1], encodedVM.length),
]
return ethers.utils.hexConcat(newEncodedVMByteArray)
return randomString;
}
export function verifyDeliveryStatusPayload(
payload: ethers.BytesLike,
batchHash: ethers.BytesLike,
relayerAddress: ethers.BytesLike,
deliverySequence: BigNumber,
deliveryAttempts: number,
successBoolean: number
): boolean {
// confirm that the payload is formatted correctly
let index: number = 0
// grab the payloadID = 2
const payloadId: number = parseInt(ethers.utils.hexDataSlice(payload, index, index + 1))
index += 1
// delivery batch hash
const deliveryBatchHash: ethers.BytesLike = ethers.utils.hexDataSlice(
payload,
index,
index + 32
)
index += 32
// deliveryId emitter address
const emitterAddress: ethers.BytesLike = ethers.utils.hexDataSlice(
payload,
index,
index + 32
)
index += 32
// deliveryId sequence
const sequence: BigNumber = BigNumber.from(
ethers.utils.hexDataSlice(payload, index, index + 8)
)
index += 8
// delivery count
const deliveryCount: number = parseInt(
ethers.utils.hexDataSlice(payload, index, index + 2)
)
index += 2
// grab the success boolean
const isDelivered: number = parseInt(
ethers.utils.hexDataSlice(payload, index, index + 1)
)
index += 1
// define the expected DeliveryStatus values
const expectedPayloadId = 2
// finally, compare the expected values with the actual values
if (payloadId != expectedPayloadId) {
console.log("Invalid payloadId")
return false
} else if (deliveryBatchHash != batchHash) {
console.log("Invalid batch hash")
return false
} else if (emitterAddress != relayerAddress) {
console.log(
"Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence"
)
return false
} else if (!sequence.eq(deliverySequence)) {
console.log(
"Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence"
)
return false
} else if (deliveryCount != deliveryAttempts) {
console.log("Invalid number of delivery attempts")
return false
} else if (isDelivered != successBoolean) {
console.log("Invalid success boolean")
return false
}
// everything looks good
return true
}