diff --git a/docs/MIP-R35.md b/docs/MIP-R35.md new file mode 100644 index 0000000..cb43799 --- /dev/null +++ b/docs/MIP-R35.md @@ -0,0 +1,31 @@ +# MIP-R35: Moonriver Collateral Factor Changes + +### **Summary** +This proposal suggests changing the collateral factors on the Moonriver markets further to continue the current market wind-down. For those unfamiliar, Moonriver price feeds will be deprecated in early January, and as a result, Moonwell is winding down these markets to prevent a possible issue in the future. Recently, collateral factor, reward variables, and supply and borrow caps were already modified to start the wind down process, this is the next reasonable conclusion. + +The following markets will have their collateral factors modified: xcKSM, MOVR, and FRAX. This proposal details the specific percentage changes to these variables. + +### **Background** +A few weeks ago, the Moonwell community received knowledge that Chainlink is deprecating its price feeds across the Moonriver network. Out of concern for Moonwell's users, we believe the best course of actions is to slowly deprecate the respective markets affected on Moonwell's Moonriver implementation: xcKSM, FRAX, and MOVR. + +### **Proposal** + +If this proposal is successful, the following markets will have their current collateral factors changed to the proposed collateral factors: + +| Market | Current Collateral Factor | Proposed Collateral Factor | +|--------|----------------------------|-----------------------------| +| xcKSM | 50% | 25% | +| MOVR | 50% | 25% | +| FRAX | 40% | 25% | + +These recommendations were provided by Anthias Labs in their forum post: [Moonriver Chain Sunset](https://forum.moonwell.fi/t/moonriver-chain-sunset/2042) + +### **Rationale** + +Lowering the collateral factors on Moonriver assets is a necessary and responsible step to ensure a safe wind-down of the affected markets ahead of Chainlink's price feed deprecation. Once these feeds go offline, Moonwell would no longer be able to reliably determine asset values on Moonriver, creating a material risk of under-collateralized borrowing, inaccurate liquidations, and potential bad debt. + +### **Voting Options** + +- **For:** Approve collateral factor changes to the Moonriver markets. +- **Against:** Do not make collateral factor changes to the Moonriver markets. +- **Abstain:** No preference. diff --git a/docs/MIP-R37.md b/docs/MIP-R37.md new file mode 100644 index 0000000..8e81fc8 --- /dev/null +++ b/docs/MIP-R37.md @@ -0,0 +1,33 @@ +# MIP-R37: Moonriver Progressive Factor Reductions + +### **Summary** +This proposal recommends reducing collateral factors on Moonriver markets from 15% to 7.5% as part of the ongoing wind-down of Moonriver lending activity. This will affect xcKSM, MOVR, and FRAX markets respectively. + +Chainlink has announced plans to deprecate oracle feeds on Moonriver, materially increasing oracle risk for remaining positions. In advance of this deadline, Anthias Labs has recommended continuing to lower collateral factors toward zero to reduce protocol exposure and encourage orderly position closure. + +This proposal represents the next step in that process. + +### **Background** +A few weeks ago, the Moonwell community received knowledge that Chainlink is deprecating its price feeds across the Moonriver network. Out of concern for Moonwell's users, we believe the best course of actions is to slowly deprecate the respective markets affected on Moonwell's Moonriver implementation: xcKSM, FRAX, and MOVR. + +### **Proposal** + +If this proposal is successful, the following markets will have their current collateral factors changed to the proposed collateral factors: + +| Market | Current Collateral Factor | Proposed Collateral Factor | +|--------|---------------------------|----------------------------| +| xcKSM | 15% | 7.5% | +| MOVR | 15% | 7.5% | +| FRAX | 14% | 7.5% | + +These recommendations were provided by Anthias Labs in their forum post: [Anthias Recommendations](https://forum.moonwell.fi/t/anthias-labs-risk-parameter-recommendations/1759/9) + +### **Rationale** + +Lowering the collateral factors on Moonriver assets is a necessary and responsible step to ensure a safe wind-down of the affected markets ahead of Chainlink price feed deprecation. Once these feeds go offline, Moonwell would no longer be able to reliably determine asset values on Moonriver. + +### **Voting Options** + +- **For:** Approve collateral factor changes to the Moonriver markets. +- **Against:** Do not make collateral factor changes to the Moonriver markets. +- **Abstain:** No preference. diff --git a/test/apollo/mip-r35/assertCurrentExpectedState.ts b/test/apollo/mip-r35/assertCurrentExpectedState.ts new file mode 100644 index 0000000..6045e37 --- /dev/null +++ b/test/apollo/mip-r35/assertCurrentExpectedState.ts @@ -0,0 +1,27 @@ +import {ethers} from "ethers"; +import {ContractBundle} from "@moonwell-fi/moonwell.js"; +import {STARTING_COLLATERAL_FACTORS} from "./vars"; +import BigNumber from "bignumber.js"; + +export async function assertCurrentExpectedState(contracts: ContractBundle, provider: ethers.providers.JsonRpcProvider){ + console.log("[+] Asserting collateral factors are in expected state BEFORE gov proposal passes") + + const comptroller = contracts.COMPTROLLER.contract.connect(provider) + + // Assert that the markets have the expected collateral factors before the proposal + for (const [ticker, expectedCF] of Object.entries(STARTING_COLLATERAL_FACTORS)) { + const market = contracts.MARKETS[ticker] + if (market) { + const marketData = await comptroller.markets(market.mTokenAddress) + const cfMantissa = new BigNumber(marketData.collateralFactorMantissa.toString()) + const cfPercent = cfMantissa.div(1e18).times(100).toNumber() + + if (Math.abs(cfPercent - expectedCF) > 0.01) { + throw new Error(`${ticker} CF expected ${expectedCF}% but found ${cfPercent}%`) + } + console.log(` ✅ ${ticker} Collateral Factor is ${cfPercent}%`) + } + } + + console.log("[+] ✅ All collateral factors are in expected state before proposal") +} diff --git a/test/apollo/mip-r35/assertExpectedEndState.ts b/test/apollo/mip-r35/assertExpectedEndState.ts new file mode 100644 index 0000000..956074d --- /dev/null +++ b/test/apollo/mip-r35/assertExpectedEndState.ts @@ -0,0 +1,18 @@ +import {ethers} from "ethers"; +import {assertMarketCFEqualsPercent} from "../../../src"; +import {ContractBundle} from "@moonwell-fi/moonwell.js"; +import {COLLATERAL_FACTOR_CHANGES} from "./vars"; + +export async function assertExpectedEndState(contracts: ContractBundle, provider: ethers.providers.JsonRpcProvider){ + console.log("[+] Asserting collateral factors are updated AFTER gov proposal passed") + + // Assert that the markets have updated collateral factors after the proposal + for (const [ticker, expectedCF] of Object.entries(COLLATERAL_FACTOR_CHANGES)) { + const market = contracts.MARKETS[ticker] + if (market) { + await assertMarketCFEqualsPercent(provider, contracts, market, expectedCF) + } + } + + console.log("[+] ✅ All collateral factors are updated after proposal") +} diff --git a/test/apollo/mip-r35/generateProposalData.ts b/test/apollo/mip-r35/generateProposalData.ts new file mode 100644 index 0000000..a17905d --- /dev/null +++ b/test/apollo/mip-r35/generateProposalData.ts @@ -0,0 +1,47 @@ +import {ethers} from "ethers"; +import {BigNumber as EthersBigNumber} from "@ethersproject/bignumber/lib/bignumber"; +import {addProposalToPropData, ProposalData} from "../../../src"; +import {ContractBundle} from "@moonwell-fi/moonwell.js"; +import {COLLATERAL_FACTOR_CHANGES} from "./vars"; +import BigNumber from "bignumber.js"; + +function cfPercentToMantissa(newCFPercent: number){ + return EthersBigNumber.from( + new BigNumber(newCFPercent) + .div(100) + .times(new BigNumber('1e18')) + .toFixed(0) + ) +} + +export async function generateProposalData(contracts: ContractBundle, provider: ethers.providers.JsonRpcProvider){ + const comptroller = contracts.COMPTROLLER.contract.connect(provider) + + const proposalData: ProposalData = { + targets: [], + values: [], + signatures: [], + callDatas: [], + } + + // Add collateral factor changes for each market + const cfOrder = ['xcKSM', 'MOVR', 'FRAX'] // Fixed order + for (const marketTicker of cfOrder) { + const newCF = COLLATERAL_FACTOR_CHANGES[marketTicker] + if (newCF !== undefined) { + const market = contracts.MARKETS[marketTicker] + if (market) { + console.log(` Adding CF change for ${marketTicker}: ${newCF}% (mToken: ${market.mTokenAddress})`) + await addProposalToPropData(comptroller, '_setCollateralFactor', + [ + market.mTokenAddress, + cfPercentToMantissa(newCF) + ], + proposalData + ) + } + } + } + + return proposalData +} diff --git a/test/apollo/mip-r35/getCalldata.ts b/test/apollo/mip-r35/getCalldata.ts new file mode 100644 index 0000000..fc5810a --- /dev/null +++ b/test/apollo/mip-r35/getCalldata.ts @@ -0,0 +1,78 @@ +import {ethers} from 'ethers' +import {Contracts} from '@moonwell-fi/moonwell.js' +import {generateProposalData} from "./generateProposalData" +import {RPC_URL, STARTING_COLLATERAL_FACTORS, COLLATERAL_FACTOR_CHANGES} from "./vars" +import * as fs from 'fs' +import * as path from 'path' + +async function main() { + const contracts = Contracts.moonriver + const provider = new ethers.providers.JsonRpcProvider(RPC_URL) + + console.log('\n===========================================') + console.log('MIP-R35: Moonriver Collateral Factor Changes') + console.log('===========================================\n') + + console.log('Governor Address:', contracts.GOVERNOR.address) + console.log('Expected:', '0x2BE2e230e89c59c8E20E633C524AD2De246e7370') + console.log('Match:', contracts.GOVERNOR.address === '0x2BE2e230e89c59c8E20E633C524AD2De246e7370' ? '✅' : '❌') + console.log() + + console.log('Generating proposal data...\n') + const proposalData = await generateProposalData(contracts, provider) + + // Read the full proposal description from the markdown file + const markdownPath = path.join(__dirname, '../../../docs/MIP-R35.md') + const description = fs.readFileSync(markdownPath, 'utf-8') + + console.log('✅ Loaded full markdown proposal from docs/MIP-R35.md') + console.log(`Description length: ${description.length} characters\n`) + + console.log('=== PROPOSAL DESCRIPTION ===\n') + console.log(description) + console.log('\n=== END DESCRIPTION ===\n') + + console.log('=== PROPOSAL DATA ===\n') + console.log(JSON.stringify(proposalData, null, 2)) + console.log('\n=== SUMMARY ===') + console.log(`Total actions: ${proposalData.targets.length}`) + console.log(`Target (Comptroller): ${proposalData.targets[0]}`) + console.log('\nActions:') + const cfMarkets = ['xcKSM', 'MOVR', 'FRAX'] + proposalData.signatures.forEach((sig, i) => { + console.log(` ${i + 1}. ${sig} - ${cfMarkets[i]} CF`) + }) + console.log('\nCollateral Factor Changes:') + for (const market of cfMarkets) { + const oldCF = STARTING_COLLATERAL_FACTORS[market] + const newCF = COLLATERAL_FACTOR_CHANGES[market] + console.log(` - ${market}: ${oldCF}% → ${newCF}%`) + } + + // Encode the propose function call + console.log('\n===========================================') + console.log('ENCODED PROPOSE CALLDATA') + console.log('===========================================\n') + + const governor = contracts.GOVERNOR.contract.connect(provider) + const proposeCalldata = await governor.populateTransaction.propose( + proposalData.targets, + proposalData.values, + proposalData.signatures, + proposalData.callDatas, + description + ) + + console.log('To:', contracts.GOVERNOR.address) + console.log('Data:', proposeCalldata.data) + console.log('\nCalldata Length:', proposeCalldata.data!.length, 'characters') + console.log('\nYou can submit this transaction to the Governor contract at:') + console.log('https://moonriver.moonscan.io/address/' + contracts.GOVERNOR.address + '#writeContract') +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/test/apollo/mip-r35/mip-r35-verification.ts b/test/apollo/mip-r35/mip-r35-verification.ts new file mode 100644 index 0000000..171f470 --- /dev/null +++ b/test/apollo/mip-r35/mip-r35-verification.ts @@ -0,0 +1,113 @@ +import {ethers, BigNumber as EthersBigNumber} from 'ethers' +import { + passGovProposal, + setupDeployerAndEnvForGovernance, + sleep, + startGanache, + replaceXCAssetWithDummyERC20 +} from "../../../src"; + +import {Contracts} from '@moonwell-fi/moonwell.js' +import {generateProposalData} from "./generateProposalData"; +import {assertCurrentExpectedState} from "./assertCurrentExpectedState"; +import {assertExpectedEndState} from "./assertExpectedEndState"; +import {F_MOVR_GRANT, FORK_BLOCK, RPC_URL, COLLATERAL_FACTOR_CHANGES} from "./vars"; + +// Helper function to set MFAM balance directly using storage manipulation +async function setMFAMBalance(provider: ethers.providers.JsonRpcProvider, address: string, amount: EthersBigNumber) { + const govTokenAddress = Contracts.moonriver.GOV_TOKEN.address + + // MFAM uses mapping(address => uint96) balances at slot 1 (not slot 0!) + // Storage slot = keccak256(abi.encode(address, slot)) + const paddedAddress = ethers.utils.hexZeroPad(address, 32) + const paddedSlot = ethers.utils.hexZeroPad('0x01', 32) + const slot = ethers.utils.keccak256(paddedAddress + paddedSlot.slice(2)) + + // Set the balance - MFAM uses uint96, but we can set the full word + await provider.send('evm_setAccountStorageAt', [ + govTokenAddress, + slot, + ethers.utils.hexZeroPad(amount.toHexString(), 32) + ]) +} + +test("mip-r35-verification", async () => { + console.log("\n===========================================") + console.log("MIP-R35: Moonriver Collateral Factor Changes") + console.log("===========================================\n") + console.log("Summary: This proposal reduces collateral factors") + console.log("for all 3 Moonriver markets as part of the wind-down process.") + console.log("Collateral factors: xcKSM=25%, MOVR=25%, FRAX=25%\n") + + const contracts = Contracts.moonriver + + const forkedChainProcess = await startGanache(contracts, + FORK_BLOCK, + RPC_URL, + [F_MOVR_GRANT] + ) + + console.log("Waiting 5 seconds for chain to bootstrap...") + await sleep(5) + + try { + const provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545') + + // At recent blocks, F_MOVR_GRANT is empty. Inject MFAM tokens directly. + const quorumAmount = EthersBigNumber.from('50000000').mul(EthersBigNumber.from(10).pow(18)) // 50M MFAM + await setMFAMBalance(provider, F_MOVR_GRANT, quorumAmount) + console.log("[+] Injected 50M MFAM into F_MOVR_GRANT for governance testing") + + // Mock xcKSM XC-20 precompile with a dummy ERC20 + // Ganache doesn't properly handle XC-20 precompiles, causing symbol() to fail + await replaceXCAssetWithDummyERC20( + provider, + contracts.MARKETS['FRAX'], // Clone from FRAX (regular ERC20) + contracts.MARKETS['xcKSM'] // Replace xcKSM XC-20 precompile + ) + // Set the symbol storage slot to "xcKSM" (slot 4 for OpenZeppelin ERC20) + const symbolBytes = ethers.utils.formatBytes32String('xcKSM') + const symbolWithLength = EthersBigNumber.from(symbolBytes).add('xcKSM'.length * 2).toHexString() + await provider.send('evm_setAccountStorageAt', [ + contracts.MARKETS['xcKSM'].tokenAddress, + ethers.utils.hexZeroPad('0x04', 32), // slot 4 = _symbol + ethers.utils.hexZeroPad(symbolWithLength, 32) + ]) + console.log("[+] Mocked xcKSM XC-20 precompile with dummy ERC20 for Ganache testing") + + // Note: Using current on-chain collateral factors (pre-MIP-R33) + // xcKSM=59%, MOVR=60%, FRAX=50% + + await setupDeployerAndEnvForGovernance( + contracts, + provider, + F_MOVR_GRANT, + FORK_BLOCK, + 5_000_000 // Quorum adjusts up just short of this amount when the proposal is submitted + ) + + await assertCurrentExpectedState(contracts, provider) + + // Generate new proposal data + const proposalData = await generateProposalData(contracts, provider) + + console.log("\n[+] Proposal Summary:") + console.log(` - Total actions: ${proposalData.targets.length}`) + console.log(` - Markets: xcKSM, MOVR, FRAX`) + console.log(` - Collateral factors: xcKSM=25%, MOVR=25%, FRAX=25%\n`) + + // Pass the proposal + await passGovProposal(contracts, provider, proposalData) + + // Assert that our end state is as desired + await assertExpectedEndState(contracts, provider) + + console.log("\n✅ MIP-R35 verification complete!") + console.log("Collateral factors reduced on all 3 Moonriver markets\n") + } finally { + // Kill our child chain. + console.log("Shutting down Ganache chain. PID", forkedChainProcess.pid!) + process.kill(-forkedChainProcess.pid!) + console.log("Ganache chain stopped.") + } +}); diff --git a/test/apollo/mip-r35/vars.ts b/test/apollo/mip-r35/vars.ts new file mode 100644 index 0000000..73abb41 --- /dev/null +++ b/test/apollo/mip-r35/vars.ts @@ -0,0 +1,26 @@ +export * from '../base-vars' + +// Fork block - use a recent block after MIP-R33 has passed +// MIP-R33 set CF to: xcKSM=50%, MOVR=50%, FRAX=40% +// We need to use a block after that proposal executed +export const FORK_BLOCK = 14_090_000 + +// RPC URL - use environment variable or fallback to public RPC +export const RPC_URL = process.env.MOONRIVER_RPC_URL || 'https://rpc.api.moonriver.moonbeam.network' + +// Starting collateral factors (current on-chain values at block 14,090,000) +// Note: MIP-R33 will change these to xcKSM=50%, MOVR=50%, FRAX=40% +// But we test from current state for now +export const STARTING_COLLATERAL_FACTORS: { [market: string]: number } = { + 'xcKSM': 59, // Current on-chain value + 'MOVR': 60, // Current on-chain value + 'FRAX': 50, // Current on-chain value +} + +// Collateral factor changes - new collateral factors for each market +// Target values: xcKSM=25%, MOVR=25%, FRAX=25% +export const COLLATERAL_FACTOR_CHANGES: { [market: string]: number } = { + 'xcKSM': 25, // 25% (down from 50%) + 'MOVR': 25, // 25% (down from 50%) + 'FRAX': 25, // 25% (down from 40%) +} diff --git a/test/apollo/mip-r37/generateProposalData.ts b/test/apollo/mip-r37/generateProposalData.ts new file mode 100644 index 0000000..a17905d --- /dev/null +++ b/test/apollo/mip-r37/generateProposalData.ts @@ -0,0 +1,47 @@ +import {ethers} from "ethers"; +import {BigNumber as EthersBigNumber} from "@ethersproject/bignumber/lib/bignumber"; +import {addProposalToPropData, ProposalData} from "../../../src"; +import {ContractBundle} from "@moonwell-fi/moonwell.js"; +import {COLLATERAL_FACTOR_CHANGES} from "./vars"; +import BigNumber from "bignumber.js"; + +function cfPercentToMantissa(newCFPercent: number){ + return EthersBigNumber.from( + new BigNumber(newCFPercent) + .div(100) + .times(new BigNumber('1e18')) + .toFixed(0) + ) +} + +export async function generateProposalData(contracts: ContractBundle, provider: ethers.providers.JsonRpcProvider){ + const comptroller = contracts.COMPTROLLER.contract.connect(provider) + + const proposalData: ProposalData = { + targets: [], + values: [], + signatures: [], + callDatas: [], + } + + // Add collateral factor changes for each market + const cfOrder = ['xcKSM', 'MOVR', 'FRAX'] // Fixed order + for (const marketTicker of cfOrder) { + const newCF = COLLATERAL_FACTOR_CHANGES[marketTicker] + if (newCF !== undefined) { + const market = contracts.MARKETS[marketTicker] + if (market) { + console.log(` Adding CF change for ${marketTicker}: ${newCF}% (mToken: ${market.mTokenAddress})`) + await addProposalToPropData(comptroller, '_setCollateralFactor', + [ + market.mTokenAddress, + cfPercentToMantissa(newCF) + ], + proposalData + ) + } + } + } + + return proposalData +} diff --git a/test/apollo/mip-r37/getCalldata.ts b/test/apollo/mip-r37/getCalldata.ts new file mode 100644 index 0000000..5aefc7d --- /dev/null +++ b/test/apollo/mip-r37/getCalldata.ts @@ -0,0 +1,78 @@ +import {ethers} from 'ethers' +import {Contracts} from '@moonwell-fi/moonwell.js' +import {generateProposalData} from "./generateProposalData" +import {RPC_URL, STARTING_COLLATERAL_FACTORS, COLLATERAL_FACTOR_CHANGES} from "./vars" +import * as fs from 'fs' +import * as path from 'path' + +async function main() { + const contracts = Contracts.moonriver + const provider = new ethers.providers.JsonRpcProvider(RPC_URL) + + console.log('\n===========================================') + console.log('MIP-R37: Moonriver Collateral Factor Changes') + console.log('===========================================\n') + + console.log('Governor Address:', contracts.GOVERNOR.address) + console.log('Expected:', '0x2BE2e230e89c59c8E20E633C524AD2De246e7370') + console.log('Match:', contracts.GOVERNOR.address === '0x2BE2e230e89c59c8E20E633C524AD2De246e7370' ? '✅' : '❌') + console.log() + + console.log('Generating proposal data...\n') + const proposalData = await generateProposalData(contracts, provider) + + // Read the full proposal description from the markdown file + const markdownPath = path.join(__dirname, '../../../docs/MIP-R37.md') + const description = fs.readFileSync(markdownPath, 'utf-8') + + console.log('✅ Loaded full markdown proposal from docs/MIP-R37.md') + console.log(`Description length: ${description.length} characters\n`) + + console.log('=== PROPOSAL DESCRIPTION ===\n') + console.log(description) + console.log('\n=== END DESCRIPTION ===\n') + + console.log('=== PROPOSAL DATA ===\n') + console.log(JSON.stringify(proposalData, null, 2)) + console.log('\n=== SUMMARY ===') + console.log(`Total actions: ${proposalData.targets.length}`) + console.log(`Target (Comptroller): ${proposalData.targets[0]}`) + console.log('\nActions:') + const cfMarkets = ['xcKSM', 'MOVR', 'FRAX'] + proposalData.signatures.forEach((sig, i) => { + console.log(` ${i + 1}. ${sig} - ${cfMarkets[i]} CF`) + }) + console.log('\nCollateral Factor Changes:') + for (const market of cfMarkets) { + const oldCF = STARTING_COLLATERAL_FACTORS[market] + const newCF = COLLATERAL_FACTOR_CHANGES[market] + console.log(` - ${market}: ${oldCF}% → ${newCF}%`) + } + + // Encode the propose function call + console.log('\n===========================================') + console.log('ENCODED PROPOSE CALLDATA') + console.log('===========================================\n') + + const governor = contracts.GOVERNOR.contract.connect(provider) + const proposeCalldata = await governor.populateTransaction.propose( + proposalData.targets, + proposalData.values, + proposalData.signatures, + proposalData.callDatas, + description + ) + + console.log('To:', contracts.GOVERNOR.address) + console.log('Data:', proposeCalldata.data) + console.log('\nCalldata Length:', proposeCalldata.data!.length, 'characters') + console.log('\nYou can submit this transaction to the Governor contract at:') + console.log('https://moonriver.moonscan.io/address/' + contracts.GOVERNOR.address + '#writeContract') +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/test/apollo/mip-r37/outputCalldata.ts b/test/apollo/mip-r37/outputCalldata.ts new file mode 100644 index 0000000..3306d09 --- /dev/null +++ b/test/apollo/mip-r37/outputCalldata.ts @@ -0,0 +1,142 @@ +import { BigNumber as EthersBigNumber } from "@ethersproject/bignumber/lib/bignumber"; +import { Contracts } from "@moonwell-fi/moonwell.js"; +import BigNumber from "bignumber.js"; + +// Configuration +const MARKETS_TO_UPDATE = ['MOVR', 'xcKSM', 'FRAX'] as const; +const NEW_COLLATERAL_FACTOR = 7.5; // 7.5% + +// Proposal description +const PROPOSAL_DESCRIPTION = `# MIP-R37: Moonriver Progressive Factor Reductions + +### **Summary** +This proposal recommends reducing collateral factors on Moonriver markets from 15% to 7.5% as part of the ongoing wind-down of Moonriver lending activity. This will affect xcKSM, MOVR, and FRAX markets respectively. + +Chainlink has announced plans to deprecate oracle feeds on Moonriver, materially increasing oracle risk for remaining positions. In advance of this deadline, Anthias Labs has recommended continuing to lower collateral factors toward zero to reduce protocol exposure and encourage orderly position closure. + +This proposal represents the next step in that process. + +### **Background** +A few weeks ago, the Moonwell community received knowledge that Chainlink is deprecating its price feeds across the Moonriver network. Out of concern for Moonwell's users, we believe the best course of actions is to slowly deprecate the respective markets affected on Moonwell's Moonriver implementation: xcKSM, FRAX, and MOVR. + +### **Proposal** + +If this proposal is successful, the following markets will have their current collateral factors changed to the proposed collateral factors: + +| Market | Current Collateral Factor | Proposed Collateral Factor | +|--------|---------------------------|----------------------------| +| xcKSM | 15% | 7.5% | +| MOVR | 15% | 7.5% | +| FRAX | 14% | 7.5% | + +These recommendations were provided by Anthias Labs in their forum post: [Anthias Recommendations](https://forum.moonwell.fi/t/anthias-labs-risk-parameter-recommendations/1759/9) + +### **Rationale** + +Lowering the collateral factors on Moonriver assets is a necessary and responsible step to ensure a safe wind-down of the affected markets ahead of Chainlink price feed deprecation. Once these feeds go offline, Moonwell would no longer be able to reliably determine asset values on Moonriver. + +### **Voting Options** + +- **For:** Approve collateral factor changes to the Moonriver markets. +- **Against:** Do not make collateral factor changes to the Moonriver markets. +- **Abstain:** No preference. +`; + +function cfPercentToMantissa(newCFPercent: number){ + return EthersBigNumber.from( + new BigNumber(newCFPercent) + .div(100) + .times(new BigNumber('1e18')) + .toFixed(0) + ) +} + +async function main() { + const contracts = Contracts.moonriver; + + const comptroller = contracts.COMPTROLLER.contract; + const governor = contracts.GOVERNOR.contract; + + console.log("==========================================="); + console.log("MIP-R37: Moonriver Progressive Factor Reductions"); + console.log("===========================================\n"); + console.log("Summary: Reduce collateral factors from 15% to 7.5%"); + console.log("Markets: xcKSM, MOVR, FRAX\n"); + + console.log("Comptroller Address:", contracts.COMPTROLLER.address); + console.log("Governor Address:", contracts.GOVERNOR.address); + console.log(""); + + const marketsToUpdate = Object.values(contracts.MARKETS).filter( + m => MARKETS_TO_UPDATE.includes(m.assetTicker as any) + ); + + const proposalData = { + targets: [] as string[], + values: [] as number[], + signatures: [] as string[], + callDatas: [] as string[], + }; + + const newCFMantissa = cfPercentToMantissa(NEW_COLLATERAL_FACTOR); + + console.log("Actions:"); + console.log("---------"); + + for (const market of marketsToUpdate) { + const iface = comptroller.interface; + const fullCalldata = iface.encodeFunctionData('_setCollateralFactor', [ + market.mTokenAddress, + newCFMantissa + ]); + + const argsOnly = '0x' + fullCalldata.slice(10); + + proposalData.targets.push(contracts.COMPTROLLER.address); + proposalData.values.push(0); + proposalData.signatures.push('_setCollateralFactor(address,uint256)'); + proposalData.callDatas.push(argsOnly); + + console.log(`\n${market.assetTicker}:`); + console.log(` mToken Address: ${market.mTokenAddress}`); + console.log(` New CF: ${NEW_COLLATERAL_FACTOR}% (${newCFMantissa.toString()})`); + console.log(` Full Calldata: ${fullCalldata}`); + } + + console.log("\n\n==========================================="); + console.log("PROPOSAL DATA (for governance submission)"); + console.log("===========================================\n"); + + console.log("targets:", JSON.stringify(proposalData.targets, null, 2)); + console.log("\nvalues:", JSON.stringify(proposalData.values, null, 2)); + console.log("\nsignatures:", JSON.stringify(proposalData.signatures, null, 2)); + console.log("\ncallDatas:", JSON.stringify(proposalData.callDatas, null, 2)); + + // Generate full propose() calldata + const proposeCalldata = governor.interface.encodeFunctionData('propose', [ + proposalData.targets, + proposalData.values, + proposalData.signatures, + proposalData.callDatas, + PROPOSAL_DESCRIPTION + ]); + + console.log("\n==========================================="); + console.log("FULL PROPOSE CALLDATA (copy for MetaMask)"); + console.log("==========================================="); + console.log(`\nGovernor Address: ${contracts.GOVERNOR.address}`); + console.log(`Function: propose(address[],uint256[],string[],bytes[],string)`); + console.log("\n--- START CALLDATA ---"); + console.log(proposeCalldata); + console.log("--- END CALLDATA ---"); + + console.log("\n==========================================="); + console.log("SUMMARY"); + console.log("==========================================="); + console.log(`Total Actions: ${proposalData.targets.length}`); + console.log(`Markets: xcKSM, MOVR, FRAX`); + console.log(`New Collateral Factor: ${NEW_COLLATERAL_FACTOR}%`); + console.log(`New CF Mantissa: ${newCFMantissa.toString()}`); +} + +main().catch(console.error); diff --git a/test/apollo/mip-r37/vars.ts b/test/apollo/mip-r37/vars.ts new file mode 100644 index 0000000..52390f1 --- /dev/null +++ b/test/apollo/mip-r37/vars.ts @@ -0,0 +1,21 @@ +export * from '../base-vars' + +// Fork block - use a recent block +export const FORK_BLOCK = 14_100_000 + +// RPC URL - use environment variable or fallback to public RPC +export const RPC_URL = process.env.MOONRIVER_RPC_URL || 'https://rpc.api.moonriver.moonbeam.network' + +// Starting collateral factors (current on-chain values) +export const STARTING_COLLATERAL_FACTORS: { [market: string]: number } = { + 'xcKSM': 15, + 'MOVR': 15, + 'FRAX': 14, +} + +// Collateral factor changes - new collateral factors for each market +export const COLLATERAL_FACTOR_CHANGES: { [market: string]: number } = { + 'xcKSM': 7.5, + 'MOVR': 7.5, + 'FRAX': 7.5, +}