core relayer contracts upgrade via governance

This commit is contained in:
chase-45 2023-01-07 16:47:01 -05:00 committed by Joe Howarth
parent b50fd28283
commit cd26ace599
12 changed files with 241 additions and 149 deletions

View File

@ -13,12 +13,16 @@ import "../libraries/external/BytesLib.sol";
contract CoreRelayerGetters is CoreRelayerState {
using BytesLib for bytes;
function owner() public view returns (address) {
return _state.owner;
function governanceActionIsConsumed(bytes32 hash) public view returns (bool) {
return _state.consumedGovernanceActions[hash];
}
function pendingOwner() public view returns (address) {
return _state.pendingOwner;
function governanceChainId() public view returns (uint16){
return _state.provider.governanceChainId;
}
function governanceContract() public view returns (bytes32){
return _state.provider.governanceContract;
}
function isInitialized(address impl) public view returns (bool) {
@ -33,6 +37,14 @@ contract CoreRelayerGetters is CoreRelayerState {
return _state.provider.chainId;
}
function evmChainId() public view returns (uint256) {
return _state.evmChainId;
}
function isFork() public view returns (bool) {
return evmChainId() != block.chainid;
}
function registeredCoreRelayerContract(uint16 chain) public view returns (bytes32) {
return _state.registeredCoreRelayerContract[chain];
}

View File

@ -20,74 +20,177 @@ abstract contract CoreRelayerGovernance is
CoreRelayerMessages,
ERC1967Upgrade
{
//TODO convert this upgrade to being managed by guardian VAAs
using BytesLib for bytes;
event ContractUpgraded(address indexed oldContract, address indexed newContract);
// event ContractUpgraded(address indexed oldContract, address indexed newContract);
// event OwnershipTransfered(address indexed oldOwner, address indexed newOwner);
// event RelayProviderUpdated(address indexed newDefaultRelayProvider);
// "CoreRelayer" (left padded)
bytes32 constant module = 0x000000000000000000000000000000000000000000436f726552656c61796572;
/// @dev registerCoreRelayerContract registers other relayer contracts with this relayer
function registerCoreRelayerContract(uint16 chainId, bytes32 coreRelayerContractAddress) public onlyOwner {
require(coreRelayerContractAddress != bytes32(0), "1"); //"invalid contract address");
require(chainId != 0, "3"); //"invalid chainId");
function submitContractUpgrade(bytes memory _vm) public {
require(!isFork(), "invalid fork");
setRegisteredCoreRelayerContract(chainId, coreRelayerContractAddress);
(IWormhole.VM memory vm, bool valid, string memory reason) = verifyGovernanceVM(_vm);
require(valid, reason);
setConsumedGovernanceAction(vm.hash);
ContractUpgrade memory contractUpgrade = parseUpgrade(vm.payload);
require(contractUpgrade.chain == chainId(), "wrong chain id");
upgradeImplementation(contractUpgrade.newContract);
}
/// @dev upgrade serves to upgrade contract implementations
function upgrade(uint16 thisRelayerChainId, address newImplementation) public onlyOwner {
require(thisRelayerChainId == chainId(), "3");
function registerCoreRelayerContract(bytes memory vaa) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = verifyGovernanceVM(vaa);
require(valid, reason);
setConsumedGovernanceAction(vm.hash);
RegisterChain memory rc = parseRegisterChain(vm.payload);
require((rc.chain == chainId() && !isFork()) || rc.chain == 0, "invalid chain id");
setRegisteredCoreRelayerContract(rc.emitterChain, rc.emitterAddress);
}
function setDefaultRelayProvider(bytes memory vaa) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = verifyGovernanceVM(vaa);
require(valid, reason);
setConsumedGovernanceAction(vm.hash);
UpdateDefaultProvider memory provider = parseUpdateDefaultProvider(vm.payload);
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;
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) 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;
}
function verifyGovernanceVM(bytes memory encodedVM) internal view returns (IWormhole.VM memory parsedVM, bool isValid, string memory invalidReason){
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVM);
if (!valid) {
return (vm, valid, reason);
}
if (vm.emitterChainId != governanceChainId()) {
return (vm, false, "wrong governance chain");
}
if (vm.emitterAddress != governanceContract()) {
return (vm, false, "wrong governance contract");
}
if (governanceActionIsConsumed(vm.hash)) {
return (vm, false, "governance action already consumed");
}
return (vm, true, "");
}
function upgradeImplementation(address newImplementation) internal {
address currentImplementation = _getImplementation();
_upgradeTo(newImplementation);
// call initialize function of the new implementation
// 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);
}
/**
* @dev submitOwnershipTransferRequest serves to begin the ownership transfer process of the contracts
* - it saves an address for the new owner in the pending state
*/
function submitOwnershipTransferRequest(uint16 thisRelayerChainId, address newOwner) public onlyOwner {
require(thisRelayerChainId == chainId(), "4");
require(newOwner != address(0), "5");
setPendingOwner(newOwner);
}
/**
* @dev confirmOwnershipTransferRequest serves to finalize an ownership transfer
* - it checks that the caller is the pendingOwner to validate the wallet address
* - it updates the owner state variable with the pendingOwner state variable
*/
function confirmOwnershipTransferRequest() public {
// cache the new owner address
address newOwner = pendingOwner();
require(msg.sender == newOwner, "6");
// cache currentOwner for Event
address currentOwner = owner();
// update the owner in the contract state and reset the pending owner
setOwner(newOwner);
setPendingOwner(address(0));
//emit OwnershipTransfered(currentOwner, newOwner);
}
function setDefaultRelayProvider(address relayProvider) public onlyOwner {
setRelayProvider(relayProvider);
}
modifier onlyOwner() {
require(owner() == _msgSender(), "7");
_;
emit ContractUpgraded(currentImplementation, newImplementation);
}
}

View File

@ -8,18 +8,22 @@ import "@openzeppelin/contracts/utils/Context.sol";
import "./CoreRelayerStructs.sol";
contract CoreRelayerSetters is CoreRelayerState, Context {
function setOwner(address owner_) internal {
_state.owner = owner_;
}
function setPendingOwner(address newOwner) internal {
_state.pendingOwner = newOwner;
}
function setInitialized(address implementation) internal {
_state.initializedImplementations[implementation] = true;
}
function setConsumedGovernanceAction(bytes32 hash) internal{
_state.consumedGovernanceActions[hash] = true;
}
function setGovernanceChainId(uint16 chainId) internal {
_state.provider.governanceChainId = chainId;
}
function setGovernanceContract(bytes32 governanceContract) internal {
_state.provider.governanceContract = governanceContract;
}
function setChainId(uint16 chainId_) internal {
_state.provider.chainId = chainId_;
}
@ -51,4 +55,9 @@ contract CoreRelayerSetters is CoreRelayerState, Context {
function setContractLock(bool status) internal {
_state.contractLock = status;
}
function setEvmChainId(uint256 evmChainId) internal {
require(evmChainId == block.chainid, "invalid evmChainId");
_state.evmChainId = evmChainId;
}
}

View File

@ -8,20 +8,22 @@ import "./CoreRelayerGovernance.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
contract CoreRelayerSetup is CoreRelayerSetters, ERC1967Upgrade {
function setup(address implementation, uint16 chainId, address wormhole, address defaultRelayProvider) public {
function setup(address implementation, uint16 chainId, address wormhole, address defaultRelayProvider, uint16 governanceChainId, bytes32 governanceContract, uint256 evmChainId) public {
// sanity check initial values
require(implementation != address(0), "1"); //"implementation cannot be address(0)");
require(wormhole != address(0), "2"); //wormhole cannot be address(0)");
require(defaultRelayProvider != address(0), "3"); //default relay provider cannot be address(0)");
setOwner(_msgSender());
setChainId(chainId);
setWormhole(wormhole);
setRelayProvider(defaultRelayProvider);
setGovernanceChainId(governanceChainId);
setGovernanceContract(governanceContract);
setEvmChainId(evmChainId);
//setRegisteredCoreRelayerContract(chainId, bytes32(uint256(uint160(address(this)))));
_upgradeTo(implementation);

View File

@ -9,16 +9,18 @@ contract CoreRelayerStorage {
struct Provider {
uint16 chainId;
address payable wormhole;
uint16 governanceChainId;
bytes32 governanceContract;
}
struct State {
Provider provider;
// delivery lock for reentrancy protection
bool contractLock;
// authority of these contracts
address owner;
// intermediate state when transfering contract ownership
address pendingOwner;
// EIP-155 Chain ID
uint256 evmChainId;
// consumed governance VAAs
mapping(bytes32 => bool) consumedGovernanceActions;
// address of the default relay provider on this chain
address defaultRelayProvider;
// Request which will be forwarded from the current delivery.

View File

@ -21,7 +21,8 @@
"integration-test": "bash shell-scripts/run_integration_tests.sh",
"typechain": "bash ../sdk/scripts/make_ethers_types.sh",
"flatten": "mkdir -p node_modules/@poanet/solidity-flattener/contracts && cp -r contracts/* node_modules/@poanet/solidity-flattener/contracts/ && poa-solidity-flattener",
"deployAndConfigureTilt": "ENV=tilt bash ./ts-scripts/shell/deployConfigureTest.sh"
"deployAndConfigureTilt": "ENV=tilt bash ./ts-scripts/shell/deployConfigureTest.sh",
"size": "forge build --sizes --force"
},
"author": "",
"license": "ISC",

View File

@ -16,7 +16,7 @@ All other configuration is done through files in the ./config/${env} directory.
## Running the scripts
All files in the coreRelayer, relayProvider, and MockIntegration directories are runnable.
All files in the coreRelayer, relayProvider, and MockIntegration directories are runnable. These are intended to run from the /ethereum directory.
The target environment must be passed in as an environment variable. So, for example, you can run the RelayProvider deployment script by running:

View File

@ -0,0 +1,23 @@
import { deployCoreRelayerImplementation } from "../helpers/deployments"
import { init, loadChains, writeOutputFiles } from "../helpers/env"
const processName = "deployCoreRelayerImpl"
init()
const chains = loadChains()
async function run() {
console.log("Start! " + processName)
const output: any = {
coreRelayerImplementations: [],
}
for (let i = 0; i < chains.length; i++) {
const coreRelayerImplementation = await deployCoreRelayerImplementation(chains[i])
output.coreRelayerImplementations.push(coreRelayerImplementation)
}
writeOutputFiles(output, processName)
}
run().then(() => console.log("Done! " + processName))

View File

@ -134,13 +134,20 @@ export async function deployCoreRelayerProxy(
//@ts-ignore
const factory = new ethers.ContractFactory(contractInterface, bytecode, signer)
let ABI = ["function setup(address,uint16,address,address)"]
const governanceChainId = 1
const governanceContract =
"0x0000000000000000000000000000000000000000000000000000000000000004"
let ABI = ["function setup(address,uint16,address,address,uint16,bytes32,uint256)"]
let iface = new ethers.utils.Interface(ABI)
let encodedData = iface.encodeFunctionData("setup", [
coreRelayerImplementationAddress,
chain.chainId,
wormholeAddress,
relayProviderProxyAddress,
governanceChainId,
governanceContract,
chain.evmNetworkId,
])
const contract = await factory.deploy(coreRelayerSetupAddress, encodedData)

View File

@ -1,16 +1,9 @@
import { RelayProviderProxy__factory } from "../../../sdk/src/ethers-contracts/factories/RelayProviderProxy__factory"
import { RelayProviderSetup__factory } from "../../../sdk/src/ethers-contracts/factories/RelayProviderSetup__factory"
import { RelayProviderImplementation__factory } from "../../../sdk/src/ethers-contracts/factories/RelayProviderImplementation__factory"
import {
init,
loadChains,
loadPrivateKey,
writeOutputFiles,
ChainInfo,
Deployment,
} from "../helpers/env"
import { ethers } from "ethers"
deployRelayProviderImplementation,
deployRelayProviderProxy,
deployRelayProviderSetup,
} from "../helpers/deployments"
import { init, loadChains, loadPrivateKey, writeOutputFiles } from "../helpers/env"
const processName = "deployRelayProvider"
init()
@ -42,64 +35,4 @@ async function run() {
writeOutputFiles(output, processName)
}
export async function deployRelayProviderImplementation(
chain: ChainInfo
): Promise<Deployment> {
console.log("deployRelayProviderImplementation " + chain.chainId)
let provider = new ethers.providers.StaticJsonRpcProvider(chain.rpc)
let signer = new ethers.Wallet(privateKey, provider)
const contractInterface = RelayProviderImplementation__factory.createInterface()
const bytecode = RelayProviderImplementation__factory.bytecode
//@ts-ignore
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 deployRelayProviderSetup(chain: ChainInfo): Promise<Deployment> {
console.log("deployRelayProviderSetup " + chain.chainId)
let provider = new ethers.providers.StaticJsonRpcProvider(chain.rpc)
let signer = new ethers.Wallet(privateKey, provider)
const contractInterface = RelayProviderSetup__factory.createInterface()
const bytecode = RelayProviderSetup__factory.bytecode
//@ts-ignore
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 deployRelayProviderProxy(
chain: ChainInfo,
relayProviderSetupAddress: string,
relayProviderImplementationAddress: string
): Promise<Deployment> {
console.log("deployRelayProviderProxy " + chain.chainId)
let provider = new ethers.providers.StaticJsonRpcProvider(chain.rpc)
let signer = new ethers.Wallet(privateKey, provider)
const contractInterface = RelayProviderProxy__factory.createInterface()
const bytecode = RelayProviderProxy__factory.bytecode
//@ts-ignore
const factory = new ethers.ContractFactory(contractInterface, bytecode, signer)
let ABI = ["function setup(address,uint16)"]
let iface = new ethers.utils.Interface(ABI)
let encodedData = iface.encodeFunctionData("setup", [
relayProviderImplementationAddress,
chain.chainId,
])
const contract = await factory.deploy(relayProviderSetupAddress, encodedData)
return await contract.deployed().then((result) => {
console.log("Successfully deployed contract at " + result.address)
return { address: result.address, chainId: chain.chainId }
})
}
run().then(() => console.log("Done!"))