Skip to content

Commit

Permalink
Merge pull request #166 from moonwell-fi/feat/mip-b26
Browse files Browse the repository at this point in the history
Safety Module Activation
  • Loading branch information
ElliotFriedman authored Apr 12, 2024
2 parents 0b5de6e + fc93592 commit 6d9ae0a
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 7 deletions.
12 changes: 11 additions & 1 deletion certora/specs/ERC20.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/*
Expand Down
1 change: 1 addition & 0 deletions certora/specs/IERC20.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
12 changes: 12 additions & 0 deletions src/IStakedWell.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/governance/multichain/MultichainGovernorDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ contract MultichainGovernorDeploy is Test {
);
}

function deployStkWellImpl() public returns (address) {
return deployCode("StakedWell.sol:StakedWell");
}

function deployStakedWell(
address stakedToken,
address rewardToken,
Expand Down
2 changes: 1 addition & 1 deletion src/proposals/MIPProposal.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
49 changes: 49 additions & 0 deletions src/proposals/mips/mip-b16/MIP-B16.md
Original file line number Diff line number Diff line change
@@ -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.
123 changes: 123 additions & 0 deletions src/proposals/mips/mip-b16/mip-b16.sol
Original file line number Diff line number Diff line change
@@ -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"
);
}
}
2 changes: 1 addition & 1 deletion src/proposals/proposalTypes/HybridProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down
31 changes: 27 additions & 4 deletions test/integration/MultichainProposal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 6d9ae0a

Please sign in to comment.