CoreRelayerLibrary for governance + ts scripts for vaa registrations

This commit is contained in:
chase-45 2023-01-09 01:00:40 -05:00 committed by Joe Howarth
parent cd26ace599
commit 5bd7ba46ab
12 changed files with 444 additions and 214 deletions

View File

@ -13,6 +13,7 @@ import "./CoreRelayerStructs.sol";
import "./CoreRelayerMessages.sol";
import "../interfaces/IWormhole.sol";
import "./CoreRelayerLibrary.sol";
abstract contract CoreRelayerGovernance is
CoreRelayerGetters,
@ -34,7 +35,7 @@ abstract contract CoreRelayerGovernance is
setConsumedGovernanceAction(vm.hash);
ContractUpgrade memory contractUpgrade = parseUpgrade(vm.payload);
CoreRelayerLibrary.ContractUpgrade memory contractUpgrade = CoreRelayerLibrary.parseUpgrade(vm.payload, module);
require(contractUpgrade.chain == chainId(), "wrong chain id");
@ -47,7 +48,7 @@ abstract contract CoreRelayerGovernance is
setConsumedGovernanceAction(vm.hash);
RegisterChain memory rc = parseRegisterChain(vm.payload);
CoreRelayerLibrary.RegisterChain memory rc = CoreRelayerLibrary.parseRegisterChain(vm.payload, module);
require((rc.chain == chainId() && !isFork()) || rc.chain == 0, "invalid chain id");
@ -60,104 +61,24 @@ abstract contract CoreRelayerGovernance is
setConsumedGovernanceAction(vm.hash);
UpdateDefaultProvider memory provider = parseUpdateDefaultProvider(vm.payload);
CoreRelayerLibrary.UpdateDefaultProvider memory provider = CoreRelayerLibrary.parseUpdateDefaultProvider(vm.payload, module);
require((provider.chain == chainId() && !isFork()) || provider.chain == 0, "invalid chain id");
setRelayProvider(provider.newProvider);
}
function parseUpgrade(bytes memory encodedUpgrade) public pure returns (ContractUpgrade memory cu) {
uint index = 0;
function upgradeImplementation(address newImplementation) internal {
address currentImplementation = _getImplementation();
cu.module = encodedUpgrade.toBytes32(index);
index += 32;
_upgradeTo(newImplementation);
require(cu.module == module, "wrong module");
// Call initialize function of the new implementation
(bool success, bytes memory reason) = newImplementation.delegatecall(abi.encodeWithSignature("initialize()"));
cu.action = encodedUpgrade.toUint8(index);
index += 1;
require(success, string(reason));
require(cu.action == 1, "invalid ContractUpgrade");
cu.chain = encodedUpgrade.toUint16(index);
index += 2;
cu.newContract = address(uint160(uint256(encodedUpgrade.toBytes32(index))));
index += 32;
require(encodedUpgrade.length == index, "invalid ContractUpgrade");
}
function parseRegisterChain(bytes memory encodedRegistration) public pure returns (RegisterChain memory registerChain) {
uint index = 0;
registerChain.module = encodedRegistration.toBytes32(index);
index += 32;
require(registerChain.module == module, "wrong module");
registerChain.action = encodedRegistration.toUint8(index);
index += 1;
registerChain.chain = encodedRegistration.toUint16(index);
index += 2;
require(registerChain.action == 2, "invalid RegisterChain");
registerChain.emitterChain = encodedRegistration.toUint16(index);
index += 2;
registerChain.emitterAddress = encodedRegistration.toBytes32(index);
index += 32;
require(encodedRegistration.length == index, "invalid RegisterChain");
}
function parseUpdateDefaultProvider(bytes memory encodedDefaultProvider) public pure returns (UpdateDefaultProvider memory defaultProvider) {
uint index = 0;
defaultProvider.module = encodedDefaultProvider.toBytes32(index);
index += 32;
require(defaultProvider.module == module, "wrong module");
defaultProvider.action = encodedDefaultProvider.toUint8(index);
index += 1;
require(defaultProvider.action == 3, "invalid DefaultProvider");
defaultProvider.chain = encodedDefaultProvider.toUint16(index);
index += 2;
defaultProvider.newProvider = address(uint160(uint256(encodedDefaultProvider.toBytes32(index))));
index += 32;
require(encodedDefaultProvider.length == index, "invalid DefaultProvider");
}
struct ContractUpgrade {
bytes32 module;
uint8 action;
uint16 chain;
address newContract;
}
struct RegisterChain {
bytes32 module;
uint8 action;
uint16 chain; //TODO Why is this on this object?
uint16 emitterChain;
bytes32 emitterAddress;
}
//This could potentially be combined with ContractUpgrade
struct UpdateDefaultProvider {
bytes32 module;
uint8 action;
uint16 chain;
address newProvider;
emit ContractUpgraded(currentImplementation, newImplementation);
}
function verifyGovernanceVM(bytes memory encodedVM) internal view returns (IWormhole.VM memory parsedVM, bool isValid, string memory invalidReason){
@ -180,17 +101,4 @@ abstract contract CoreRelayerGovernance is
return (vm, true, "");
}
function upgradeImplementation(address newImplementation) internal {
address currentImplementation = _getImplementation();
_upgradeTo(newImplementation);
// Call initialize function of the new implementation
(bool success, bytes memory reason) = newImplementation.delegatecall(abi.encodeWithSignature("initialize()"));
require(success, string(reason));
emit ContractUpgraded(currentImplementation, newImplementation);
}
}

