-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Market Reserve Automation #456
Open
ElliotFriedman
wants to merge
53
commits into
main
Choose a base branch
from
feat/market-reserve-automation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,715
−0
Open
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
8231339
init: market automation
ElliotFriedman 25df713
remove: reserve registry
ElliotFriedman b19b3ac
add: auction delay and guardian
ElliotFriedman 666b48d
test: auction delay and guardian
ElliotFriedman 8311c90
remove unneeded check from constructor
ElliotFriedman 8abac49
Merge branch 'main' of github.com:moonwell-fi/moonwell-contracts-v2 i…
ElliotFriedman 485cf8a
remove unused import
ElliotFriedman f241b98
end -> start time natspec
ElliotFriedman 148d6e2
naming: clarify variable and function names
ElliotFriedman b3b4db0
add: erc20 holding deposit integration test
ElliotFriedman 7671520
add: purchase failure tests + erc20 holding deposit tests
ElliotFriedman 27bbc5d
add: reserve automation integration tests
ElliotFriedman 162c355
add: reserve automation deploy script
ElliotFriedman bf2fd20
refactor init params
ElliotFriedman 6e33739
refactor constructor params, add additional checks on deployment
ElliotFriedman 310fb6b
refactor: make validate and deploy function external to ease testing
ElliotFriedman 7498cdb
make setup virtual for test
ElliotFriedman fd39fb2
add tests for newly deployed reserve automation contracts directly fr…
ElliotFriedman e51aed2
fix: change buffer updated time based on amount of buffer taken
ElliotFriedman 9ca8c21
add new integration test
ElliotFriedman 408375d
give mock erc20 token functionality
ElliotFriedman 56be0b6
fix error message
ElliotFriedman b1f3034
fix test issues
ElliotFriedman 6ecb3f6
increment timestamp to fix accrue interest error
ElliotFriedman 0f56033
increase wait time accruing interest
ElliotFriedman 3f2bf19
add new tests, make testSetup view
ElliotFriedman 06bdea2
remove sale start time check, allow guardian to cancel many times
ElliotFriedman 0105662
checkpoint: add premium and discount logic + other changes like cachi…
ElliotFriedman 8ed09c0
add _addReserves function
ElliotFriedman 4dc46f4
add fuzz test around discount application throughout period
ElliotFriedman de5f26b
fix discount, and start and end timestamps to be inclusive
ElliotFriedman 376098d
fmt
ElliotFriedman 61d64b2
fix failing tests
ElliotFriedman baa073f
uncomment code, fix deployment
ElliotFriedman 671026c
add period start and end tests as well as chainlink price caching
ElliotFriedman 635955c
check mToken and reserve asset match
ElliotFriedman 23d4861
organize import
ElliotFriedman f85ea52
cancel auction sends all funds back to mToken by adding reserves
ElliotFriedman 0eb75db
add tests around start and end time bounds, as well as moving through…
ElliotFriedman 81807c0
add price caching test
ElliotFriedman 6865e3c
Merge branch 'main' of github.com:moonwell-fi/moonwell-contracts-v2 i…
ElliotFriedman 0e0e65d
fix underlying mismatch
ElliotFriedman f5c8f84
add MWethDelegatorOwner
ElliotFriedman 693779e
address feedback, make internal function private, name mapping variables
ElliotFriedman 2057280
Merge pull request #466 from moonwell-fi/feat/mweth-owner
ElliotFriedman 40f3d94
address pr feedback
ElliotFriedman 84a8a1e
Merge branch 'main' into feat/market-reserve-automation
ElliotFriedman 2f39550
update saleinitiated function
ElliotFriedman 1a12311
test final second of final period
ElliotFriedman dd6ac45
Merge branch 'feat/market-reserve-automation' of github.com:moonwell-…
ElliotFriedman 605e4c1
update ci run
ElliotFriedman f4bc3ec
rename addresses -> _addresses
ElliotFriedman d40879e
make post proposal integration test, add _testUpperLowerBoundsPremium…
ElliotFriedman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[ | ||
"MOONWELL_USDC", | ||
"MOONWELL_USDBC", | ||
"MOONWELL_DAI", | ||
"MOONWELL_WETH", | ||
"MOONWELL_cbETH", | ||
"MOONWELL_wstETH", | ||
"MOONWELL_rETH", | ||
"MOONWELL_AERO", | ||
"MOONWELL_weETH", | ||
"MOONWELL_cbBTC", | ||
"MOONWELL_EURC", | ||
"MOONWELL_wrsETH" | ||
] |
285 changes: 285 additions & 0 deletions
285
proposals/mips/mip-reserve-automation/reserveAutomationDeploy.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,285 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity 0.8.19; | ||
|
||
import {ITransparentUpgradeableProxy} from "@openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
import {ProxyAdmin} from "@openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; | ||
import {Script} from "@forge-std/Script.sol"; | ||
import {Test} from "@forge-std/Test.sol"; | ||
import {console} from "@forge-std/console.sol"; | ||
|
||
import "@protocol/utils/ChainIds.sol"; | ||
|
||
import {AutomationDeploy} from "@protocol/market/AutomationDeploy.sol"; | ||
import {ReserveAutomation} from "@protocol/market/ReserveAutomation.sol"; | ||
import {ERC20HoldingDeposit} from "@protocol/market/ERC20HoldingDeposit.sol"; | ||
import {ChainIds, BASE_FORK_ID} from "@utils/ChainIds.sol"; | ||
import {AllChainAddresses as Addresses} from "@proposals/Addresses.sol"; | ||
|
||
/// how to run locally: | ||
/// forge script proposals/mips/mip-reserve-automation/reserveAutomationDeploy.sol:ReserveAutomationDeploy --rpc-url base | ||
contract ReserveAutomationDeploy is Script, Test { | ||
using ChainIds for uint256; | ||
|
||
/// @notice the name of the proposal | ||
string public constant NAME = "Reserve Automation Deployment"; | ||
|
||
AutomationDeploy private _deployer; | ||
Addresses internal _addresses; | ||
|
||
function setUp() public virtual { | ||
_addresses = new Addresses(); | ||
} | ||
|
||
/// @notice array of mToken names to deploy automation for | ||
function _getMTokens( | ||
uint256 chainId | ||
) internal view returns (string[] memory) { | ||
string memory file = vm.readFile( | ||
string.concat( | ||
"./proposals/mips/mip-reserve-automation/", | ||
string.concat(vm.toString(chainId), ".json") | ||
) | ||
); | ||
string[] memory mTokens = abi.decode(vm.parseJson(file), (string[])); | ||
return mTokens; | ||
} | ||
|
||
function run() public { | ||
vm.startBroadcast(); | ||
|
||
deploy(_addresses); | ||
|
||
vm.stopBroadcast(); | ||
|
||
_addresses.printAddresses(); | ||
|
||
validate(_addresses); | ||
} | ||
|
||
function deploy(Addresses addresses) public { | ||
address temporalGov = addresses.getAddress("TEMPORAL_GOVERNOR"); | ||
address pauseGuardian = addresses.getAddress("PAUSE_GUARDIAN"); | ||
address xWellProxy = addresses.getAddress("xWELL_PROXY"); | ||
|
||
_deployer = new AutomationDeploy(); | ||
|
||
/// Deploy ERC20HoldingDeposit for xWELL | ||
address holdingDeposit = _deployer.deployERC20HoldingDeposit( | ||
xWellProxy, | ||
temporalGov | ||
); | ||
|
||
addresses.addAddress("RESERVE_WELL_HOLDING_DEPOSIT", holdingDeposit); | ||
|
||
/// Deploy ReserveAutomation for each mToken | ||
string[] memory mTokens = _getMTokens(block.chainid); | ||
for (uint256 i = 0; i < mTokens.length; i++) { | ||
string memory mTokenName = mTokens[i]; | ||
string memory underlyingName = _getUnderlyingName(mTokenName); | ||
string memory oracleName = _getOracleName(underlyingName); | ||
string memory reserveAutomationIdentifier = string.concat( | ||
"RESERVE_AUTOMATION_", | ||
_stripMoonwellPrefix(mTokenName) | ||
); | ||
|
||
/// avoid redeploying a contract that already exists | ||
if (!addresses.isAddressSet(reserveAutomationIdentifier)) { | ||
ReserveAutomation.InitParams memory params = ReserveAutomation | ||
.InitParams({ | ||
recipientAddress: holdingDeposit, | ||
wellToken: xWellProxy, | ||
reserveAsset: addresses.getAddress(underlyingName), | ||
wellChainlinkFeed: addresses.getAddress( | ||
"CHAINLINK_WELL_USD" | ||
), | ||
reserveChainlinkFeed: addresses.getAddress(oracleName), | ||
owner: temporalGov, | ||
mTokenMarket: addresses.getAddress(mTokenName), | ||
guardian: pauseGuardian | ||
}); | ||
|
||
address automation = _deployer.deployReserveAutomation(params); | ||
|
||
addresses.addAddress(reserveAutomationIdentifier, automation); | ||
} | ||
} | ||
} | ||
|
||
function validate(Addresses addresses) public view { | ||
address temporalGov = addresses.getAddress("TEMPORAL_GOVERNOR"); | ||
address pauseGuardian = addresses.getAddress("PAUSE_GUARDIAN"); | ||
address xWellProxy = addresses.getAddress("xWELL_PROXY"); | ||
address holdingDeposit = addresses.getAddress( | ||
"RESERVE_WELL_HOLDING_DEPOSIT" | ||
); | ||
|
||
/// Validate ERC20HoldingDeposit | ||
assertEq( | ||
ERC20HoldingDeposit(holdingDeposit).token(), | ||
xWellProxy, | ||
"incorrect holding deposit token" | ||
); | ||
assertEq( | ||
ERC20HoldingDeposit(holdingDeposit).owner(), | ||
temporalGov, | ||
"incorrect holding deposit owner" | ||
); | ||
|
||
/// Validate ReserveAutomation for each mToken | ||
string[] memory mTokens = _getMTokens(block.chainid); | ||
for (uint256 i = 0; i < mTokens.length; i++) { | ||
string memory mTokenName = mTokens[i]; | ||
string memory underlyingName = _getUnderlyingName(mTokenName); | ||
string memory oracleName = _getOracleName(underlyingName); | ||
|
||
address automation = addresses.getAddress( | ||
string.concat( | ||
"RESERVE_AUTOMATION_", | ||
_stripMoonwellPrefix(mTokenName) | ||
) | ||
); | ||
|
||
ReserveAutomation reserve = ReserveAutomation(automation); | ||
|
||
assertEq( | ||
reserve.owner(), | ||
temporalGov, | ||
string.concat("incorrect owner for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.guardian(), | ||
pauseGuardian, | ||
string.concat("incorrect guardian for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.recipientAddress(), | ||
holdingDeposit, | ||
string.concat("incorrect recipient address for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.wellToken(), | ||
xWellProxy, | ||
string.concat("incorrect well token for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.reserveAsset(), | ||
addresses.getAddress(underlyingName), | ||
string.concat("incorrect reserve asset for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.wellChainlinkFeed(), | ||
addresses.getAddress("CHAINLINK_WELL_USD"), | ||
string.concat("incorrect well chainlink feed for ", mTokenName) | ||
); | ||
assertEq( | ||
reserve.reserveChainlinkFeed(), | ||
addresses.getAddress(oracleName), | ||
string.concat( | ||
"incorrect reserve chainlink feed for ", | ||
mTokenName | ||
) | ||
); | ||
assertEq( | ||
reserve.mTokenMarket(), | ||
addresses.getAddress(mTokenName), | ||
string.concat("incorrect mToken market for ", mTokenName) | ||
); | ||
} | ||
} | ||
|
||
/// @notice Helper function to get the underlying token name from an mToken name | ||
/// @param mTokenName The name of the mToken | ||
/// @return The name of the underlying token | ||
function _getUnderlyingName( | ||
string memory mTokenName | ||
) internal pure returns (string memory) { | ||
string memory token = _stripMoonwellPrefix(mTokenName); | ||
if (keccak256(bytes(token)) == keccak256(bytes("USDBC"))) { | ||
return "USDBC"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("USDC"))) { | ||
return "USDC"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("DAI"))) { | ||
return "DAI"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("WETH"))) { | ||
return "WETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("cbETH"))) { | ||
return "cbETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("wstETH"))) { | ||
return "wstETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("rETH"))) { | ||
return "rETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("AERO"))) { | ||
return "AERO"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("weETH"))) { | ||
return "weETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("cbBTC"))) { | ||
return "cbBTC"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("EURC"))) { | ||
return "EURC"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("wrsETH"))) { | ||
return "wrsETH"; | ||
} else if (keccak256(bytes(token)) == keccak256(bytes("WELL"))) { | ||
return "xWELL_PROXY"; | ||
} else { | ||
revert("unknown mToken"); | ||
} | ||
} | ||
|
||
/// @notice Helper function to get the oracle name for a token | ||
/// @param tokenName The name of the token | ||
/// @return The name of the oracle for the token | ||
function _getOracleName( | ||
string memory tokenName | ||
) internal pure returns (string memory) { | ||
if ( | ||
keccak256(bytes(tokenName)) == keccak256(bytes("USDBC")) || | ||
keccak256(bytes(tokenName)) == keccak256(bytes("USDC")) | ||
) { | ||
return "USDC_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("DAI"))) { | ||
return "DAI_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("WETH"))) { | ||
return "CHAINLINK_ETH_USD"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("cbETH"))) { | ||
return "cbETH_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("wstETH"))) { | ||
return "CHAINLINK_WSTETH_STETH_COMPOSITE_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("rETH"))) { | ||
return "CHAINLINK_RETH_ETH_COMPOSITE_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("AERO"))) { | ||
return "CHAINLINK_AERO_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("weETH"))) { | ||
return "CHAINLINK_WEETH_ETH_COMPOSITE_ORACLE"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("cbBTC"))) { | ||
return "CHAINLINK_BTC_USD"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("EURC"))) { | ||
return "CHAINLINK_EURC_USD"; | ||
} else if (keccak256(bytes(tokenName)) == keccak256(bytes("wrsETH"))) { | ||
return "CHAINLINK_wrsETH_COMPOSITE_ORACLE"; | ||
} else if ( | ||
keccak256(bytes(tokenName)) == keccak256(bytes("xWELL_PROXY")) | ||
) { | ||
return "CHAINLINK_WELL_USD"; | ||
} else { | ||
revert("unknown token"); | ||
} | ||
} | ||
|
||
/// @notice Helper function to strip the MOONWELL_ prefix from a token name | ||
/// @param mTokenName The name of the mToken | ||
/// @return The token name without the MOONWELL_ prefix | ||
function _stripMoonwellPrefix( | ||
string memory mTokenName | ||
) internal pure returns (string memory) { | ||
bytes memory mTokenBytes = bytes(mTokenName); | ||
bytes memory prefix = bytes("MOONWELL_"); | ||
require(mTokenBytes.length > prefix.length, "invalid mToken name"); | ||
|
||
bytes memory result = new bytes(mTokenBytes.length - prefix.length); | ||
for (uint256 i = prefix.length; i < mTokenBytes.length; i++) { | ||
result[i - prefix.length] = mTokenBytes[i]; | ||
} | ||
|
||
return string(result); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
pragma solidity =0.8.19; | ||
|
||
import {MErc20Storage} from "@protocol/MTokenInterfaces.sol"; | ||
import {ReserveAutomation} from "@protocol/market/ReserveAutomation.sol"; | ||
import {ERC20HoldingDeposit} from "@protocol/market/ERC20HoldingDeposit.sol"; | ||
|
||
contract AutomationDeploy { | ||
function deployReserveAutomation( | ||
ReserveAutomation.InitParams memory params | ||
) public returns (address) { | ||
require(params.wellToken.code.length > 0, "wellToken must be set"); | ||
require(params.reserveAsset.code.length > 0, "mToken must be set"); | ||
require( | ||
params.wellChainlinkFeed.code.length > 0, | ||
"wellChainlinkFeed must be set" | ||
); | ||
require( | ||
params.reserveChainlinkFeed.code.length > 0, | ||
"reserveChainlinkFeed must be set" | ||
); | ||
require( | ||
params.recipientAddress.code.length > 0, | ||
"recipientAddress must be set" | ||
); | ||
require( | ||
params.mTokenMarket.code.length > 0, | ||
"mTokenMarket must be set" | ||
); | ||
require( | ||
MErc20Storage(params.mTokenMarket).underlying() == | ||
params.reserveAsset, | ||
"reserveUnderlying must match mToken underlying" | ||
); | ||
require(params.owner != address(0), "owner must be set"); | ||
require(params.guardian != address(0), "guardian must be set"); | ||
|
||
ReserveAutomation automation = new ReserveAutomation(params); | ||
return address(automation); | ||
} | ||
|
||
function deployERC20HoldingDeposit( | ||
address token, | ||
address owner | ||
) public returns (address) { | ||
require(token.code.length > 0, "token must be set"); | ||
anajuliabit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return address(new ERC20HoldingDeposit(token, owner)); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
based on the current repo folder structure I believe it's makes more sense to keep this file inside /script and rename to DeployReserveAutomation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chainink oracle, market and underlying address names could be placed in a json config file so we could easily deploy the reserve automation to any chain