diff --git a/certora/specs/ERC20.spec b/certora/specs/ERC20.spec index 0097d3a6a..c06835337 100644 --- a/certora/specs/ERC20.spec +++ b/certora/specs/ERC20.spec @@ -285,8 +285,18 @@ rule noChangeTotalSupply(env e) { f(e, args); uint256 totalSupplyAfter = totalSupply(); + uint128 rateLimitPerSecond; + uint112 bufferCap; + uint32 lastBufferUsedTime; + uint112 bufferStored; + uint112 midpoint; + + rateLimitPerSecond, bufferCap, lastBufferUsedTime, bufferStored, midpoint = rateLimits(e.msg.sender); + + assert (totalSupplyAfter > totalSupplyBefore && bufferCap(e.msg.sender) > assert_uint256(midpoint)) => + bufferCap(e.msg.sender) != 0; assert totalSupplyAfter > totalSupplyBefore => f.selector == sig:mint(address,uint256).selector; - assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector; + assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector && bufferCap(e.msg.sender) != 0; } /* diff --git a/certora/specs/IERC20.spec b/certora/specs/IERC20.spec index b69a006f4..41794ac24 100644 --- a/certora/specs/IERC20.spec +++ b/certora/specs/IERC20.spec @@ -20,4 +20,5 @@ methods { function rateLimitPerSecond(address) external returns (uint256) envfree; function minBufferCap() external returns (uint112) envfree; function maxRateLimitPerSecond() external returns (uint128) envfree; + function rateLimits(address) external returns (uint128, uint112, uint32, uint112, uint112) envfree; } diff --git a/src/IStakedWell.sol b/src/IStakedWell.sol index bfc87a217..3ea0c5424 100644 --- a/src/IStakedWell.sol +++ b/src/IStakedWell.sol @@ -29,6 +29,18 @@ interface IStakedWell { uint256 blockNumber ) external view returns (uint256); + /// @notice view the reward speed for stkWELL + function assets( + address + ) + external + view + returns ( + uint128 emissionsPerSecond, + uint128 lastUpdateTimestamp, + uint256 index + ); + function redeem(address to, uint256 amount) external; function mint(address to, uint256 amount) external; diff --git a/src/governance/multichain/MultichainGovernorDeploy.sol b/src/governance/multichain/MultichainGovernorDeploy.sol index c02bf3779..daf83eda2 100644 --- a/src/governance/multichain/MultichainGovernorDeploy.sol +++ b/src/governance/multichain/MultichainGovernorDeploy.sol @@ -197,6 +197,10 @@ contract MultichainGovernorDeploy is Test { ); } + function deployStkWellImpl() public returns (address) { + return deployCode("StakedWell.sol:StakedWell"); + } + function deployStakedWell( address stakedToken, address rewardToken, diff --git a/src/proposals/MIPProposal.s.sol b/src/proposals/MIPProposal.s.sol index ed6b9f6b2..2ffe10410 100644 --- a/src/proposals/MIPProposal.s.sol +++ b/src/proposals/MIPProposal.s.sol @@ -66,9 +66,9 @@ abstract contract MIPProposal is Script { if (DO_AFTER_DEPLOY_SETUP) afterDeploySetup(addresses); vm.stopBroadcast(); + if (DO_TEARDOWN) teardown(addresses, deployerAddress); if (DO_BUILD) build(addresses); if (DO_RUN) run(addresses, deployerAddress); - if (DO_TEARDOWN) teardown(addresses, deployerAddress); if (DO_VALIDATE) validate(addresses, deployerAddress); /// todo print out actual proposal calldata if (DO_PRINT) { diff --git a/src/proposals/mips/mip-b16/MIP-B16.md b/src/proposals/mips/mip-b16/MIP-B16.md new file mode 100644 index 000000000..e46828f4a --- /dev/null +++ b/src/proposals/mips/mip-b16/MIP-B16.md @@ -0,0 +1,49 @@ +# MIP-B16 Base Safety Module Activation Resubmission + +**Authors**: Elliot, Ana + +## Simple Summary + +The original proposal was approved but contained an error that prevented +successful execution on Moonbeam. This issue has been corrected in this +resubmission. If this proposal passes, the community can expect to have rewards +enabled for the Safety Module on Base on Thursday. + +## Summary + +This proposal aims to activate reward emissions for the new Safety Module on +Base, incentivizing users to help secure the Moonwell protocol through staking +and participate in onchain governance. + +## Overview + +The Moonwell community has recently approved the implementation of a new +multichain governor contract to govern the Moonwell protocol. Additionally, a +new Base native WELL token utilizing the xERC20 token standard has been approved +for usage, which is a significant milestone for the community. By adopting this +new Base native WELL token, tokenholders will soon be able to stake their WELL +directly on Base and vote in onchain governance. A new Safety Module has already +been deployed on Base and is registered in the Multichain Vote Collection +contract as a source of voting power, allowing users securing the protocol to +participate in governance. Initially, rewards were set to 0 on the new Safety +Module on Base. This proposal aims to enable rewards for users in the Safety +Module with the new Base native WELL token. + +## Implementation + +This proposal will set and fund rewards that will be distributed to all Safety +Module stakers on Base. Initial reward speeds will be set to the value +recommended by emissions admin Warden Finance of 0.8962755116489610000 WELL per +second. **Voting** + +A "Yay" vote indicates your support for enabling reward emissions for the Safety +Module on Base, as outlined in this proposal. A "Nay" vote indicates your +opposition to enabling reward emissions for the Safety Module on Base. + +## Conclusion + +Adding rewards to the Safety Module on Base creates incentives for users to +backstop the protocol with the new Base native WELL token. Staking WELL in the +Safety Module not only gives community members the ability to earn native yield +on their holdings, but also presents an easy way to get involved in governance +as WELL is automatically self delegated. diff --git a/src/proposals/mips/mip-b16/mip-b16.sol b/src/proposals/mips/mip-b16/mip-b16.sol new file mode 100644 index 000000000..c36827cb4 --- /dev/null +++ b/src/proposals/mips/mip-b16/mip-b16.sol @@ -0,0 +1,123 @@ +//SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.19; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +import "@forge-std/Test.sol"; + +import {Addresses} from "@proposals/Addresses.sol"; +import {IStakedWell} from "@protocol/IStakedWell.sol"; +import {HybridProposal} from "@proposals/proposalTypes/HybridProposal.sol"; +import {ParameterValidation} from "@proposals/utils/ParameterValidation.sol"; +import {MultichainGovernorDeploy} from "@protocol/governance/multichain/MultichainGovernorDeploy.sol"; + +/// DO_VALIDATE=true DO_PRINT=true DO_BUILD=true DO_RUN=true forge script +/// src/proposals/mips/mip-b16/mip-b16.sol:mipb16 +contract mipb16 is + HybridProposal, + MultichainGovernorDeploy, + ParameterValidation +{ + string public constant name = "MIP-B16 Resubmission"; + + /// @notice this is based on Warden Finance's recommendation for reward speeds + uint256 public constant REWARD_SPEED = 896275511648961000; + + /// @notice the amount of WELL to be sent to the Safety Module for funding 38 days of rewards + /// 36*86400*.896275511648961000 = 2,787,775.3514329283 WELL, round up to 2,787,776 + uint256 public constant WELL_AMOUNT = 2_787_776 * 1e18; + + constructor() { + bytes memory proposalDescription = abi.encodePacked( + vm.readFile("./src/proposals/mips/mip-b16/MIP-B16.md") + ); + + _setProposalDescription(proposalDescription); + } + + /// @notice proposal's actions happen only on base + function primaryForkId() public view override returns (uint256) { + return baseForkId; + } + + function teardown(Addresses addresses, address) public override {} + + /// run this action through the Multichain Governor + function build(Addresses addresses) public override { + /// Base actions + + _pushHybridAction( + addresses.getAddress("xWELL_PROXY"), + abi.encodeWithSignature( + "transferFrom(address,address,uint256)", + addresses.getAddress("FOUNDATION_MULTISIG"), + addresses.getAddress("ECOSYSTEM_RESERVE_PROXY"), + WELL_AMOUNT + ), + "Transfer xWELL rewards to Ecosystem Reserve Proxy on Base", + false + ); + + _pushHybridAction( + addresses.getAddress("stkWELL_PROXY"), + abi.encodeWithSignature( + "configureAsset(uint128,address)", + REWARD_SPEED, + addresses.getAddress("stkWELL_PROXY") + ), + "Set reward speed for the Safety Module on Base", + false + ); + } + + function run(Addresses addresses, address) public override { + /// safety check to ensure no moonbeam actions are run + require( + baseActions.length == 2, + "MIP-B16: should have two base actions" + ); + + require( + moonbeamActions.length == 0, + "MIP-B16: should have no moonbeam actions" + ); + vm.selectFork(moonbeamForkId); + + _runMoonbeamMultichainGovernor(addresses, address(1000000000)); + + vm.selectFork(baseForkId); + + _runBase(addresses, addresses.getAddress("TEMPORAL_GOVERNOR")); + } + + /// @notice validations on Base + function validate(Addresses addresses, address) public override { + vm.selectFork(baseForkId); + + address stkWellProxy = addresses.getAddress("stkWELL_PROXY"); + ( + uint128 emissionsPerSecond, + uint128 lastUpdateTimestamp, + + ) = IStakedWell(stkWellProxy).assets(stkWellProxy); + + assertEq( + emissionsPerSecond, + REWARD_SPEED, + "MIP-B16: emissionsPerSecond incorrect" + ); + + assertGt( + lastUpdateTimestamp, + 0, + "MIP-B16: lastUpdateTimestamp not set" + ); + assertEq( + ERC20(addresses.getAddress("xWELL_PROXY")).balanceOf( + addresses.getAddress("ECOSYSTEM_RESERVE_PROXY") + ), + WELL_AMOUNT, + "MIP-B16: ecosystem reserve not funded" + ); + } +} diff --git a/src/proposals/proposalTypes/HybridProposal.sol b/src/proposals/proposalTypes/HybridProposal.sol index d6fbc69b0..b17534919 100644 --- a/src/proposals/proposalTypes/HybridProposal.sol +++ b/src/proposals/proposalTypes/HybridProposal.sol @@ -437,7 +437,7 @@ abstract contract HybridProposal is function build(Addresses) public virtual override {} - function teardown(Addresses, address) public pure virtual override {} + function teardown(Addresses, address) public virtual override {} function run(Addresses, address) public virtual override {} diff --git a/test/integration/MultichainProposal.t.sol b/test/integration/MultichainProposal.t.sol index 42899d24f..c6192bc73 100644 --- a/test/integration/MultichainProposal.t.sol +++ b/test/integration/MultichainProposal.t.sol @@ -33,9 +33,7 @@ import {IEcosystemReserveUplift, IEcosystemReserveControllerUplift} from "@proto import {TokenSaleDistributorInterfaceV1} from "@protocol/views/TokenSaleDistributorInterfaceV1.sol"; import {mipm23c} from "@proposals/mips/mip-m23/mip-m23c.sol"; -import {mipm25} from "@proposals/mips/mip-m25/mip-m25.sol"; -import {validateProxy} from "@proposals/utils/ProxyUtils.sol"; import {ITimelock as Timelock} from "@protocol/interfaces/ITimelock.sol"; /// @notice run this on a chainforked moonbeam node. @@ -269,6 +267,31 @@ contract MultichainProposalTest is ); } + function testNoBaseWormholeCoreAddressInProposal() public { + address wormholeBase = addresses.getAddress( + "WORMHOLE_CORE_BASE", + baseChainId + ); + vm.selectFork(moonbeamForkId); + uint256[] memory proposals = governor.liveProposals(); + for (uint256 i = 0; i < proposals.length; i++) { + if (proposals[i] == 4) { + continue; + } + + (address[] memory targets, , ) = governor.getProposalData( + proposals[i] + ); + + for (uint256 j = 0; j < targets.length; j++) { + require( + targets[j] != wormholeBase, + "targeted wormhole core base address on moonbeam" + ); + } + } + } + function testGetAllMarketConfigs() public { MultiRewardDistributor mrd = MultiRewardDistributor( addresses.getAddress("MRD_PROXY") @@ -2405,7 +2428,7 @@ contract MultichainProposalTest is ) = stkwell.assets(address(stkwell)); assertEq(1e18, emissionsPerSecond, "emissions per second"); - assertGt(index, 0, "index incorrect"); + assertGt(index, 1, "rewards per second"); assertEq( block.timestamp, lastUpdateTimestamp, @@ -2587,7 +2610,7 @@ contract MultichainProposalTest is ) = stkwell.assets(address(stkwell)); assertEq(1e18, emissionsPerSecond, "emissions per second"); - assertGt(index, 0, "index incorrect"); + assertGt(index, 1, "rewards per second"); assertEq( block.timestamp, lastUpdateTimestamp,