View File

@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../libraries/external/BytesLib.sol";
library CoreRelayerLibrary {
using BytesLib for bytes;
function parseUpgrade(bytes memory encodedUpgrade, bytes32 module) public pure returns (ContractUpgrade memory cu) {
uint index = 0;
cu.module = encodedUpgrade.toBytes32(index);
index += 32;
require(cu.module == module, "wrong module");
cu.action = encodedUpgrade.toUint8(index);
index += 1;
require(cu.action == 1, "invalid ContractUpgrade");
cu.chain = encodedUpgrade.toUint16(index);
index += 2;
cu.newContract = address(uint160(uint256(encodedUpgrade.toBytes32(index))));
index += 32;
require(encodedUpgrade.length == index, "invalid ContractUpgrade");
}
function parseRegisterChain(bytes memory encodedRegistration, bytes32 module) public pure returns (RegisterChain memory registerChain) {
uint index = 0;
registerChain.module = encodedRegistration.toBytes32(index);
index += 32;
require(registerChain.module == module, "wrong module");
registerChain.action = encodedRegistration.toUint8(index);
index += 1;
registerChain.chain = encodedRegistration.toUint16(index);
index += 2;
require(registerChain.action == 2, "invalid RegisterChain");
registerChain.emitterChain = encodedRegistration.toUint16(index);
index += 2;
registerChain.emitterAddress = encodedRegistration.toBytes32(index);
index += 32;
require(encodedRegistration.length == index, "invalid RegisterChain");
}
function parseUpdateDefaultProvider(bytes memory encodedDefaultProvider, bytes32 module) public pure returns (UpdateDefaultProvider memory defaultProvider) {
uint index = 0;
defaultProvider.module = encodedDefaultProvider.toBytes32(index);
index += 32;
require(defaultProvider.module == module, "wrong module");
defaultProvider.action = encodedDefaultProvider.toUint8(index);
index += 1;
require(defaultProvider.action == 3, "invalid DefaultProvider");
defaultProvider.chain = encodedDefaultProvider.toUint16(index);
index += 2;
defaultProvider.newProvider = address(uint160(uint256(encodedDefaultProvider.toBytes32(index))));
index += 32;
require(encodedDefaultProvider.length == index, "invalid DefaultProvider");
}
struct ContractUpgrade {
bytes32 module;
uint8 action;
uint16 chain;
address newContract;
}
struct RegisterChain {
bytes32 module;
uint8 action;
uint16 chain; //TODO Why is this on this object?
uint16 emitterChain;
bytes32 emitterAddress;
}
//This could potentially be combined with ContractUpgrade
struct UpdateDefaultProvider {
bytes32 module;
uint8 action;
uint16 chain;
address newProvider;
}
}

