This document explains how to add new markets to Moonwell from end to end, leveraging existing tooling in a way that allows integration and governance testing both pre and post-deploy.
All MIPS should be placed in the proposals/mips/mip-bxx
folder, where bxx is the next available MIP number. If the next available MIP number is 4, then the folder should be named mip-b04
. You should copy all of the existing files from the proposals/mips/examples/mip-market-listing
folder into the new mip-bxx
folder as a starting point. The only 3 files that are required are MIP-Bxx.md
, MTokens.json
, and RewardStreams.json
. The MIP-Bxx.md
file is the proposal description, the MTokens.json
file is the configuration for the MTokens being added, and the RewardStreams.json
file is the configuration for the reward streams being added.
Please also copy the mip-market-listing.sol
file into the new mip-bxx
folder and rename it to mip-bxx.sol
. This file is the Solidity script that will deploy the new markets and generate the calldata for the governance proposal. Even though you won't be executing it from this path, it's good to keep it as a reference to the version of code used to deploy the market.
Go to mainnetMTokensExample.json
to see what an example mToken JSON configuration looks like. Copy the file MTokens.json
in the proposals/mips/examples/mip-market-listing
folder to a new mip-bxx
folder, replacing all of the values with the correct values for those markets. Initial mint amount, collateral factor, should be set to the correct values and replaced with the actual values the market should have once deployed.
"initialMintAmount": 1e12,
"collateralFactor": 0.75e18,
"reserveFactor": 0.25e18,
"seizeShare": 0.03e18,
"supplyCap": 10500e18,
"borrowCap": 6300e18,
"priceFeedName": "ETH_ORACLE",
"tokenAddressName": "WETH",
"name": "Moonwell WETH",
"symbol": "mWETH",
"addressesString": "MOONWELL_WETH",
"jrm": {
"baseRatePerYear": 0.02e18,
"multiplierPerYear": 0.15e18,
"jumpMultiplierPerYear": 3e18,
"kink": 0.6e18
}
initialMintAmount
amount of tokens to mint during gov proposal, if token has 18 decimals, should be1e12
, if token has 6 decimals, should be1e6
. If token has greater than 18 decimals, decimals should be token decimals - 6 decimals. Temporal Governor must be funded with the initialMintAmount of said tokens before the proposal is executed in order for the proposal to succeed.collateralFactor
the percentage of value of supplied assets that is counted towards an account's collateral value, scaled up by1e18
. Must be less than1e18
to ensure system solvency.reserveFactor
percentage of interest accrued that goes to the Moonwell DAO reserves, scaled up by1e18
.seizeShare
percentage of seized collateral that goes to protocol reserves when a liquidation occurs, scaled up by1e18
.supplyCap
cap of amount of assets that can be supplied for a given market.borrowCap
cap of amount of assets that can be borrowed for a given market.priceFeedName
name of the chainlink aggregator address inAddresses.json
. Note user must add the address to Addresses.json on the proper network pre-deployment in order for the price feed to be set correctlytokenAddressName
name of the underlying token address name for the new market, set inAddresses.json
. Note user must add the underlying token address to Addresses.json on the proper network pre-deployment in order for the MToken's underlying address to be set correctlyname
name of the Moonwell MToken. Prefixed withMoonwell
.symbol
symbol of the Moonwell MToken. Prefixed withm
.addressesString
name of the address string set inAddresses.json
.jrm
parameters for the configuration of a JumpRateModel for each MToken.baseRatePerYear
cost to borrow per year as a percentage, scaled by1e18
.multiplierPerYear
multiplier on the base rate, which creates the curve of the rate before kink is hit, as a percentage, scaled by1e18
.jumpMultiplierPerYear
rate multiplier as a percentage, scaled by1e18
after kink is hit.kink
the point on the utilization curve after which the interest rate spikes usingjumpMultiplierPerYear
as a percentage, scaled by1e18
If there are no MTokens being added, the file is still needed, but it should contain an empty array.
Go to mainnetRewardStreams.json
to see what an example reward JSON configuration looks like. Copy the file RewardStreams.json
in the proposals/mips/examples/mip-market-listing
folder into the new mip-bxx
folder, replacing all of the values with the correct values for those markets.
If there are no reward streams, the file is still needed, but it should contain an empty array.
Once the proposal description has been created, copy and paste it into a file named MIP-Bxx.md
in the new mip-bxx
folder.
Once both the MIP-Bxx.md
, MTokens.json
, and RewardStreams.json
files have the necessary contents, environment variables must be set for the script to read in their path. Export the following environment variables pointing to the correct paths:
export LISTING_PATH="./src/proposals/mips/mip-bxx/MIP-Bxx.md"
export MTOKENS_PATH="./src/proposals/mips/mip-bxx/MTokens.json"
export EMISSION_PATH="./src/proposals/mips/mip-bxx/RewardStreams.json"
Set the BASE_RPC_URL
environment variable to the RPC URL of base. This can be done using the public RPC endpoint.
BASE_RPC_URL="https://mainnet.base.org"
Or by setting to a private RPC endpoint if the public end point is not working.
To get local tests running a chainfork with the new proposal type, set the PROPOSAL_ARTIFACT_PATH
to the path of the proposal artifact. For example, if the proposal artifact is located at artifacts/foundry/mip-b05.sol/mipb05.json
, the environment variable should be set to:
export PROPOSAL_ARTIFACT_PATH=artifacts/foundry/mip-b05.sol/mipb05.json
Once this has been set, then integration tests can be created which ensure the proposal is working as expected.
If deploying and generating calldata for the first time, environment variable DO_AFTER_DEPLOY_MTOKEN_BROADCAST
should be set to true. After doing deploy and setting the addresses in Addresses.json
, this variable should be set to false. This variable is used to determine whether or not to broadcast the after deploy transactions that configure the MToken, which are not needed after the tokens are deployed.
If any errors show up relating to not being able to read in a file, double check the environment variables and make sure the paths are correct.
To deploy these new markets, run mip-market-listing.sol
using command:
forge script src/proposals/mips/examples/mip-market-listing/mip-market-listing.sol:mip0x \
-vvvv \
--rpc-url base \
--broadcast
Once the contracts are deployed, copy and paste the JSON the deployment script outputted into the Addresses.json
file. Now, save these changes and proceed to the next step. DO NOT DELETE EXISTING JSON, JUST ADD TO IT.
Now that the contracts are deployed, it's time to generate the calldata. If the contracts have not been deployed and added to Addresses.json yet, go back to previous steps and do that first:
then, generate the calldata by running:
forge test --match-test testPrintNewMarketCalldataAlreadyDeployedMToken -vvv --fork-url base
If you encounter errors, ensure the environment variables are set correctly and the paths are correct.
Environment variable DO_AFTER_DEPLOY_MTOKEN_BROADCAST
should be set to false as this step should only be used to generate calldata. This variable is used to determine whether or not to broadcast the after deploy transactions that configure the MToken, which are not needed after the tokens are deployed as that should have been done in the deployment step.
Recommend referring to the GOVPROPOSALS.md file in the root of the repo for more information on how to generate calldata and deploy contracts. Then run the forge script command with the proper environment variables after the contracts have been deployed and addresses added to Addresses.json. Using this output, double check the calldata generated by the test is correct by comparing it to the calldata generated by running the commands in the GOVPROPOSALS.md file.
Then, scroll up to get the calldata to propose these changes to the DAO. After section "artemis governor queue governance calldata", copy and paste the calldata and send the calldata to the governance contract on moonbase by going to metamask and submitting a transaction to the ArtemisGovernor contract with the calldata the raw hex copied.
Once the calldata is sent, wait for the proposal to finish the voting period, then queue and execute it.
To test the changes introduced by creating these market(s) and ensure the system solvency, modify the PostProposalCheck to add the newMarketDeploy mip to the array of mips tested. This can be done by uncommenting the line that adds it to the mips
address array, and lengthening the array to support 2 active proposals, or if only doing this as a single proposal, write to mips array at index 0 and comment out the other line adds the incorrect MIP.
After PostProposalCheck is modified, the view the HundredFinanceExploit example file, and replicate the structure where the PostProposalCheck contract is imported and inherited, then write the necessary tests for these newly added markets, ensuring supplying, borrowing, repaying all work.
This framework is designed to ensure that the system is safe and that the parameters look to be correct. In the market-listing solidity file, there are checks that the parameters are within sane values. There are checks around the supply and borrow cap, and if these values are set to 0, no checks will run, however, if these values are non zero, the checks will ensure that the supply and borrow cap are not set to values that are too high. If the value of a supply or borrow cap exceed 120m tokens adjusted for decimals, then a warning will fire when running the script and the script will not continue. If these values are correct, you can override the warnings by setting:
export OVERRIDE_SUPPLY_CAP=true
export OVERRIDE_BORROW_CAP=true
If you set these variables to true, make sure to set them back to false after running the proposal to ensure no errors are missed in the future.
If the borrow cap is not equal to 0, and there is no supply cap, this is invalid for a governance proposal and the creation of the proposal will revert with an appropriate message.
If the supply cap is set, but the borrow cap is greater than or equal to the supply cap, this is invalid for a governance proposal and the creation of the proposal will revert with an appropriate message.
If the collateral factor of a market is set to greater than 95%, then the creation of the proposal will revert with an appropriate message as it is assumed that no collateral will ever have a collateral factor higher than 95%.