core relayer contracts upgrade via governance
This commit is contained in:
parent
b50fd28283
commit
cd26ace599
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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))
|
|
@ -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)
|
||||
|
|
|
@ -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!"))
|
||||
|
|
Loading…
Reference in New Issue