View File

@ -57,7 +57,7 @@ contract CoreRelayerSetters is CoreRelayerState, Context {
}
function setEvmChainId(uint256 evmChainId) internal {
require(evmChainId == block.chainid, "invalid evmChainId");
require(evmChainId == block.chainid, "invalid evmChainId ");
_state.evmChainId = evmChainId;
}
}

View File

@ -1,8 +1,10 @@
{
"guardianSetIndex": 0,
"description": "This file contains the chains against which all the scripts should run.",
"chains": [
{
"evmNetworkId": 1337,
"description": "Ganache is supposed to be 1337, but the on-chain block.chainid returns 1",
"evmNetworkId": 1,
"chainId": 2,
"rpc": "http://localhost:8545",
"wormholeAddress": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"

View File

@ -1,5 +1,6 @@
import {
deployCoreRelayerImplementation,
deployCoreRelayerLibrary,
deployCoreRelayerProxy,
deployCoreRelayerSetup,
} from "../helpers/deployments"
@ -18,13 +19,18 @@ async function run() {
console.log("Start! " + processName)
const output: any = {
coreRelayerLibraries: [],
coreRelayerImplementations: [],
coreRelayerSetups: [],
coreRelayerProxies: [],
}
for (let i = 0; i < chains.length; i++) {
const coreRelayerImplementation = await deployCoreRelayerImplementation(chains[i])
const coreRelayerLibrary = await deployCoreRelayerLibrary(chains[i])
const coreRelayerImplementation = await deployCoreRelayerImplementation(
chains[i],
coreRelayerLibrary.address
)
const coreRelayerSetup = await deployCoreRelayerSetup(chains[i])
const coreRelayerProxy = await deployCoreRelayerProxy(
chains[i],
@ -34,6 +40,7 @@ async function run() {
getRelayProviderAddress(chains[i])
)
output.coreRelayerLibraries.push(coreRelayerLibrary)
output.coreRelayerImplementations.push(coreRelayerImplementation)
output.coreRelayerSetups.push(coreRelayerSetup)
output.coreRelayerProxies.push(coreRelayerProxy)

View File

@ -1,4 +1,7 @@
import { deployCoreRelayerImplementation } from "../helpers/deployments"
import {
deployCoreRelayerImplementation,
deployCoreRelayerLibrary,
} from "../helpers/deployments"
import { init, loadChains, writeOutputFiles } from "../helpers/env"
const processName = "deployCoreRelayerImpl"
@ -9,12 +12,18 @@ async function run() {
console.log("Start! " + processName)
const output: any = {
coreRelayerLibraries: [],
coreRelayerImplementations: [],
}
for (let i = 0; i < chains.length; i++) {
const coreRelayerImplementation = await deployCoreRelayerImplementation(chains[i])
const coreRelayerLibrary = await deployCoreRelayerLibrary(chains[i])
const coreRelayerImplementation = await deployCoreRelayerImplementation(
chains[i],
coreRelayerLibrary.address
)
output.coreRelayerImplementations.push(coreRelayerImplementation)
output.coreRelayerLibraries.push(coreRelayerLibrary)
}
writeOutputFiles(output, processName)

View File

@ -7,6 +7,7 @@ import {
getRelayProviderAddress,
getCoreRelayerAddress,
} from "../helpers/env"
import { createRegisterChainVAA, createDefaultRelayProviderVAA } from "../helpers/vaa"
const processName = "registerChainsCoreRelayer"
init()
@ -24,15 +25,11 @@ async function registerChainsCoreRelayer(chain: ChainInfo) {
console.log("registerChainsCoreRelayer " + chain.chainId)
const coreRelayer = getCoreRelayer(chain)
const relayProviderAddress = getRelayProviderAddress(chain)
await coreRelayer.setDefaultRelayProvider(relayProviderAddress)
await coreRelayer.setDefaultRelayProvider(createDefaultRelayProviderVAA(chain))
for (let i = 0; i < chains.length; i++) {
await coreRelayer.registerCoreRelayerContract(
chains[i].chainId,
"0x" + tryNativeToHexString(getCoreRelayerAddress(chains[i]), "ethereum")
)
await coreRelayer.registerCoreRelayerContract(createRegisterChainVAA(chains[i]))
}
console.log("Did all contract registrations for the core relayer on " + chain.chainId)

View File

@ -5,6 +5,7 @@ import { MockRelayerIntegration__factory } from "../../../sdk/src"
import { CoreRelayerProxy__factory } from "../../../sdk/src/ethers-contracts/factories/CoreRelayerProxy__factory"
import { CoreRelayerSetup__factory } from "../../../sdk/src/ethers-contracts/factories/CoreRelayerSetup__factory"
import { CoreRelayerImplementation__factory } from "../../../sdk/src/ethers-contracts/factories/CoreRelayerImplementation__factory"
import { CoreRelayerLibrary__factory } from "../../../sdk/src/ethers-contracts/factories/CoreRelayerLibrary__factory"
import {
init,
@ -92,15 +93,47 @@ export async function deployMockIntegration(chain: ChainInfo): Promise<Deploymen
})
}
export async function deployCoreRelayerLibrary(chain: ChainInfo): Promise<Deployment> {
console.log("deployCoreRelayerLibrary " + chain.chainId)
let signer = getSigner(chain)
const contractInterface = CoreRelayerLibrary__factory.createInterface()
const bytecode = CoreRelayerLibrary__factory.bytecode
const factory = new ethers.ContractFactory(contractInterface, bytecode, signer)
const contract = await factory.deploy()
return await contract.deployed().then((result) => {
console.log("Successfully deployed contract at " + result.address)
return { address: result.address, chainId: chain.chainId }
})
}
export async function deployCoreRelayerImplementation(
chain: ChainInfo
chain: ChainInfo,
coreRelayerLibraryAddress: string
): Promise<Deployment> {
console.log("deployCoreRelayerImplementation " + chain.chainId)
const signer = getSigner(chain)
const contractInterface = CoreRelayerImplementation__factory.createInterface()
const bytecode = CoreRelayerImplementation__factory.bytecode
const bytecode: string = CoreRelayerImplementation__factory.bytecode
/*
Linked libraries in EVM are contained in the bytecode and linked at compile time.
However, the linked address of the CoreRelayerLibrary is not known until deployment time,
So, rather that recompiling the contracts with a static link, we modify the bytecode directly
once we have the CoreRelayLibraryAddress.
*/
const bytecodeWithLibraryLink = link(
bytecode,
"CoreRelayerLibrary",
coreRelayerLibraryAddress
)
//@ts-ignore
const factory = new ethers.ContractFactory(contractInterface, bytecode, signer)
const factory = new ethers.ContractFactory(
contractInterface,
bytecodeWithLibraryLink,
signer
)
const contract = await factory.deploy()
return await contract.deployed().then((result) => {
console.log("Successfully deployed contract at " + result.address)
@ -156,3 +189,11 @@ export async function deployCoreRelayerProxy(
return { address: result.address, chainId: chain.chainId }
})
}
function link(bytecode: string, libName: String, libAddress: string) {
//This doesn't handle the libName, because Forge embed a psuedonym into the bytecode, like
//__$a7dd444e34bd28bbe3641e0101a6826fa7$__
//This means we can't link more than one library per bytecode
//const example = "__$a7dd444e34bd28bbe3641e0101a6826fa7$__"
let symbol = /__.*?__/g
return bytecode.replace(symbol, libAddress.toLowerCase().substr(2))
}

View File

@ -69,6 +69,15 @@ export function loadPrivateKey(): string {
return privateKey
}
export function loadGuardianSetIndex(): number {
const chainFile = fs.readFileSync(`./ts-scripts/config/${env}/chains.json`)
const chains = JSON.parse(chainFile.toString())
if (chains.guardianSetIndex == undefined) {
throw Error("Failed to pull guardian set index from the chains file!")
}
return chains.guardianSetIndex
}
export function loadRelayProviders(): Deployment[] {
const contractsFile = fs.readFileSync(`./ts-scripts/config/${env}/contracts.json`)
if (!contractsFile) {

View File

@ -0,0 +1,118 @@
import { BigNumber, ethers } from "ethers"
import { ChainId, tryNativeToHexString } from "@certusone/wormhole-sdk"
import {
ChainInfo,
getCoreRelayerAddress,
getRelayProviderAddress,
loadGuardianKey,
loadGuardianSetIndex,
} from "./env"
const elliptic = require("elliptic")
const governanceChainId = 1
const governanceContract =
"0x0000000000000000000000000000000000000000000000000000000000000004"
//don't use the variable module in global scope in node
const coreRelayerModule =
"0x000000000000000000000000000000000000000000436f726552656c61796572"
export function createDefaultRelayProviderVAA(chain: ChainInfo) {
/*
bytes32 module;
uint8 action;
uint16 chain;
bytes32 newProvider; //Struct in the contract is an address, wire type is a wh format 32
*/
const payload = ethers.utils.solidityPack(
["bytes32", "uint8", "uint16", "bytes32"],
[
coreRelayerModule,
3,
chain.chainId,
"0x" + tryNativeToHexString(getRelayProviderAddress(chain), "ethereum"),
]
)
return encodeAndSignGovernancePayload(payload)
}
export function createRegisterChainVAA(chain: ChainInfo): string {
const coreRelayerAddress = getCoreRelayerAddress(chain)
// bytes32 module;
// uint8 action;
// uint16 chain; //0
// uint16 emitterChain;
// bytes32 emitterAddress;
const payload = ethers.utils.solidityPack(
["bytes32", "uint8", "uint16", "uint16", "bytes32"],
[
coreRelayerModule,
2,
0,
chain.chainId,
"0x" + tryNativeToHexString(coreRelayerAddress, "ethereum"),
]
)
return encodeAndSignGovernancePayload(payload)
}
export function encodeAndSignGovernancePayload(payload: string): string {
const timestamp = Math.floor(+new Date() / 1000)
const nonce = 1
const sequence = 1
const consistencyLevel = 1
const encodedVAABody = ethers.utils.solidityPack(
["uint32", "uint32", "uint16", "bytes32", "uint64", "uint8", "bytes"],
[
timestamp,
nonce,
governanceChainId,
governanceContract,
sequence,
consistencyLevel,
payload,
]
)
const hash = doubleKeccak256(encodedVAABody)
// sign the hash
const ec = new elliptic.ec("secp256k1")
const key = ec.keyFromPrivate(loadGuardianKey())
const signature = key.sign(hash.substring(2), { canonical: true })
// pack the signatures
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 = [
ethers.utils.solidityPack(["uint8"], [1]).substring(2),
ethers.utils.solidityPack(["uint32"], [loadGuardianSetIndex()]).substring(2), // guardianSetIndex
ethers.utils.solidityPack(["uint8"], [1]).substring(2), // number of signers
signatures,
encodedVAABody.substring(2),
].join("")
return "0x" + vm
}
export function doubleKeccak256(body: ethers.BytesLike) {
return ethers.utils.keccak256(ethers.utils.keccak256(body))
}
export function zeroPadBytes(value: string, length: number): string {
while (value.length < 2 * length) {
value = "0" + value
}
return value
}

View File

@ -1 +1 @@
ts-node ./ts-scripts/relayProvider/deployRelayProvider.ts && ts-node ./ts-scripts/coreRelayer/deployCoreRelayer.ts && ts-node ./ts-scripts/relayProvider/registerChainsRelayProvider.ts && ts-node ./ts-scripts/coreRelayer/registerChainsCoreRelayer.ts && ts-node ./ts-scripts/relayProvider/configureRelayProvider.ts && ts-node ./ts-scripts/mockIntegration/deployMockIntegration.ts && ts-node ./ts-scripts/mockIntegration/messageTest.ts
ts-node ./ts-scripts/relayProvider/deployRelayProvider.ts && ts-node ./ts-scripts/coreRelayer/deployCoreRelayer.ts && ts-node ./ts-scripts/relayProvider/registerChainsRelayProvider.ts && ts-node ./ts-scripts/coreRelayer/registerChainsCoreRelayerSelfSign.ts && ts-node ./ts-scripts/relayProvider/configureRelayProvider.ts && ts-node ./ts-scripts/mockIntegration/deployMockIntegration.ts && ts-node ./ts-scripts/mockIntegration/messageTest.ts

View File

@ -1,33 +1,33 @@
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");
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);
const wormholeMessageInterface = new ethers.utils.Interface(WORMHOLE_MESSAGE_EVENT_ABI)
// loop through the logs and parse the events that were emitted
const logDescriptions: ethers.utils.LogDescription[] = await Promise.all(
receipt.logs.map(async (log) => {
return wormholeMessageInterface.parseLog(log);
return wormholeMessageInterface.parseLog(log)
})
);
)
return logDescriptions;
return logDescriptions
}
export function doubleKeccak256(body: ethers.BytesLike) {
return ethers.utils.keccak256(ethers.utils.keccak256(body));
return ethers.utils.keccak256(ethers.utils.keccak256(body))
}
function zeroPadBytes(value: string, length: number): string {
while (value.length < 2 * length) {
value = "0" + value;
value = "0" + value
}
return value;
return value
}
export async function getSignedBatchVaaFromReceiptOnEth(
@ -36,19 +36,19 @@ export async function getSignedBatchVaaFromReceiptOnEth(
guardianSetIndex: number
): Promise<ethers.BytesLike> {
// grab each message from the transaction logs
const messageEvents = await parseWormholeEventsFromReceipt(receipt);
const messageEvents = await parseWormholeEventsFromReceipt(receipt)
// create a timestamp for the
const timestamp = Math.floor(+new Date() / 1000);
const timestamp = Math.floor(+new Date() / 1000)
let observationHashes = "";
let encodedObservationsWithLengthPrefix = "";
let observationHashes = ""
let encodedObservationsWithLengthPrefix = ""
for (let i = 0; i < messageEvents.length; i++) {
const event = messageEvents[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(
@ -62,29 +62,31 @@ export async function getSignedBatchVaaFromReceiptOnEth(
event.args.consistencyLevel,
event.args.payload,
]
);
)
// compute the hash of the observation
const hash = doubleKeccak256(encodedObservation);
observationHashes += hash.substring(2);
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),
ethers.utils
.solidityPack(["uint32"], [encodedObservation.substring(2).length / 2])
.substring(2),
encodedObservation.substring(2),
];
encodedObservationsWithLengthPrefix += observationElements.join("");
]
encodedObservationsWithLengthPrefix += observationElements.join("")
}
// compute the has of batch hashes - hash(hash(VAA1), hash(VAA2), ...)
const batchHash = doubleKeccak256("0x" + observationHashes);
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 });
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 = [
@ -92,8 +94,8 @@ export async function getSignedBatchVaaFromReceiptOnEth(
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 signatures = packSig.join("")
const vm = [
// this is a type 2 VAA since it's a batch
@ -105,9 +107,9 @@ export async function getSignedBatchVaaFromReceiptOnEth(
observationHashes,
ethers.utils.solidityPack(["uint8"], [messageEvents.length]).substring(2),
encodedObservationsWithLengthPrefix,
].join("");
].join("")
return "0x" + vm;
return "0x" + vm
}
export async function getSignedVaaFromReceiptOnEth(
@ -117,21 +119,21 @@ export async function getSignedVaaFromReceiptOnEth(
) {
//: Promise<ethers.BytesLike> {
// parse the wormhole message logs
const messageEvents = await parseWormholeEventsFromReceipt(receipt);
const messageEvents = await parseWormholeEventsFromReceipt(receipt)
// find the VAA event
let event;
let event
if (messageEvents.length == 1) {
event = messageEvents[0];
event = messageEvents[0]
} else {
throw new Error("More than one message emitted!");
throw new Error("More than one message emitted!")
}
// create a timestamp and find the emitter address
const timestamp = Math.floor(+new Date() / 1000);
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(
@ -145,15 +147,15 @@ export async function getSignedVaaFromReceiptOnEth(
event.args.consistencyLevel,
event.args.payload,
]
);
)
// compute the hash of the observation
const hash = doubleKeccak256(encodedObservation);
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 });
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 = [
@ -161,8 +163,8 @@ export async function getSignedVaaFromReceiptOnEth(
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 signatures = packSig.join("")
const vm = [
// this is a type 1 VAA
@ -171,62 +173,79 @@ export async function getSignedVaaFromReceiptOnEth(
ethers.utils.solidityPack(["uint8"], [1]).substring(2), // number of signers
signatures,
encodedObservation.substring(2),
].join("");
].join("")
return "0x" + vm;
return "0x" + vm
}
export function removeObservationFromBatch(indexToRemove: number, encodedVM: ethers.BytesLike): ethers.BytesLike {
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;
let index: number = 5
// grab the signature count
const sigCount: number = parseInt(ethers.utils.hexDataSlice(encodedVM, index, index + 1));
index += 1;
const sigCount: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
index += 1
// skip the signatures
index += 66 * sigCount;
index += 66 * sigCount
// hash count
const hashCount: number = parseInt(ethers.utils.hexDataSlice(encodedVM, index, index + 1));
index += 1;
const hashCount: number = parseInt(
ethers.utils.hexDataSlice(encodedVM, index, index + 1)
)
index += 1
// skip the hashes
index += 32 * hashCount;
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;
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];
let bytesRangeToRemove: number[] = [0, 0]
for (let i = 0; i < observationCount; i++) {
const observationStartIndex = index;
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 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;
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;
bytesRangeToRemove[0] = observationStartIndex
bytesRangeToRemove[1] = observationStartIndex + 5 + observationLen
}
index += observationLen;
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,
observationCountIndex + 1,
bytesRangeToRemove[0]
),
ethers.utils.hexDataSlice(encodedVM, bytesRangeToRemove[1], encodedVM.length),
];
return ethers.utils.hexConcat(newEncodedVMByteArray);
]
return ethers.utils.hexConcat(newEncodedVMByteArray)
}
export function verifyDeliveryStatusPayload(
@ -238,56 +257,74 @@ export function verifyDeliveryStatusPayload(
successBoolean: number
): boolean {
// confirm that the payload is formatted correctly
let index: number = 0;
let index: number = 0
// grab the payloadID = 2
const payloadId: number = parseInt(ethers.utils.hexDataSlice(payload, index, index + 1));
index += 1;
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;
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;
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;
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;
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;
const isDelivered: number = parseInt(
ethers.utils.hexDataSlice(payload, index, index + 1)
)
index += 1
// define the expected DeliveryStatus values
const expectedPayloadId = 2;
const expectedPayloadId = 2
// finally, compare the expected values with the actual values
if (payloadId != expectedPayloadId) {
console.log("Invalid payloadId");
return false;
console.log("Invalid payloadId")
return false
} else if (deliveryBatchHash != batchHash) {
console.log("Invalid batch hash");
return false;
console.log("Invalid batch hash")
return false
} else if (emitterAddress != relayerAddress) {
console.log("Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence");
return false;
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;
console.log(
"Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence"
)
return false
} else if (deliveryCount != deliveryAttempts) {
console.log("Invalid number of delivery attempts");
return false;
console.log("Invalid number of delivery attempts")
return false
} else if (isDelivered != successBoolean) {
console.log("Invalid success boolean");
return false;
console.log("Invalid success boolean")
return false
}
// everything looks good
return true;
return true
}