diff --git a/contract_manager/scripts/common.ts b/contract_manager/scripts/common.ts index 274361a295..fce41ea6ce 100644 --- a/contract_manager/scripts/common.ts +++ b/contract_manager/scripts/common.ts @@ -6,7 +6,11 @@ import { Contract } from "web3-eth-contract"; import { InferredOptionType } from "yargs"; import { PrivateKey, getDefaultDeploymentConfig } from "../src/core/base"; import { EvmChain } from "../src/core/chains"; -import { EvmEntropyContract, EvmWormholeContract } from "../src/core/contracts"; +import { + EvmEntropyContract, + EvmExecutorContract, + EvmWormholeContract, +} from "../src/core/contracts"; export interface BaseDeployConfig { gasMultiplier: number; @@ -237,6 +241,27 @@ export function findWormholeContract( } } +/** + * Finds the executor contract for a given EVM chain. + * @param {EvmChain} chain The EVM chain to find the executor contract for. + * @returns If found, the executor contract for the given EVM chain. Else, undefined + */ +export function findExecutorContract( + chain: EvmChain, +): EvmExecutorContract | undefined { + for (const contract of Object.values(DefaultStore.executor_contracts)) { + if ( + contract instanceof EvmExecutorContract && + contract.chain.getId() === chain.getId() + ) { + console.log( + `Found executor contract for ${chain.getId()} at ${contract.address}`, + ); + return contract; + } + } +} + export interface DeployWormholeReceiverContractsConfig extends BaseDeployConfig { saveContract: boolean; diff --git a/contract_manager/scripts/deploy_evm_entropy_contracts.ts b/contract_manager/scripts/deploy_evm_entropy_contracts.ts index 547c87427f..fdcea61b9e 100644 --- a/contract_manager/scripts/deploy_evm_entropy_contracts.ts +++ b/contract_manager/scripts/deploy_evm_entropy_contracts.ts @@ -9,7 +9,6 @@ import { } from "../src/core/contracts/evm"; import { DeploymentType, - getDefaultDeploymentConfig, toDeploymentType, toPrivateKey, } from "../src/core/base"; @@ -22,6 +21,7 @@ import { topupAccountsIfNecessary, DefaultAddresses, } from "./common"; +import { getOrDeployExecutorContract } from "./deploy_evm_executor"; interface DeploymentConfig extends BaseDeployConfig { type: DeploymentType; @@ -44,44 +44,6 @@ const parser = yargs(hideBin(process.argv)) }, }); -async function deployExecutorContracts( - chain: EvmChain, - config: DeploymentConfig, - wormholeAddr: string, -): Promise { - const executorImplAddr = await deployIfNotCached( - CACHE_FILE, - chain, - config, - "ExecutorUpgradable", - [], - ); - - // Craft the init data for the proxy contract - const { governanceDataSource } = getDefaultDeploymentConfig(config.type); - - const executorImplContract = getWeb3Contract( - config.jsonOutputDir, - "ExecutorUpgradable", - executorImplAddr, - ); - - const executorInitData = executorImplContract.methods - .initialize( - wormholeAddr, - 0, // lastExecutedSequence, - chain.getWormholeChainId(), - governanceDataSource.emitterChain, - `0x${governanceDataSource.emitterAddress}`, - ) - .encodeABI(); - - return await deployIfNotCached(CACHE_FILE, chain, config, "ERC1967Proxy", [ - executorImplAddr, - executorInitData, - ]); -} - async function deployEntropyContracts( chain: EvmChain, config: DeploymentConfig, @@ -166,7 +128,7 @@ async function main() { console.log(`Deploying entropy contracts on ${chain.getId()}...`); - const executorAddr = await deployExecutorContracts( + const executorContract = await getOrDeployExecutorContract( chain, deploymentConfig, wormholeContract.address, @@ -174,7 +136,7 @@ async function main() { const entropyAddr = await deployEntropyContracts( chain, deploymentConfig, - executorAddr, + executorContract.address, ); if (deploymentConfig.saveContract) { diff --git a/contract_manager/scripts/deploy_evm_executor.ts b/contract_manager/scripts/deploy_evm_executor.ts new file mode 100644 index 0000000000..703b06003f --- /dev/null +++ b/contract_manager/scripts/deploy_evm_executor.ts @@ -0,0 +1,147 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { EvmChain } from "../src/core/chains"; +import { + BaseDeployConfig, + COMMON_DEPLOY_OPTIONS, + deployIfNotCached, + findExecutorContract, + getOrDeployWormholeContract, + getWeb3Contract, +} from "./common"; +import { + DeploymentType, + getDefaultDeploymentConfig, + toDeploymentType, + toPrivateKey, +} from "../src/core/base"; +import { DefaultStore } from "../src/node/utils/store"; +import { EvmExecutorContract } from "../src/core/contracts/evm"; + +const CACHE_FILE = ".cache-deploy-evm-executor"; + +const parser = yargs(hideBin(process.argv)) + .scriptName("deploy_evm_executor.ts") + .usage( + "Usage: $0 --std-output-dir --private-key --chain ", + ) + .options({ + ...COMMON_DEPLOY_OPTIONS, + chain: { + type: "string", + demandOption: true, + desc: "Chain to upload the contract on. Can be one of the evm chains available in the store", + }, + }); + +interface DeploymentConfig extends BaseDeployConfig { + type: DeploymentType; + saveContract: boolean; +} + +export async function getOrDeployExecutorContract( + chain: EvmChain, + config: DeploymentConfig, + wormholeAddr: string, +): Promise { + return ( + findExecutorContract(chain) ?? + (await deployExecutorContracts(chain, config, wormholeAddr)) + ); +} + +/** + * Deploys the executor contracts for a given EVM chain. + * @param {EvmChain} chain The EVM chain to deploy the executor contracts for. + * @param {DeploymentConfig} config The deployment configuration. + * @param {string} wormholeAddr The address of the wormhole contract. + * @returns {Promise} The address of the deployed executor contract. + */ +export async function deployExecutorContracts( + chain: EvmChain, + config: DeploymentConfig, + wormholeAddr: string, +): Promise { + const executorImplAddr = await deployIfNotCached( + CACHE_FILE, + chain, + config, + "ExecutorUpgradable", + [], + ); + + // Craft the init data for the proxy contract + const { governanceDataSource } = getDefaultDeploymentConfig(config.type); + + const executorImplContract = getWeb3Contract( + config.jsonOutputDir, + "ExecutorUpgradable", + executorImplAddr, + ); + + const executorInitData = executorImplContract.methods + .initialize( + wormholeAddr, + 0, // lastExecutedSequence, + chain.getWormholeChainId(), + governanceDataSource.emitterChain, + `0x${governanceDataSource.emitterAddress}`, + ) + .encodeABI(); + + const executorAddr = await deployIfNotCached( + CACHE_FILE, + chain, + config, + "ERC1967Proxy", + [executorImplAddr, executorInitData], + ); + + return new EvmExecutorContract(chain, executorAddr); +} + +export async function main() { + const argv = await parser.argv; + + const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain); + + const deploymentConfig: DeploymentConfig = { + type: toDeploymentType(argv.deploymentType), + gasMultiplier: argv.gasMultiplier, + gasPriceMultiplier: argv.gasPriceMultiplier, + privateKey: toPrivateKey(argv.privateKey), + jsonOutputDir: argv.stdOutputDir, + saveContract: argv.saveContract, + }; + + const wormholeContract = await getOrDeployWormholeContract( + chain, + deploymentConfig, + CACHE_FILE, + ); + + console.log( + `Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`, + ); + + console.log(`Deploying executor contracts on ${chain.getId()}...`); + + const executorContract = await getOrDeployExecutorContract( + chain, + deploymentConfig, + wormholeContract.address, + ); + + if (deploymentConfig.saveContract) { + console.log("Saving the contract in the store..."); + DefaultStore.executor_contracts[executorContract.getId()] = + executorContract; + DefaultStore.saveAllContracts(); + } + + console.log( + `✅ Executor contract on ${chain.getId()} at ${executorContract.address}\n\n`, + ); +} + +main(); diff --git a/contract_manager/src/core/contracts/evm.ts b/contract_manager/src/core/contracts/evm.ts index e59ab931f1..599934299b 100644 --- a/contract_manager/src/core/contracts/evm.ts +++ b/contract_manager/src/core/contracts/evm.ts @@ -412,16 +412,32 @@ export class EvmEntropyContract extends Storable { } } -export class EvmExecutorContract { +export class EvmExecutorContract extends Storable { + static type = "EvmExecutorContract"; + constructor( public chain: EvmChain, public address: string, - ) {} + ) { + super(); + } getId(): string { return `${this.chain.getId()}_${this.address}`; } + getType(): string { + return EvmExecutorContract.type; + } + + toJson() { + return { + chain: this.chain.getId(), + address: this.address, + type: EvmExecutorContract.type, + }; + } + async getWormholeContract(): Promise { const web3 = this.chain.getWeb3(); //Unfortunately, there is no public method to get the wormhole address @@ -431,6 +447,17 @@ export class EvmExecutorContract { return new EvmWormholeContract(this.chain, address); } + static fromJson( + chain: Chain, + parsed: { type: string; address: string }, + ): EvmExecutorContract { + if (parsed.type !== EvmExecutorContract.type) + throw new Error("Invalid type"); + if (!(chain instanceof EvmChain)) + throw new Error(`Wrong chain type ${chain}`); + return new EvmExecutorContract(chain, parsed.address); + } + getContract() { const web3 = this.chain.getWeb3(); return new web3.eth.Contract(EXECUTOR_ABI, this.address); diff --git a/contract_manager/src/node/utils/store.ts b/contract_manager/src/node/utils/store.ts index 008d697e88..3529791e03 100644 --- a/contract_manager/src/node/utils/store.ts +++ b/contract_manager/src/node/utils/store.ts @@ -29,6 +29,7 @@ import { IotaWormholeContract, IotaPriceFeedContract, EvmPulseContract, + EvmExecutorContract, } from "../../core/contracts"; import { Token } from "../../core/token"; import { PriceFeedContract, Storable } from "../../core/base"; @@ -46,6 +47,7 @@ import { export class Store { public chains: Record = { global: new GlobalChain() }; public contracts: Record = {}; + public executor_contracts: Record = {}; public entropy_contracts: Record = {}; public pulse_contracts: Record = {}; public wormhole_contracts: Record = {}; @@ -118,6 +120,7 @@ export class Store { const contracts: Storable[] = Object.values(this.contracts); contracts.push(...Object.values(this.entropy_contracts)); contracts.push(...Object.values(this.wormhole_contracts)); + contracts.push(...Object.values(this.executor_contracts)); for (const contract of contracts) { if (!contractsByType[contract.getType()]) { contractsByType[contract.getType()] = []; @@ -167,6 +170,7 @@ export class Store { [AptosWormholeContract.type]: AptosWormholeContract, [EvmEntropyContract.type]: EvmEntropyContract, [EvmWormholeContract.type]: EvmWormholeContract, + [EvmExecutorContract.type]: EvmExecutorContract, [FuelPriceFeedContract.type]: FuelPriceFeedContract, [FuelWormholeContract.type]: FuelWormholeContract, [StarknetPriceFeedContract.type]: StarknetPriceFeedContract, @@ -192,7 +196,8 @@ export class Store { if ( this.contracts[chainContract.getId()] || this.entropy_contracts[chainContract.getId()] || - this.wormhole_contracts[chainContract.getId()] + this.wormhole_contracts[chainContract.getId()] || + this.executor_contracts[chainContract.getId()] ) throw new Error( `Multiple contracts with id ${chainContract.getId()} found`, @@ -201,6 +206,8 @@ export class Store { this.entropy_contracts[chainContract.getId()] = chainContract; } else if (chainContract instanceof WormholeContract) { this.wormhole_contracts[chainContract.getId()] = chainContract; + } else if (chainContract instanceof EvmExecutorContract) { + this.executor_contracts[chainContract.getId()] = chainContract; } else { this.contracts[chainContract.getId()] = chainContract; } diff --git a/contract_manager/store/chains/EvmChains.json b/contract_manager/store/chains/EvmChains.json index 9f7ce46b3f..6fca380397 100644 --- a/contract_manager/store/chains/EvmChains.json +++ b/contract_manager/store/chains/EvmChains.json @@ -401,7 +401,7 @@ { "id": "sepolia", "mainnet": false, - "rpcUrl": "https://eth-sepolia.blastapi.io/$ENV_BLAST_API_KEY", + "rpcUrl": "https://eth-sepolia.public.blastapi.io", "networkId": 11155111, "type": "EvmChain" }, @@ -1265,5 +1265,12 @@ "rpcUrl": "https://k8s.testnet.json-rpc.injective.network/", "networkId": 1439, "type": "EvmChain" + }, + { + "id": "ethereal_testnet", + "mainnet": false, + "rpcUrl": "https://rpc-ethereal-testnet.t.conduit.xyz", + "networkId": 657468, + "type": "EvmChain" } ] diff --git a/contract_manager/store/contracts/EvmEntropyContracts.json b/contract_manager/store/contracts/EvmEntropyContracts.json index 7cd26b45b4..84e47a6fe8 100644 --- a/contract_manager/store/contracts/EvmEntropyContracts.json +++ b/contract_manager/store/contracts/EvmEntropyContracts.json @@ -189,4 +189,4 @@ "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", "type": "EvmEntropyContract" } -] +] \ No newline at end of file diff --git a/contract_manager/store/contracts/EvmExecutorContracts.json b/contract_manager/store/contracts/EvmExecutorContracts.json new file mode 100644 index 0000000000..57023ef3d8 --- /dev/null +++ b/contract_manager/store/contracts/EvmExecutorContracts.json @@ -0,0 +1,202 @@ +[ + { + "chain": "arbitrum_sepolia", + "address": "0x9DF02366A266D79DA5E978aDBEe8B8502117ee1a", + "type": "EvmExecutorContract" + }, + { + "chain": "blast_s2_testnet", + "address": "0x5f3c61944CEb01B3eAef861251Fb1E0f14b848fb", + "type": "EvmExecutorContract" + }, + { + "chain": "base_sepolia", + "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", + "type": "EvmExecutorContract" + }, + { + "chain": "optimism_sepolia", + "address": "0x549Ebba8036Ab746611B4fFA1423eb0A4Df61440", + "type": "EvmExecutorContract" + }, + { + "chain": "zetachain_testnet", + "address": "0xfA25E653b44586dBbe27eE9d252192F0e4956683", + "type": "EvmExecutorContract" + }, + { + "chain": "etherlink_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "sei_evm_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "kaia_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "b3_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "apechain_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "soneium_minato_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "sanko_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "abstract_testnet", + "address": "0x0d8B7FE8598e2BcEcAf1E60f51B4b8B8E4453BA5", + "type": "EvmExecutorContract" + }, + { + "chain": "sonic_blaze_testnet", + "address": "0x8D254a21b3C86D32F7179855531CE99164721933", + "type": "EvmExecutorContract" + }, + { + "chain": "unichain_sepolia", + "address": "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", + "type": "EvmExecutorContract" + }, + { + "chain": "tabi_testnet", + "address": "0x8D254a21b3C86D32F7179855531CE99164721933", + "type": "EvmExecutorContract" + }, + { + "chain": "monad_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "story_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "berachain_bepolia", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "hyperevm_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "arbitrum", + "address": "0x24654078A8E043e8985D962a5100CDfA2026f92C", + "type": "EvmExecutorContract" + }, + { + "chain": "optimism", + "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb", + "type": "EvmExecutorContract" + }, + { + "chain": "blast", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "zetachain", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "base", + "address": "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67", + "type": "EvmExecutorContract" + }, + { + "chain": "sei_evm_mainnet", + "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E", + "type": "EvmExecutorContract" + }, + { + "chain": "etherlink", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "kaia", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "b3_mainnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "apechain_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "sanko", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "unichain", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "abstract", + "address": "0x6650bBd680A4cAdcB30AFfa0Ec78ca0811Db0B85", + "type": "EvmExecutorContract" + }, + { + "chain": "fantom_sonic_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "berachain_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "hyperevm", + "address": "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167", + "type": "EvmExecutorContract" + }, + { + "chain": "story", + "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb", + "type": "EvmExecutorContract" + }, + { + "chain": "soneium", + "address": "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c", + "type": "EvmExecutorContract" + }, + { + "chain": "sepolia", + "address": "0xB0D4a9640aDdd415551B6A4fe75403c9f73A7C49", + "type": "EvmExecutorContract" + }, + { + "chain": "ethereal_testnet", + "address": "0xD458261E832415CFd3BAE5E416FdF3230ce6F134", + "type": "EvmExecutorContract" + } +] \ No newline at end of file diff --git a/contract_manager/store/contracts/EvmWormholeContracts.json b/contract_manager/store/contracts/EvmWormholeContracts.json index 41384b2247..ce1ced7a16 100644 --- a/contract_manager/store/contracts/EvmWormholeContracts.json +++ b/contract_manager/store/contracts/EvmWormholeContracts.json @@ -838,5 +838,10 @@ "chain": "injective_evm_testnet", "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", "type": "EvmWormholeContract" + }, + { + "chain": "ethereal_testnet", + "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E", + "type": "EvmWormholeContract" } ] \ No newline at end of file diff --git a/governance/xc_admin/packages/xc_admin_common/src/chains.ts b/governance/xc_admin/packages/xc_admin_common/src/chains.ts index 388cd1879b..d2be72f363 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/chains.ts +++ b/governance/xc_admin/packages/xc_admin_common/src/chains.ts @@ -246,6 +246,7 @@ export const RECEIVER_CHAINS = { mezo_testnet: 50124, hemi_testnet: 50125, injective_evm_testnet: 50126, + ethereal_testnet: 50127, }; // If there is any overlapping value the receiver chain will replace the wormhole diff --git a/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol b/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol new file mode 100644 index 0000000000..62bb1b7410 --- /dev/null +++ b/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {PythLazer} from "../src/PythLazer.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + +contract PythLazerChangeOwnership is Script { + address public constant LAZER_PROXY_ADDRESS = + address(0xACeA761c27A909d4D3895128EBe6370FDE2dF481); + + uint256 public OLD_OWNER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address public OLD_OWNER = vm.addr(OLD_OWNER_PRIVATE_KEY); + // EVM Executor Contract + address public NEW_OWNER = vm.envAddress("NEW_OWNER"); + + function run() public { + console.log("Old owner: %s", OLD_OWNER); + console.log("New owner: %s", NEW_OWNER); + console.log("Lazer proxy address: %s", LAZER_PROXY_ADDRESS); + console.log("Lazer owner: %s", PythLazer(LAZER_PROXY_ADDRESS).owner()); + console.log("Moving ownership from %s to %s", OLD_OWNER, NEW_OWNER); + + PythLazer lazer = PythLazer(LAZER_PROXY_ADDRESS); + vm.startBroadcast(OLD_OWNER_PRIVATE_KEY); + require(lazer.owner() == OLD_OWNER, "Old owner mismatch"); + lazer.transferOwnership(NEW_OWNER); + console.log("Ownership transferred"); + console.log( + "New Lazer owner: %s", + PythLazer(LAZER_PROXY_ADDRESS).owner() + ); + vm.stopBroadcast(); + } +}