Skip to content

Commit 6d9ae0a

Browse files
Merge pull request #166 from moonwell-fi/feat/mip-b26
Safety Module Activation
2 parents 0b5de6e + fc93592 commit 6d9ae0a

File tree

9 files changed

+229
-7
lines changed

9 files changed

+229
-7
lines changed

certora/specs/ERC20.spec

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,18 @@ rule noChangeTotalSupply(env e) {
285285
f(e, args);
286286
uint256 totalSupplyAfter = totalSupply();
287287

288+
uint128 rateLimitPerSecond;
289+
uint112 bufferCap;
290+
uint32 lastBufferUsedTime;
291+
uint112 bufferStored;
292+
uint112 midpoint;
293+
294+
rateLimitPerSecond, bufferCap, lastBufferUsedTime, bufferStored, midpoint = rateLimits(e.msg.sender);
295+
296+
assert (totalSupplyAfter > totalSupplyBefore && bufferCap(e.msg.sender) > assert_uint256(midpoint)) =>
297+
bufferCap(e.msg.sender) != 0;
288298
assert totalSupplyAfter > totalSupplyBefore => f.selector == sig:mint(address,uint256).selector;
289-
assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector;
299+
assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector && bufferCap(e.msg.sender) != 0;
290300
}
291301

