CoreRelayerLibrary for governance + ts scripts for vaa registrations
This commit is contained in:
parent
cd26ace599
commit
5bd7ba46ab
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue