diff --git a/contracts/deploy/base/051_migrate_base_operators_to_talos_kms.js b/contracts/deploy/base/051_migrate_base_operators_to_talos_kms.js new file mode 100644 index 0000000000..6a83594b35 --- /dev/null +++ b/contracts/deploy/base/051_migrate_base_operators_to_talos_kms.js @@ -0,0 +1,45 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +// Executed by base.governor 5/8 -> Base Timelock (these setters are onlyGovernor). +// Re-points the Base CrossChainRemoteStrategy operator and the OETHBaseVault +// operatorAddr to the new Talos signer, and unpauses OETHb rebases so Talos can +// rebase the vault directly (operator-gated) — replacing the PermissionedRebaseModule. +module.exports = deployOnBase( + { + deployName: "051_migrate_base_operators_to_talos", + }, + async () => { + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + addresses.base.CrossChainRemoteStrategy + ); + + const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); + const cOETHbVault = await ethers.getContractAt( + "IVault", + cOETHbVaultProxy.address + ); + + return { + name: "Migrate the Base CrossChainRemoteStrategy operator and OETHBaseVault operatorAddr to the new Talos signer, and unpause OETHb rebases.", + actions: [ + { + contract: cCrossChainRemoteStrategy, + signature: "setOperator(address)", + args: [addresses.talosRelayer], + }, + { + contract: cOETHbVault, + signature: "setOperatorAddr(address)", + args: [addresses.talosRelayer], + }, + { + contract: cOETHbVault, + signature: "unpauseRebase()", + args: [], + }, + ], + }; + } +); diff --git a/contracts/deploy/base/052_migrate_base_merkl_module_to_talos_kms.js b/contracts/deploy/base/052_migrate_base_merkl_module_to_talos_kms.js new file mode 100644 index 0000000000..6d4e327413 --- /dev/null +++ b/contracts/deploy/base/052_migrate_base_merkl_module_to_talos_kms.js @@ -0,0 +1,41 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGnosisSafe } = require("../../utils/deploy"); + +// The Base MerklPoolBoosterBribesModule is admined by the multichainStrategist +// 2/8 Safe (its DEFAULT_ADMIN_ROLE holder), so granting OPERATOR_ROLE to the new +// Talos signer is a plain Safe transaction +module.exports = deploymentWithGnosisSafe( + { + deployName: "052_migrate_base_merkl_module_to_talos", + safe: addresses.multichainStrategist, + network: "base", + forceDeploy: false, + }, + async () => { + const cMerklModule = await ethers.getContractAt( + "MerklPoolBoosterBribesModule", + addresses.base.MerklPoolBoosterBribesModule + ); + + const OPERATOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("OPERATOR_ROLE") + ); + + return { + name: "Grant the OPERATOR_ROLE of the Base MerklPoolBoosterBribesModule to the new Talos signer, and revoke it from the old relayer.", + actions: [ + { + contract: cMerklModule, + signature: "grantRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.talosRelayer], + }, + // grantRole is additive — the old relayer keeps OPERATOR_ROLE, so revoke it. + { + contract: cMerklModule, + signature: "revokeRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.base.OZRelayerAddress], + }, + ], + }; + } +); diff --git a/contracts/deploy/base/053_grant_base_claim_bribes_module1_talos_kms.js b/contracts/deploy/base/053_grant_base_claim_bribes_module1_talos_kms.js new file mode 100644 index 0000000000..66f42989ba --- /dev/null +++ b/contracts/deploy/base/053_grant_base_claim_bribes_module1_talos_kms.js @@ -0,0 +1,39 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGnosisSafe } = require("../../utils/deploy"); + +// ClaimBribesSafeModule1 is admined by the ClaimBribes 2/8 Safe (its +// DEFAULT_ADMIN_ROLE holder and immutable safeContract()), so granting +// OPERATOR_ROLE to the new Talos signer is a plain Safe transaction. +module.exports = deploymentWithGnosisSafe( + { + deployName: "053_grant_base_claim_bribes_module1_talos", + network: "base", + forceDeploy: false, + }, + async () => { + const cModule = await ethers.getContract("ClaimBribesSafeModule1"); + const safe = await cModule.safeContract(); + + const OPERATOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("OPERATOR_ROLE") + ); + + return { + safe, + name: "Grant the OPERATOR_ROLE of the Base ClaimBribesSafeModule1 to the new Talos signer, and revoke it from the old relayer.", + actions: [ + { + contract: cModule, + signature: "grantRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.talosRelayer], + }, + // grantRole is additive — the old relayer keeps OPERATOR_ROLE, so revoke it. + { + contract: cModule, + signature: "revokeRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.base.OZRelayerAddress], + }, + ], + }; + } +); diff --git a/contracts/deploy/base/054_grant_base_claim_bribes_module3_talos_kms.js b/contracts/deploy/base/054_grant_base_claim_bribes_module3_talos_kms.js new file mode 100644 index 0000000000..64dbb3f406 --- /dev/null +++ b/contracts/deploy/base/054_grant_base_claim_bribes_module3_talos_kms.js @@ -0,0 +1,39 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGnosisSafe } = require("../../utils/deploy"); + +// ClaimBribesSafeModule3 is admined by the base.strategist 1/2 Safe (its +// DEFAULT_ADMIN_ROLE holder and immutable safeContract()), so granting +// OPERATOR_ROLE to the new Talos signer is a plain Safe transaction. +module.exports = deploymentWithGnosisSafe( + { + deployName: "054_grant_base_claim_bribes_module3_talos", + network: "base", + forceDeploy: false, + }, + async () => { + const cModule = await ethers.getContract("ClaimBribesSafeModule3"); + const safe = await cModule.safeContract(); + + const OPERATOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("OPERATOR_ROLE") + ); + + return { + safe, + name: "Grant the OPERATOR_ROLE of the Base ClaimBribesSafeModule3 to the new Talos signer, and revoke it from the old relayer.", + actions: [ + { + contract: cModule, + signature: "grantRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.talosRelayer], + }, + // grantRole is additive — the old relayer keeps OPERATOR_ROLE, so revoke it. + { + contract: cModule, + signature: "revokeRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.base.OZRelayerAddress], + }, + ], + }; + } +); diff --git a/contracts/deploy/hyperevm/003_migrate_crosschain_strategy_to_talos_kms.js b/contracts/deploy/hyperevm/003_migrate_crosschain_strategy_to_talos_kms.js new file mode 100644 index 0000000000..34170e007e --- /dev/null +++ b/contracts/deploy/hyperevm/003_migrate_crosschain_strategy_to_talos_kms.js @@ -0,0 +1,30 @@ +const { deployOnHyperEVM } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +// Re-point the HyperEVM CrossChainRemoteStrategy operator to the new Talos KMS +// signer (from the old relayer 0xC79A…0517). setOperator is onlyGovernor and the +// strategy's governor is the HyperEVM Timelock, so this is executed via that +// timelock (scheduled/executed by the hyperevm 5/8 admin). deployOnHyperEVM +// writes the schedule + execute Safe Transaction Builder JSON for the 5/8 admin. +module.exports = deployOnHyperEVM( + { + deployName: "003_migrate_crosschain_strategy_to_talos", + }, + async () => { + const cCrossChainRemoteStrategy = await ethers.getContractAt( + "CrossChainRemoteStrategy", + addresses.hyperevm.CrossChainRemoteStrategy + ); + + return { + name: "Migrate the HyperEVM CrossChainRemoteStrategy operator to the new Talos signer.", + actions: [ + { + contract: cCrossChainRemoteStrategy, + signature: "setOperator(address)", + args: [addresses.talosRelayer], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/196_migrate_operators_to_talos_kms.js b/contracts/deploy/mainnet/196_migrate_operators_to_talos_kms.js new file mode 100644 index 0000000000..b96300957c --- /dev/null +++ b/contracts/deploy/mainnet/196_migrate_operators_to_talos_kms.js @@ -0,0 +1,73 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +// the governor to execute this proposal is OGN governance +module.exports = deploymentWithGovernanceProposal( + { + deployName: "196_migrate_operators_to_talos", + forceDeploy: false, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", // fill in after the proposal is submitted on-chain + }, + async () => { + // OUSD Vault (proxy "VaultProxy") + OETH Vault — IVault exposes both setters + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cOUSDVault = await ethers.getContractAt( + "IVault", + cVaultProxy.address + ); + + const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cOETHVault = await ethers.getContractAt( + "IVault", + cOETHVaultProxy.address + ); + + // Cross-chain strategies (same contract code, two Create2 proxies) + const cCrossChainMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + addresses.mainnet.CrossChainMasterStrategy + ); + const cCrossChainHyperEVMMasterStrategy = await ethers.getContractAt( + "CrossChainMasterStrategy", + addresses.mainnet.CrossChainHyperEVMMasterStrategy + ); + + return { + name: "Migrate scheduled-action operator of the OUSD/OETH vaults and the Crosschain (Base + HyperEVM) strategies to the new signer, and unpause OUSD/OETH rebases.", + actions: [ + { + contract: cOUSDVault, + signature: "setOperatorAddr(address)", + args: [addresses.talosRelayer], + }, + { + contract: cOETHVault, + signature: "setOperatorAddr(address)", + args: [addresses.talosRelayer], + }, + { + contract: cCrossChainMasterStrategy, + signature: "setOperator(address)", + args: [addresses.talosRelayer], + }, + { + contract: cCrossChainHyperEVMMasterStrategy, + signature: "setOperator(address)", + args: [addresses.talosRelayer], + }, + { + contract: cOUSDVault, + signature: "unpauseRebase()", + args: [], + }, + { + contract: cOETHVault, + signature: "unpauseRebase()", + args: [], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/197_migrate_xogn_module6_to_talos_kms.js b/contracts/deploy/mainnet/197_migrate_xogn_module6_to_talos_kms.js new file mode 100644 index 0000000000..54121e3ed2 --- /dev/null +++ b/contracts/deploy/mainnet/197_migrate_xogn_module6_to_talos_kms.js @@ -0,0 +1,37 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGnosisSafe } = require("../../utils/deploy"); + +// the governor to execute this proposal is Gnosis 5/8 Multisig +module.exports = deploymentWithGnosisSafe( + { + deployName: "197_migrate_xogn_module6_to_talos", + safe: addresses.mainnet.Guardian, + forceDeploy: false, + }, + async () => { + const cCollectXOGNRewardsModule6 = await ethers.getContract( + "CollectXOGNRewardsModule6" + ); + + const OPERATOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("OPERATOR_ROLE") + ); + + return { + name: "Grant the OPERATOR_ROLE of CollectXOGNRewardsModule6 to the new Talos signer, and revoke it from the old relayer.", + actions: [ + { + contract: cCollectXOGNRewardsModule6, + signature: "grantRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.talosRelayer], + }, + // grantRole is additive — the old relayer keeps OPERATOR_ROLE, so revoke it. + { + contract: cCollectXOGNRewardsModule6, + signature: "revokeRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.mainnet.validatorRegistrator], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/198_migrate_strategist_modules_to_talos_kms.js b/contracts/deploy/mainnet/198_migrate_strategist_modules_to_talos_kms.js new file mode 100644 index 0000000000..fa5b966f57 --- /dev/null +++ b/contracts/deploy/mainnet/198_migrate_strategist_modules_to_talos_kms.js @@ -0,0 +1,45 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGnosisSafe } = require("../../utils/deploy"); + +// the governor to execute this proposal is 2/8 Cross chain strategist +module.exports = deploymentWithGnosisSafe( + { + deployName: "198_migrate_strategist_modules_to_talos", + safe: addresses.multichainStrategist, + forceDeploy: false, + }, + async () => { + const moduleNames = [ + "ClaimStrategyRewardsSafeModule", + "AutoWithdrawalModule", + "MerklPoolBoosterBribesModule", + "CurvePoolBoosterBribesModule", + ]; + const modules = []; + for (const name of moduleNames) { + modules.push(await ethers.getContract(name)); + } + + const OPERATOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("OPERATOR_ROLE") + ); + + return { + name: "Grant the OPERATOR_ROLE of all strategist safe modules to the new Talos signer, and revoke it from the old relayer.", + // grantRole is additive — the old relayer keeps OPERATOR_ROLE, so for each + // module grant the new signer AND revoke the old relayer. + actions: modules.flatMap((cModule) => [ + { + contract: cModule, + signature: "grantRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.talosRelayer], + }, + { + contract: cModule, + signature: "revokeRole(bytes32,address)", + args: [OPERATOR_ROLE, addresses.mainnet.validatorRegistrator], + }, + ]), + }; + } +); diff --git a/contracts/deploy/sonic/030_migrate_sonic_operators_to_talos_kms.js b/contracts/deploy/sonic/030_migrate_sonic_operators_to_talos_kms.js new file mode 100644 index 0000000000..0cbc4561f7 --- /dev/null +++ b/contracts/deploy/sonic/030_migrate_sonic_operators_to_talos_kms.js @@ -0,0 +1,49 @@ +const addresses = require("../../utils/addresses"); +const { deployOnSonic } = require("../../utils/deploy-l2"); + +// Migrate the Sonic operator/registrator roles from the old relayer EOA to the +// new Talos KMS signer. All three actions are governor-gated and executed via +// the Sonic Timelock (scheduled by the Sonic 5/8 admin). The vault rebase is +// operator-gated and currently paused, so we also unpause it once the operator +// is set. +module.exports = deployOnSonic( + { + deployName: "030_migrate_sonic_operators_to_talos", + }, + async ({ ethers }) => { + const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); + const cOSonicVault = await ethers.getContractAt( + "IVault", + cOSonicVaultProxy.address + ); + + const cSonicStakingStrategyProxy = await ethers.getContract( + "SonicStakingStrategyProxy" + ); + const cSonicStakingStrategy = await ethers.getContractAt( + "SonicStakingStrategy", + cSonicStakingStrategyProxy.address + ); + + return { + name: "Migrate Sonic operators to the Talos KMS signer", + actions: [ + { + contract: cOSonicVault, + signature: "setOperatorAddr(address)", + args: [addresses.talosRelayer], + }, + { + contract: cOSonicVault, + signature: "unpauseRebase()", + args: [], + }, + { + contract: cSonicStakingStrategy, + signature: "setRegistrator(address)", + args: [addresses.talosRelayer], + }, + ], + }; + } +); diff --git a/contracts/test/_fixture-hyperevm.js b/contracts/test/_fixture-hyperevm.js index 4717489092..88ae881501 100644 --- a/contracts/test/_fixture-hyperevm.js +++ b/contracts/test/_fixture-hyperevm.js @@ -105,7 +105,11 @@ const crossChainHyperEVMFixture = deployments.createFixture(async () => { addresses.CCTPTokenMessengerV2 ); - const relayer = await impersonateAndFund(addresses.hyperevm.OZRelayerAddress); + // The cross-chain operator is re-pointed during the Talos signer migration + // (deploy 003), so read it from the strategy instead of hardcoding a relayer. + const relayer = await impersonateAndFund( + await fixture.crossChainRemoteStrategy.operator() + ); return { ...fixture, diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 0943564e81..f41462c63c 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1632,8 +1632,10 @@ async function crossChainFixture() { addresses.CCTPTokenMessengerV2 ); + // The cross-chain operator is repointed during the Talos signer migration + // (deploy 196), so read it from the strategy instead of hardcoding a relayer. fixture.relayer = await impersonateAndFund( - addresses.mainnet.validatorRegistrator + await cCrossChainMasterStrategy.operator() ); await setERC20TokenBalance( diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index a05685af3a..d24ba89022 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -8,7 +8,7 @@ addresses.createX = "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"; addresses.multichainStrategist = "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971"; addresses.multichainBuybackOperator = "0xBB077E716A5f1F1B63ed5244eBFf5214E50fec8c"; -addresses.talosRelayer = "0x0aBCDa6Fa7d500cf69B0eA5de9a607Cd9941221C"; +addresses.talosRelayer = "0x739212d5bAfE6AAC8Be49a60B7d003bD41DBf38b"; // new Talos signer addresses.votemarket = "0x8c2c5A295450DDFf4CB360cA73FCCC12243D14D9"; // CCTP contracts (uses same addresses on all chains) diff --git a/contracts/utils/deploy.js b/contracts/utils/deploy.js index d03b7bf1ff..4056466bef 100644 --- a/contracts/utils/deploy.js +++ b/contracts/utils/deploy.js @@ -48,6 +48,8 @@ const { getStorageAt, } = require("@nomicfoundation/hardhat-network-helpers"); const { keccak256, defaultAbiCoder } = require("ethers/lib/utils.js"); +const fs = require("fs"); +const path = require("path"); // Wait for 3 blocks confirmation on Mainnet. let NUM_CONFIRMATIONS = isMainnet ? 3 : 0; @@ -993,6 +995,47 @@ async function buildGnosisSafeJson( }); } +// Inlined to avoid a circular import: deploy-l2.js already requires deploy.js, +// so its getNetworkName() can't be imported here. +function safeOpsNetworkName() { + if (isForkTest) return "hardhat"; + if (isFork) return "localhost"; + return process.env.NETWORK_NAME || "mainnet"; +} + +function safeValueToString(value) { + if (BigNumber.isBigNumber(value)) return value.toString(); + if (Array.isArray(value)) return JSON.stringify(value.map(safeValueToString)); + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } + // address / bytes32 / bytes / string pass through unchanged + return String(value); +} + +// (contract, signature, args) -> { paramName: stringValue } for the Gnosis Safe +// Transaction Builder. Param names come from the ABI fragment. Tuple/struct +// params are unsupported (not used by the Safe migrations) and throw loudly. +function buildContractInputsValues(contract, signature, args) { + const inputs = contract.interface.getFunction(signature).inputs; + if (inputs.length !== args.length) { + throw new Error( + `${signature}: expected ${inputs.length} args, got ${args.length}` + ); + } + const out = {}; + inputs.forEach((input, i) => { + if (input.baseType === "tuple" || input.components) { + throw new Error( + `${signature}: tuple/struct param "${input.name}" is not supported` + ); + } + const name = input.name && input.name.length ? input.name : `arg${i}`; + out[name] = safeValueToString(args[i]); + }); + return out; +} + async function getProposalExecutionValue(governor, proposalId) { const actions = await governor.getActions(proposalId); const rawValues = @@ -1311,6 +1354,145 @@ function deploymentWithGuardianGovernor(opts, fn) { return main; } +/** + * Shortcut to create a deployment executed by a plain Gnosis Safe (NOT GovernorSix + * governance, NOT the Guardian-via-timelock path). The deploy fn returns a single + * list of actions ({ contract, signature, args, value }). The helper always builds + * the Gnosis Safe Transaction Builder JSON and writes it to + * deployments//operations/.json (mainnet -> committed + * artifact, fork -> gitignored). On fork it additionally executes the actions by + * impersonating the Safe, so fork tests pass and downstream deploys see the state. + * + * @param {Object} opts deployment options. `safe` is the executing Safe address. + * @param {Function} fn async (tools) => { name?, safe?, actions: [...] } + * @returns {Function} main object used by hardhat + */ +function deploymentWithGnosisSafe(opts, fn) { + const { deployName, dependencies, forceDeploy, onlyOnFork, forceSkip } = opts; + const optsSafe = opts.safe; + // Target network the Safe lives on; gates the real-deploy run + skip. Defaults + // to mainnet so existing mainnet deploys are unaffected. + const targetNetwork = opts.network || "mainnet"; + + const runDeployment = async (hre) => { + // getAssetAddresses is mainnet-centric (resolves mock assets); on L2s it can + // throw. Safe-batch deploys don't need it, so tolerate failure. + let assetAddresses = {}; + try { + assetAddresses = await getAssetAddresses(hre.deployments); + } catch (e) { + log( + `getAssetAddresses unavailable (${e.message}); continuing without it.` + ); + } + const proposal = await fn({ + assetAddresses, + deployWithConfirmation, + ethers, + getTxOpts, + withConfirmation, + }); + + if (!proposal?.actions?.length) { + log("No Safe proposal actions."); + return; + } + + const safeAddress = proposal.safe || optsSafe; + if (!safeAddress) { + throw new Error( + `deploymentWithGnosisSafe (${deployName}): no Safe address. ` + + `Set opts.safe or return { safe } from the deploy fn.` + ); + } + + const { actions } = proposal; + + // Build + write the Safe Transaction Builder JSON (every environment). + const safeJson = await buildGnosisSafeJson( + safeAddress, + actions.map((a) => a.contract.address), + actions.map((a) => constructContractMethod(a.contract, a.signature)), + actions.map((a) => + buildContractInputsValues(a.contract, a.signature, a.args) + ), + actions.map((a) => BigNumber.from(a.value ?? 0).toString()) + ); + const filePath = path.resolve( + __dirname, + `./../deployments/${safeOpsNetworkName()}/operations/${deployName}.json` + ); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(safeJson, null, 2)); + console.log(`Safe batch JSON written to ${filePath}`); + + if (!isFork) { + // Real deploy: JSON only. The operator imports it into the Safe + // Transaction Builder for the Safe to execute. + console.log( + `Import ${deployName}.json into the Gnosis Safe (${safeAddress}) Transaction Builder to execute.` + ); + return; + } + + // Fork: execute the batch by impersonating the Safe. + const safeSigner = await impersonateAndFund(safeAddress); + console.log(`Impersonating Safe ${safeAddress} to execute actions on fork`); + for (const action of actions) { + const { contract, signature, args, value } = action; + const txOpts = { + ...(await getTxOpts()), + ...(value ? { value } : {}), + }; + log(`Sending Safe action ${signature} to ${contract.address}`); + await withConfirmation( + contract.connect(safeSigner)[signature](...args, txOpts) + ); + console.log(`... ${signature} completed`); + } + }; + + const main = async (hre) => { + console.log(`Running ${deployName} deployment...`); + if (!hre) { + hre = require("hardhat"); + } + await runDeployment(hre); + console.log(`${deployName} deploy done.`); + return true; + }; + + main.id = deployName; + main.dependencies = dependencies; + // L2 fixtures filter deploys by network tag (deployments.fixture(["base"])); + // mainnet's fixture runs all deploys untagged, so only tag for non-mainnet. + if (targetNetwork !== "mainnet") { + main.tags = [targetNetwork]; + } + if (forceSkip) { + main.skip = () => true; + } else if (forceDeploy) { + main.skip = () => false; + } else { + main.skip = async () => { + if (isFork) { + const networkName = isForkTest ? "hardhat" : "localhost"; + const migrations = require(`./../deployments/${networkName}/.migrations.json`); + return Boolean(migrations[deployName]); + } else { + const onTarget = + targetNetwork === "base" + ? isBase + : targetNetwork === "sonic" + ? isSonic + : isMainnet; + return onlyOnFork ? true : isSmokeTest || !onTarget; + } + }; + } + return main; +} + function encodeSaltForCreateX(deployer, crosschainProtectionFlag, salt) { // Generate encoded salt (deployer address || crosschainProtectionFlag || bytes11(keccak256(rewardToken, gauge))) @@ -1471,6 +1653,7 @@ module.exports = { executeProposalOnFork, deploymentWithGovernanceProposal, deploymentWithGuardianGovernor, + deploymentWithGnosisSafe, constructContractMethod, buildGnosisSafeJson,