292302
/*

certora/specs/IERC20.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ methods {
2020
function rateLimitPerSecond(address) external returns (uint256) envfree;
2121
function minBufferCap() external returns (uint112) envfree;
2222
function maxRateLimitPerSecond() external returns (uint128) envfree;
23+
function rateLimits(address) external returns (uint128, uint112, uint32, uint112, uint112) envfree;
2324
}

src/IStakedWell.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ interface IStakedWell {
2929
uint256 blockNumber
3030
) external view returns (uint256);
3131

32+
/// @notice view the reward speed for stkWELL
33+
function assets(
34+
address
35+
)
36+
external
37+
view
38+
returns (
39+
uint128 emissionsPerSecond,
40+
uint128 lastUpdateTimestamp,
41+
uint256 index
42+
);
43+
3244
function redeem(address to, uint256 amount) external;
3345

3446
function mint(address to, uint256 amount) external;

src/governance/multichain/MultichainGovernorDeploy.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ contract MultichainGovernorDeploy is Test {
197197
);
198198
}
199199

200+
function deployStkWellImpl() public returns (address) {
201+
return deployCode("StakedWell.sol:StakedWell");
202+
}
203+
200204
function deployStakedWell(
201205
address stakedToken,
202206
address rewardToken,

src/proposals/MIPProposal.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ abstract contract MIPProposal is Script {
6666
if (DO_AFTER_DEPLOY_SETUP) afterDeploySetup(addresses);
6767
vm.stopBroadcast();
6868

69+
if (DO_TEARDOWN) teardown(addresses, deployerAddress);
6970
if (DO_BUILD) build(addresses);
7071
if (DO_RUN) run(addresses, deployerAddress);
71-
if (DO_TEARDOWN) teardown(addresses, deployerAddress);
7272
if (DO_VALIDATE) validate(addresses, deployerAddress);
7373
/// todo print out actual proposal calldata
7474
if (DO_PRINT) {

src/proposals/mips/mip-b16/MIP-B16.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# MIP-B16 Base Safety Module Activation Resubmission
2+
3+
**Authors**: Elliot, Ana
4+
5+
## Simple Summary
6+
7+
The original proposal was approved but contained an error that prevented
8+
successful execution on Moonbeam. This issue has been corrected in this
9+
resubmission. If this proposal passes, the community can expect to have rewards
10+
enabled for the Safety Module on Base on Thursday.
11+
12+
## Summary
13+
14+
This proposal aims to activate reward emissions for the new Safety Module on
15+
Base, incentivizing users to help secure the Moonwell protocol through staking
16+
and participate in onchain governance.
17+
18+
## Overview
19+
20+
The Moonwell community has recently approved the implementation of a new
21+
multichain governor contract to govern the Moonwell protocol. Additionally, a
22+
new Base native WELL token utilizing the xERC20 token standard has been approved
23+
for usage, which is a significant milestone for the community. By adopting this
24+
new Base native WELL token, tokenholders will soon be able to stake their WELL
25+
directly on Base and vote in onchain governance. A new Safety Module has already
26+
been deployed on Base and is registered in the Multichain Vote Collection
27+
contract as a source of voting power, allowing users securing the protocol to
28+
participate in governance. Initially, rewards were set to 0 on the new Safety
29+
Module on Base. This proposal aims to enable rewards for users in the Safety
30+
Module with the new Base native WELL token.
31+
32+
## Implementation
33+
34+
This proposal will set and fund rewards that will be distributed to all Safety
35+
Module stakers on Base. Initial reward speeds will be set to the value
36+
recommended by emissions admin Warden Finance of 0.8962755116489610000 WELL per
37+
second. **Voting**
38+
39+
A "Yay" vote indicates your support for enabling reward emissions for the Safety
40+
Module on Base, as outlined in this proposal. A "Nay" vote indicates your
41+
opposition to enabling reward emissions for the Safety Module on Base.
42+
43+
## Conclusion
44+
45+
Adding rewards to the Safety Module on Base creates incentives for users to
46+
backstop the protocol with the new Base native WELL token. Staking WELL in the
47+
Safety Module not only gives community members the ability to earn native yield
48+
on their holdings, but also presents an easy way to get involved in governance
49+
as WELL is automatically self delegated.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.19;
3+
4+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
6+
import "@forge-std/Test.sol";
7+
8+
import {Addresses} from "@proposals/Addresses.sol";
9+
import {IStakedWell} from "@protocol/IStakedWell.sol";
10+
import {HybridProposal} from "@proposals/proposalTypes/HybridProposal.sol";
11+
import {ParameterValidation} from "@proposals/utils/ParameterValidation.sol";
12+
import {MultichainGovernorDeploy} from "@protocol/governance/multichain/MultichainGovernorDeploy.sol";
13+
14+
/// DO_VALIDATE=true DO_PRINT=true DO_BUILD=true DO_RUN=true forge script
15+
/// src/proposals/mips/mip-b16/mip-b16.sol:mipb16
16+
contract mipb16 is
17+
HybridProposal,
18+
MultichainGovernorDeploy,
19+
ParameterValidation
20+
{
21+
string public constant name = "MIP-B16 Resubmission";
22+
23+
/// @notice this is based on Warden Finance's recommendation for reward speeds
24+
uint256 public constant REWARD_SPEED = 896275511648961000;
25+
26+
/// @notice the amount of WELL to be sent to the Safety Module for funding 38 days of rewards
27+
/// 36*86400*.896275511648961000 = 2,787,775.3514329283 WELL, round up to 2,787,776
28+
uint256 public constant WELL_AMOUNT = 2_787_776 * 1e18;
29+
30+
constructor() {
31+
bytes memory proposalDescription = abi.encodePacked(
32+
vm.readFile("./src/proposals/mips/mip-b16/MIP-B16.md")
33+
);
34+
35+
_setProposalDescription(proposalDescription);
36+
}
37+
38+
/// @notice proposal's actions happen only on base
39+
function primaryForkId() public view override returns (uint256) {
40+
return baseForkId;
41+
}
42+
43+
function teardown(Addresses addresses, address) public override {}
44+
45+
/// run this action through the Multichain Governor
46+
function build(Addresses addresses) public override {
47+
/// Base actions
48+
49+
_pushHybridAction(
50+
addresses.getAddress("xWELL_PROXY"),
51+
abi.encodeWithSignature(
52+
"transferFrom(address,address,uint256)",
53+
addresses.getAddress("FOUNDATION_MULTISIG"),
54+
addresses.getAddress("ECOSYSTEM_RESERVE_PROXY"),
55+
WELL_AMOUNT
56+
),
57+
"Transfer xWELL rewards to Ecosystem Reserve Proxy on Base",
58+
false
59+
);
60+
61+
_pushHybridAction(
62+
addresses.getAddress("stkWELL_PROXY"),
63+
abi.encodeWithSignature(
64+
"configureAsset(uint128,address)",
65+
REWARD_SPEED,
66+
addresses.getAddress("stkWELL_PROXY")
67+
),
68+
"Set reward speed for the Safety Module on Base",
69+
false
70+
);
71+
}
72+
73+
function run(Addresses addresses, address) public override {
74+
/// safety check to ensure no moonbeam actions are run
75+
require(
76+
baseActions.length == 2,
77+
"MIP-B16: should have two base actions"
78+
);
79+
80+
require(
81+
moonbeamActions.length == 0,
82+
"MIP-B16: should have no moonbeam actions"
83+
);
84+
vm.selectFork(moonbeamForkId);
85+
86+
_runMoonbeamMultichainGovernor(addresses, address(1000000000));
87+
88+
vm.selectFork(baseForkId);
89+
90+
_runBase(addresses, addresses.getAddress("TEMPORAL_GOVERNOR"));
91+
}
92+
93+
/// @notice validations on Base
94+
function validate(Addresses addresses, address) public override {
95+
vm.selectFork(baseForkId);
96+
97+
address stkWellProxy = addresses.getAddress("stkWELL_PROXY");
98+
(
99+
uint128 emissionsPerSecond,
100+
uint128 lastUpdateTimestamp,
101+
102+
) = IStakedWell(stkWellProxy).assets(stkWellProxy);
103+
104+
assertEq(
105+
emissionsPerSecond,
106+
REWARD_SPEED,
107+
"MIP-B16: emissionsPerSecond incorrect"
108+
);
109+
110+
assertGt(
111+
lastUpdateTimestamp,
112+
0,
113+
"MIP-B16: lastUpdateTimestamp not set"
114+
);
115+
assertEq(
116+
ERC20(addresses.getAddress("xWELL_PROXY")).balanceOf(
117+
addresses.getAddress("ECOSYSTEM_RESERVE_PROXY")
118+
),
119+
WELL_AMOUNT,
120+
"MIP-B16: ecosystem reserve not funded"
121+
);
122+
}
123+
}

src/proposals/proposalTypes/HybridProposal.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ abstract contract HybridProposal is
437437

438438
function build(Addresses) public virtual override {}
439439

440-
function teardown(Addresses, address) public pure virtual override {}
440+
function teardown(Addresses, address) public virtual override {}
441441

442442
function run(Addresses, address) public virtual override {}
443443

test/integration/MultichainProposal.t.sol

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ import {IEcosystemReserveUplift, IEcosystemReserveControllerUplift} from "@proto
3333
import {TokenSaleDistributorInterfaceV1} from "@protocol/views/TokenSaleDistributorInterfaceV1.sol";
3434

3535
import {mipm23c} from "@proposals/mips/mip-m23/mip-m23c.sol";
36-
import {mipm25} from "@proposals/mips/mip-m25/mip-m25.sol";
3736

38-
import {validateProxy} from "@proposals/utils/ProxyUtils.sol";
3937
import {ITimelock as Timelock} from "@protocol/interfaces/ITimelock.sol";
4038

4139
/// @notice run this on a chainforked moonbeam node.
@@ -269,6 +267,31 @@ contract MultichainProposalTest is
269267
);
270268
}
271269

270+
function testNoBaseWormholeCoreAddressInProposal() public {
271+
address wormholeBase = addresses.getAddress(
272+
"WORMHOLE_CORE_BASE",
273+
baseChainId
274+
);
275+
vm.selectFork(moonbeamForkId);
276+
uint256[] memory proposals = governor.liveProposals();
277+
for (uint256 i = 0; i < proposals.length; i++) {
278+
if (proposals[i] == 4) {
279+
continue;
280+
}
281+
282+
(address[] memory targets, , ) = governor.getProposalData(
283+
proposals[i]
284+
);
285+
286+
for (uint256 j = 0; j < targets.length; j++) {
287+
require(
288+
targets[j] != wormholeBase,
289+
"targeted wormhole core base address on moonbeam"
290+
);
291+
}
292+
}
293+
}
294+
272295
function testGetAllMarketConfigs() public {
273296
MultiRewardDistributor mrd = MultiRewardDistributor(
274297
addresses.getAddress("MRD_PROXY")
@@ -2405,7 +2428,7 @@ contract MultichainProposalTest is
24052428
) = stkwell.assets(address(stkwell));
24062429

24072430
assertEq(1e18, emissionsPerSecond, "emissions per second");
2408-
assertGt(index, 0, "index incorrect");
2431+
assertGt(index, 1, "rewards per second");
24092432
assertEq(
24102433
block.timestamp,
24112434
lastUpdateTimestamp,
@@ -2587,7 +2610,7 @@ contract MultichainProposalTest is
25872610
) = stkwell.assets(address(stkwell));
25882611

25892612
assertEq(1e18, emissionsPerSecond, "emissions per second");
2590-
assertGt(index, 0, "index incorrect");
2613+
assertGt(index, 1, "rewards per second");
25912614
assertEq(
25922615
block.timestamp,
25932616
lastUpdateTimestamp,

0 commit comments

Comments
 (0)