diff --git a/brownie/addresses.py b/brownie/addresses.py index 4a6ae4e3c6..4426c54831 100644 --- a/brownie/addresses.py +++ b/brownie/addresses.py @@ -6,6 +6,7 @@ # Governance STRATEGIST = '0xF14BBdf064E3F67f51cd9BD646aE3716aD938FDC' +MULTICHAIN_STRATEGIST = '0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971' GOVERNOR = '0x72426ba137dec62657306b12b1e869d43fec6ec7' GOV_MULTISIG = '0xbe2AB3d3d8F6a32b96414ebbd865dBD276d3d899' ORIGINTEAM = '0x449e0b5564e0d141b3bc3829e74ffa0ea8c08ad5' @@ -149,4 +150,8 @@ CCIP_ROUTER = "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D" -ADDR_ZERO = '0x0000000000000000000000000000000000000000' \ No newline at end of file +ADDR_ZERO = '0x0000000000000000000000000000000000000000' + +EQUALIZER_SPECTRA_OETHB_BRIBE_CONTRACT = "0xaAbF246f6f4Fa87717690dE00896a49c18A944b9" + +EQUALIZER_WETH_OETHB_BRIBE_CONTRACT = "0x4467B367c7EeF806E11756bFCD76D3801DFb942C" \ No newline at end of file diff --git a/brownie/runlogs/2025_01_strategist.py b/brownie/runlogs/2025_01_strategist.py index 26046d390f..1bcabccbc1 100644 --- a/brownie/runlogs/2025_01_strategist.py +++ b/brownie/runlogs/2025_01_strategist.py @@ -83,3 +83,68 @@ def main(): print("-----") +# ------------------------------------- +# Jan 29, 2025 - Equalizer Bribes +# ------------------------------------- + +from eth_abi.packed import encode_packed +from aerodrome_harvest import * +def main(): + with TemporaryForkForReallocations() as txs: + # Swap WETH to superOETHb + amount = 0.02933972 * 10**18 + txs.append(weth.approve(AERODROME_SWAP_ROUTER_BASE, amount, {'from': MULTICHAIN_STRATEGIST})) + + oethb_path = encode_packed( + ['address', 'int24', 'address'], + [ + WETH_BASE, + 1, # WETH > OETHb tickSpacing + OETHB, + ] + ).hex() + + txs.append( + aero_router.exactInput( + swap_params_multiple( + amount, + oethb_path, + recipient=MULTICHAIN_STRATEGIST, + to_token=OETHB, + to_token_label="WETH" + ), + {'from': MULTICHAIN_STRATEGIST} + ) + ) + + # Bribe spectra/oethb pool + spectra_pool_bribe_amount = 0.01903247923 * 10**18 + txs.append( + oethb.approve(EQUALIZER_SPECTRA_OETHB_BRIBE_CONTRACT, spectra_pool_bribe_amount, {'from': MULTICHAIN_STRATEGIST}) + ) + + spectra_oethb_bribe_contract = load_contract("aero_bribes", EQUALIZER_SPECTRA_OETHB_BRIBE_CONTRACT) + txs.append( + spectra_oethb_bribe_contract.notifyRewardAmount( + OETHB, + spectra_pool_bribe_amount, + {'from': MULTICHAIN_STRATEGIST} + ) + ) + + # Bribe weth/oethb pool + weth_pool_bribe_amount = 0.004425216108 * 10**18 + txs.append( + oethb.approve(EQUALIZER_WETH_OETHB_BRIBE_CONTRACT, weth_pool_bribe_amount, {'from': MULTICHAIN_STRATEGIST}) + ) + + weth_oethb_bribe_contract = load_contract("aero_bribes", EQUALIZER_WETH_OETHB_BRIBE_CONTRACT) + txs.append( + weth_oethb_bribe_contract.notifyRewardAmount( + OETHB, + weth_pool_bribe_amount, + {'from': MULTICHAIN_STRATEGIST} + ) + ) + + print(to_gnosis_json(txs, MULTICHAIN_STRATEGIST, "1")) diff --git a/brownie/world.py b/brownie/world.py index e7c3b65255..02f38a5bcd 100644 --- a/brownie/world.py +++ b/brownie/world.py @@ -1,6 +1,6 @@ from world_abstract import * -std = {'from': STRATEGIST} +std = {'from': MULTICHAIN_STRATEGIST} # ousd = Contract.from_explorer(OUSD, as_proxy_for=OUSD_IMPL) # usdt = Contract.from_explorer(USDT) diff --git a/contracts/contracts/interfaces/ITimelockController.sol b/contracts/contracts/interfaces/ITimelockController.sol index 349c50dfbd..37e5317e23 100644 --- a/contracts/contracts/interfaces/ITimelockController.sol +++ b/contracts/contracts/interfaces/ITimelockController.sol @@ -47,4 +47,6 @@ interface ITimelockController { function getMinDelay() external view returns (uint256); function updateDelay(uint256 newDelay) external; + + function CANCELLER_ROLE() external view returns (bytes32); } diff --git a/contracts/deploy/mainnet/084_deploy_woeth_on_arb.js b/contracts/deploy/arbitrumOne/001_deploy_woeth_on_arb.js similarity index 97% rename from contracts/deploy/mainnet/084_deploy_woeth_on_arb.js rename to contracts/deploy/arbitrumOne/001_deploy_woeth_on_arb.js index eacbd44e3c..407c238051 100644 --- a/contracts/deploy/mainnet/084_deploy_woeth_on_arb.js +++ b/contracts/deploy/arbitrumOne/001_deploy_woeth_on_arb.js @@ -7,7 +7,7 @@ const { getTxOpts } = require("../../utils/tx"); module.exports = deployOnArb( { - deployName: "084_deploy_woeth_on_arb", + deployName: "001_deploy_woeth_on_arb", }, async ({ ethers }) => { const { deployerAddr } = await getNamedAccounts(); diff --git a/contracts/deploy/mainnet/088_upgrade_woeth_on_arb.js b/contracts/deploy/arbitrumOne/002_upgrade_woeth_on_arb.js similarity index 95% rename from contracts/deploy/mainnet/088_upgrade_woeth_on_arb.js rename to contracts/deploy/arbitrumOne/002_upgrade_woeth_on_arb.js index b762e7f643..c280de2ab6 100644 --- a/contracts/deploy/mainnet/088_upgrade_woeth_on_arb.js +++ b/contracts/deploy/arbitrumOne/002_upgrade_woeth_on_arb.js @@ -5,7 +5,7 @@ const { impersonateAndFund } = require("../../utils/signers"); module.exports = deployOnArb( { - deployName: "088_upgrade_woeth_on_arb", + deployName: "002_upgrade_woeth_on_arb", }, async ({ ethers }) => { const cWOETHProxy = await ethers.getContract("BridgedWOETHProxy"); diff --git a/contracts/deploy/base/021_upgrade_oeth.js b/contracts/deploy/base/022_upgrade_oeth.js similarity index 91% rename from contracts/deploy/base/021_upgrade_oeth.js rename to contracts/deploy/base/022_upgrade_oeth.js index 5da1c0573e..4d57fcffbe 100644 --- a/contracts/deploy/base/021_upgrade_oeth.js +++ b/contracts/deploy/base/022_upgrade_oeth.js @@ -3,7 +3,8 @@ const { deployWithConfirmation } = require("../../utils/deploy"); module.exports = deployOnBaseWithGuardian( { - deployName: "021_upgrade_oeth", + deployName: "022_upgrade_oeth", + // forceSkip: true, }, async ({ ethers }) => { const dOETHb = await deployWithConfirmation( diff --git a/contracts/deploy/base/023_update_weth_share.js b/contracts/deploy/base/023_update_weth_share.js new file mode 100644 index 0000000000..792cc4a4f1 --- /dev/null +++ b/contracts/deploy/base/023_update_weth_share.js @@ -0,0 +1,28 @@ +const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2"); +const { utils } = require("ethers"); + +module.exports = deployOnBaseWithGuardian( + { + deployName: "023_update_weth_share", + }, + async ({ ethers }) => { + const cAMOStrategyProxy = await ethers.getContract( + "AerodromeAMOStrategyProxy" + ); + const cAMOStrategy = await ethers.getContractAt( + "AerodromeAMOStrategy", + cAMOStrategyProxy.address + ); + + return { + actions: [ + { + // 1. Set WETH share to be 1% to 15% + contract: cAMOStrategy, + signature: "setAllowedPoolWethShareInterval(uint256,uint256)", + args: [utils.parseUnits("0.01", 18), utils.parseUnits("0.15", 18)], + }, + ], + }; + } +); diff --git a/contracts/deploy/base/024_multisig_as_canceller.js b/contracts/deploy/base/024_multisig_as_canceller.js new file mode 100644 index 0000000000..9164388f33 --- /dev/null +++ b/contracts/deploy/base/024_multisig_as_canceller.js @@ -0,0 +1,27 @@ +const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnBaseWithGuardian( + { + deployName: "024_multisig_as_canceller", + }, + async ({ ethers }) => { + const cTimelock = await ethers.getContractAt( + "ITimelockController", + addresses.base.timelock + ); + + const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); + + return { + name: "Grant canceller role to 5/8 Multisig", + actions: [ + { + contract: cTimelock, + signature: "grantRole(bytes32,address)", + args: [timelockCancellerRole, addresses.base.governor], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/119_multisig_as_canceller.js b/contracts/deploy/mainnet/119_multisig_as_canceller.js new file mode 100644 index 0000000000..502b09e553 --- /dev/null +++ b/contracts/deploy/mainnet/119_multisig_as_canceller.js @@ -0,0 +1,37 @@ +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "119_multisig_as_canceller", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: + "68528526132026080998168122859635399048147530736160306492805070199180314362200", + }, + async () => { + const { timelockAddr } = await getNamedAccounts(); + + const cTimelock = await ethers.getContractAt( + "ITimelockController", + timelockAddr + ); + + const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); + + // Governance Actions + // ---------------- + return { + name: "Grant canceller role to 5/8 Multisig", + actions: [ + { + contract: cTimelock, + signature: "grantRole(bytes32,address)", + args: [timelockCancellerRole, addresses.mainnet.Guardian], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/120_remove_ousd_amo.js b/contracts/deploy/mainnet/120_remove_ousd_amo.js new file mode 100644 index 0000000000..bbf7abda2b --- /dev/null +++ b/contracts/deploy/mainnet/120_remove_ousd_amo.js @@ -0,0 +1,43 @@ +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "120_remove_ousd_amo", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: + "47377301530901645877668147419124102540503539821842750844128770769774878595548", + }, + async () => { + const cOUSDVaultProxy = await ethers.getContract("VaultProxy"); + const cOUSDVault = await ethers.getContractAt( + "IVault", + cOUSDVaultProxy.address + ); + + const cOUSDMetaStrategyProxy = await ethers.getContract( + "ConvexOUSDMetaStrategyProxy" + ); + + // Governance Actions + // ---------------- + return { + name: "Remove OUSD AMO Strategy", + actions: [ + { + contract: cOUSDVault, + signature: "removeStrategy(address)", + args: [cOUSDMetaStrategyProxy.address], + }, + { + contract: cOUSDVault, + signature: "setOusdMetaStrategy(address)", + args: [addresses.zero], + }, + ], + }; + } +); diff --git a/contracts/deploy/sonic/004_timelock_1d_delay.js b/contracts/deploy/sonic/004_timelock_1d_delay.js new file mode 100644 index 0000000000..f145e1d921 --- /dev/null +++ b/contracts/deploy/sonic/004_timelock_1d_delay.js @@ -0,0 +1,25 @@ +const { deployOnSonic } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnSonic( + { + deployName: "004_timelock_1d_delay", + }, + async ({ ethers }) => { + const cTimelock = await ethers.getContractAt( + "ITimelockController", + addresses.sonic.timelock + ); + + return { + actions: [ + { + // 1. Update delay to 1d + contract: cTimelock, + signature: "updateDelay(uint256)", + args: [24 * 60 * 60], + }, + ], + }; + } +); diff --git a/contracts/deploy/sonic/005_multisig_as_canceller.js b/contracts/deploy/sonic/005_multisig_as_canceller.js new file mode 100644 index 0000000000..4215430791 --- /dev/null +++ b/contracts/deploy/sonic/005_multisig_as_canceller.js @@ -0,0 +1,27 @@ +const { deployOnSonic } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnSonic( + { + deployName: "005_multisig_as_canceller", + }, + async ({ ethers }) => { + const cTimelock = await ethers.getContractAt( + "ITimelockController", + addresses.sonic.timelock + ); + + const timelockCancellerRole = await cTimelock.CANCELLER_ROLE(); + + return { + actions: [ + { + // 1. Grant canceller role to 5/8 Multisig + contract: cTimelock, + signature: "grantRole(bytes32,address)", + args: [timelockCancellerRole, addresses.sonic.admin], + }, + ], + }; + } +); diff --git a/contracts/deploy/sonic/006_yf_swpx_os_pool.js b/contracts/deploy/sonic/006_yf_swpx_os_pool.js new file mode 100644 index 0000000000..7a2842559f --- /dev/null +++ b/contracts/deploy/sonic/006_yf_swpx_os_pool.js @@ -0,0 +1,26 @@ +const { deployOnSonic } = require("../../utils/deploy-l2"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnSonic( + { + deployName: "006_yf_swpx_os_pool", + }, + async ({ ethers }) => { + const cOSonicProxy = await ethers.getContract("OSonicProxy"); + const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); + + return { + actions: [ + { + // 1. Delegate the yield from the SwapX SWPx/OS pool to SwapX Treasury + contract: cOSonic, + signature: "delegateYield(address,address)", + args: [ + addresses.sonic.SwapXSWPxOSPool, + addresses.sonic.SwapXTreasury, + ], + }, + ], + }; + } +); diff --git a/contracts/deployments/arbitrumOne/.migrations.json b/contracts/deployments/arbitrumOne/.migrations.json index 8cc91d34ab..2988389fe2 100644 --- a/contracts/deployments/arbitrumOne/.migrations.json +++ b/contracts/deployments/arbitrumOne/.migrations.json @@ -1,4 +1,4 @@ { - "084_deploy_woeth_on_arb": 1707820141, - "088_upgrade_woeth_on_arb": 1710959437 + "001_deploy_woeth_on_arb": 1707820141, + "002_upgrade_woeth_on_arb": 1710959437 } \ No newline at end of file diff --git a/contracts/deployments/base/.migrations.json b/contracts/deployments/base/.migrations.json index 903a923fd1..f2ce7f5587 100644 --- a/contracts/deployments/base/.migrations.json +++ b/contracts/deployments/base/.migrations.json @@ -19,5 +19,7 @@ "018_strategist_as_executor": 1729078818, "019_async_withdrawals": 1730100488, "020_upgrade_amo": 1730157451, - "021_multichain_strategist": 1736267102 -} \ No newline at end of file + "021_multichain_strategist": 1736267102, + "023_update_weth_share": 1737969363, + "024_multisig_as_canceller": 1737993822 +} diff --git a/contracts/deployments/mainnet/.migrations.json b/contracts/deployments/mainnet/.migrations.json index afb77bcc1d..ab6fe75e6e 100644 --- a/contracts/deployments/mainnet/.migrations.json +++ b/contracts/deployments/mainnet/.migrations.json @@ -102,5 +102,8 @@ "112_ousd_morpho_gauntlet_usdc": 1734483227, "113_ousd_morpho_gauntlet_usdt": 1734560711, "114_simple_harvester": 1736329331, - "117_oeth_fixed_rate_dripper": 1736875175 + "117_oeth_fixed_rate_dripper": 1736875175, + "118_multichain_strategist": 1736875175, + "119_multisig_as_canceller": 1737991984, + "120_remove_ousd_amo": 1737992146 } diff --git a/contracts/deployments/sonic/.migrations.json b/contracts/deployments/sonic/.migrations.json index 7b6b6a42f6..664ce615cd 100644 --- a/contracts/deployments/sonic/.migrations.json +++ b/contracts/deployments/sonic/.migrations.json @@ -1,5 +1,8 @@ { "001_vault_and_token": 1736867284, "002_oracle_router": 1737018791, - "003_sonic_staking_strategy": 1737523080 -} \ No newline at end of file + "003_sonic_staking_strategy": 1737523080, + "004_timelock_1d_delay": 1737667104, + "005_multisig_as_canceller": 1737993969, + "006_yf_swpx_os_pool": 1738198367 +} diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index 898eeb9060..5399c2d877 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -1,7 +1,9 @@ const ethers = require("ethers"); const { task } = require("hardhat/config"); const { + isArbitrum, isArbitrumFork, + isArbForkTest, isHoleskyFork, isHolesky, isForkTest, @@ -91,6 +93,8 @@ if (isHolesky || isHoleskyForkTest || isHoleskyFork) { paths.deploy = "deploy/base"; } else if (isSonic || isSonicFork || isSonicForkTest || isSonicUnitTest) { paths.deploy = "deploy/sonic"; +} else if (isArbitrum || isArbitrumFork || isArbForkTest) { + paths.deploy = "deploy/arbitrumOne"; } else { // holesky deployment files are in contracts/deploy/mainnet paths.deploy = "deploy/mainnet"; diff --git a/contracts/test/behaviour/sfcStakingStrategy.js b/contracts/test/behaviour/sfcStakingStrategy.js index fa8b45ee01..82b305210d 100644 --- a/contracts/test/behaviour/sfcStakingStrategy.js +++ b/contracts/test/behaviour/sfcStakingStrategy.js @@ -704,9 +704,10 @@ const shouldBehaveLikeASFCStakingStrategy = (context) => { "Pending withdrawals not reduced by expected amount" ); - expect(await sonicStakingStrategy.checkBalance(wS.address)).to.equal( + // the strategy will keep yielding so the balance can be higher + expect(await sonicStakingStrategy.checkBalance(wS.address)).to.gte( contractBalanceBefore.sub(amountToWithdraw), - "Strategy checkBalance not reduced by expected amount" + "Strategy checkBalance reduced by too much" ); }; diff --git a/contracts/test/vault/vault.mainnet.fork-test.js b/contracts/test/vault/vault.mainnet.fork-test.js index 2694dc3099..dda24e4b6a 100644 --- a/contracts/test/vault/vault.mainnet.fork-test.js +++ b/contracts/test/vault/vault.mainnet.fork-test.js @@ -79,9 +79,7 @@ describe("ForkTest: Vault", function () { it("Should have the correct OUSD MetaStrategy address set", async () => { const { vault } = fixture; - expect(await vault.ousdMetaStrategy()).to.equal( - addresses.mainnet.ConvexOUSDAMOStrategy - ); + expect(await vault.ousdMetaStrategy()).to.equal(addresses.zero); }); it("Should have supported assets", async () => { @@ -345,7 +343,6 @@ describe("ForkTest: Vault", function () { const knownStrategies = [ // Update this every time a new strategy is added. Below are mainnet addresses "0x5e3646A1Db86993f73E6b74A57D8640B69F7e259", // Aave - "0x89Eb88fEdc50FC77ae8a18aAD1cA0ac27f777a90", // OUSD MetaStrategy "0x79F2188EF9350A1dC11A062cca0abE90684b0197", // MorphoAaveStrategy "0x6b69B755C629590eD59618A2712d8a2957CA98FC", // Maker DSR Strategy "0x603CDEAEC82A60E3C4A10dA6ab546459E5f64Fa0", // Meta Morpho USDC diff --git a/contracts/test/vault/vault.sonic.fork-test.js b/contracts/test/vault/vault.sonic.fork-test.js index 6cb9911ea2..3ef6183d51 100644 --- a/contracts/test/vault/vault.sonic.fork-test.js +++ b/contracts/test/vault/vault.sonic.fork-test.js @@ -131,10 +131,7 @@ describe("ForkTest: Sonic Vault", function () { .withArgs(nick.address, mintAmount); // check 99% is deposited to staking strategy - const depositAmount = mintAmount.mul(99).div(100); - await expect(tx) - .to.emit(sonicStakingStrategy, "Deposit") - .withArgs(wS.address, addresses.zero, depositAmount); + await expect(tx).to.emit(sonicStakingStrategy, "Deposit"); }); it("should withdraw from staking strategy", async () => { diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index e8473375e0..c6ea7aa852 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -356,6 +356,10 @@ addresses.sonic.OSonicProxy = "0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794"; addresses.sonic.WOSonicProxy = "0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1"; addresses.sonic.OSonicVaultProxy = "0xa3c0eCA00D2B76b4d1F170b0AB3FdeA16C180186"; +// SwapX on Sonic +addresses.sonic.SwapXSWPxOSPool = "0x9Cb484FAD38D953bc79e2a39bBc93655256F0B16"; +addresses.sonic.SwapXTreasury = "0x896c3f0b63a8DAE60aFCE7Bca73356A9b611f3c8"; + // Holesky addresses.holesky.WETH = "0x94373a4919B3240D86eA41593D5eBa789FEF3848"; diff --git a/contracts/utils/deploy-l2.js b/contracts/utils/deploy-l2.js index 5424c45ddb..a6b20a110d 100644 --- a/contracts/utils/deploy-l2.js +++ b/contracts/utils/deploy-l2.js @@ -14,6 +14,7 @@ const { const { proposeGovernanceArgs } = require("./governor"); const { impersonateAndFund } = require("./signers"); const { getTxOpts } = require("./tx"); +const { mine } = require("@nomicfoundation/hardhat-network-helpers"); function log(msg, deployResult = null) { if (isBaseFork || isArbFork || process.env.VERBOSE) { @@ -222,6 +223,10 @@ function deployOnSonic(opts, fn) { withConfirmation, }; + // Mine one block to workaround "No known hardfork for execution on historical block" + // https://github.com/NomicFoundation/hardhat/issues/5511 + await mine(1); + const adminAddr = addresses.sonic.admin; console.log("Sonic Admin addr", adminAddr); diff --git a/contracts/utils/hardhat-helpers.js b/contracts/utils/hardhat-helpers.js index a69b495ee3..453f2d11f8 100644 --- a/contracts/utils/hardhat-helpers.js +++ b/contracts/utils/hardhat-helpers.js @@ -2,6 +2,7 @@ const fetch = require("sync-fetch"); require("dotenv").config(); const isFork = process.env.FORK === "true"; +const isArbitrum = process.env.NETWORK_NAME === "arbitrumOne"; const isArbitrumFork = process.env.FORK_NETWORK_NAME === "arbitrumOne"; const isHoleskyFork = process.env.FORK_NETWORK_NAME === "holesky"; const isHolesky = process.env.NETWORK_NAME === "holesky"; @@ -151,6 +152,7 @@ const networkMap = { module.exports = { isFork, + isArbitrum, isArbitrumFork, isBase, isBaseFork,