-
Notifications
You must be signed in to change notification settings - Fork 54
refactor timelocks 2 #842
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
refactor timelocks 2 #842
Changes from all commits
988ccbb
0422e96
435fa50
ffd7285
da75aa4
19395f3
d4673d6
b63b776
5f558d9
0f75fc5
b97dd52
7d7690e
c974403
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ import { | |
| /// @dev Markets get removed from the marketIds when the allocation is zero, but it doesn't mean that the adapter has | ||
| /// zero shares on the market. | ||
| /// @dev This adapter can only be used for markets with the adaptive curve irm. | ||
| /// @dev Before adding the adapter to the vault, its timelocks must be properly set. | ||
| /// @dev Donated shares are lost forever. | ||
| /// | ||
| /// BURN SHARES | ||
|
|
@@ -51,13 +52,12 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 { | |
| address public skimRecipient; | ||
| bytes32[] public marketIds; | ||
| mapping(bytes32 marketId => uint256) public supplyShares; | ||
| mapping(bytes32 marketId => uint256) public burnSharesExecutableAt; | ||
|
|
||
| function marketIdsLength() external view returns (uint256) { | ||
| return marketIds.length; | ||
| } | ||
|
|
||
| /* FUNCTIONS */ | ||
| /* CONSTRUCTOR */ | ||
|
|
||
| constructor(address _parentVault, address _morpho, address _adaptiveCurveIrm) { | ||
| factory = msg.sender; | ||
|
|
@@ -70,53 +70,49 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 { | |
| SafeERC20Lib.safeApprove(asset, _parentVault, type(uint256).max); | ||
| } | ||
|
|
||
| function setSkimRecipient(address newSkimRecipient) external { | ||
| require(msg.sender == IVaultV2(parentVault).owner(), NotAuthorized()); | ||
| skimRecipient = newSkimRecipient; | ||
| emit SetSkimRecipient(newSkimRecipient); | ||
| } | ||
| /* TIMELOCKS */ | ||
|
|
||
| /// @dev Skims the adapter's balance of `token` and sends it to `skimRecipient`. | ||
| /// @dev This is useful to handle rewards that the adapter has earned. | ||
| function skim(address token) external { | ||
| require(msg.sender == skimRecipient, NotAuthorized()); | ||
| uint256 balance = IERC20(token).balanceOf(address(this)); | ||
| SafeERC20Lib.safeTransfer(token, skimRecipient, balance); | ||
| emit Skim(token, balance); | ||
| function timelocked() internal { | ||
| uint256 executableAt = IVaultV2(parentVault).executableAt(msg.data); | ||
| require(executableAt != 0, DataNotTimelocked()); | ||
| require(block.timestamp >= executableAt, TimelockNotExpired()); | ||
| IVaultV2(parentVault).revoke(msg.data); | ||
| emit Accept(bytes4(msg.data), msg.data); | ||
|
Comment on lines
+78
to
80
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here it also emits the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree that's misleading. but on the other side you gain that you don't need to index the adapter's storage |
||
| } | ||
|
|
||
| function submitBurnShares(bytes32 marketId) external { | ||
| require(msg.sender == IVaultV2(parentVault).curator(), NotAuthorized()); | ||
| require(burnSharesExecutableAt[marketId] == 0, AlreadyPending()); | ||
| burnSharesExecutableAt[marketId] = | ||
| block.timestamp + IVaultV2(parentVault).timelock(IVaultV2.removeAdapter.selector); | ||
| emit SubmitBurnShares(marketId, burnSharesExecutableAt[marketId]); | ||
| } | ||
| /* TIMELOCKED FUNCTIONS */ | ||
|
|
||
| function revokeBurnShares(bytes32 marketId) external { | ||
| require( | ||
| msg.sender == IVaultV2(parentVault).curator() || IVaultV2(parentVault).isSentinel(msg.sender), | ||
| NotAuthorized() | ||
| ); | ||
| require(burnSharesExecutableAt[marketId] != 0, NotPending()); | ||
| burnSharesExecutableAt[marketId] = 0; | ||
| emit RevokeBurnShares(marketId); | ||
| /// @dev Function name to avoid selector clash with other adapters. | ||
| function morphoMarketV1AdapterV2SetSkimRecipient(address newSkimRecipient) external { | ||
| timelocked(); | ||
| skimRecipient = newSkimRecipient; | ||
| emit SetSkimRecipient(newSkimRecipient); | ||
| } | ||
|
|
||
| /// @dev Function name to avoid selector clash with other adapters. | ||
| /// @dev Deallocate 0 from the vault after burning shares to update the allocation there. | ||
| function burnShares(bytes32 marketId) external { | ||
| require(burnSharesExecutableAt[marketId] != 0, NotTimelocked()); | ||
| require(block.timestamp >= burnSharesExecutableAt[marketId], TimelockNotExpired()); | ||
| burnSharesExecutableAt[marketId] = 0; | ||
| function morphoMarketV1AdapterV2BurnShares(bytes32 marketId) external { | ||
| timelocked(); | ||
| uint256 supplySharesBefore = supplyShares[marketId]; | ||
| supplyShares[marketId] = 0; | ||
| emit BurnShares(marketId, supplySharesBefore); | ||
| } | ||
|
|
||
| /* OTHER FUNCTIONS */ | ||
|
|
||
| /// @dev Skims the adapter's balance of `token` and sends it to `skimRecipient`. | ||
| /// @dev This is useful to handle rewards that the adapter has earned. | ||
| function skim(address token) external { | ||
| require(msg.sender == skimRecipient, Unauthorized()); | ||
| uint256 balance = IERC20(token).balanceOf(address(this)); | ||
| SafeERC20Lib.safeTransfer(token, skimRecipient, balance); | ||
| emit Skim(token, balance); | ||
| } | ||
|
|
||
| /// @dev Returns the ids of the allocation and the change in allocation. | ||
| function allocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) { | ||
| MarketParams memory marketParams = abi.decode(data, (MarketParams)); | ||
| require(msg.sender == parentVault, NotAuthorized()); | ||
| require(msg.sender == parentVault, Unauthorized()); | ||
| require(marketParams.loanToken == asset, LoanAssetMismatch()); | ||
| require(marketParams.irm == adaptiveCurveIrm, IrmMismatch()); | ||
| bytes32 marketId = Id.unwrap(marketParams.id()); | ||
|
|
@@ -145,7 +141,7 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 { | |
| returns (bytes32[] memory, int256) | ||
| { | ||
| MarketParams memory marketParams = abi.decode(data, (MarketParams)); | ||
| require(msg.sender == parentVault, NotAuthorized()); | ||
| require(msg.sender == parentVault, Unauthorized()); | ||
| require(marketParams.loanToken == asset, LoanAssetMismatch()); | ||
| require(marketParams.irm == adaptiveCurveIrm, IrmMismatch()); | ||
| bytes32 marketId = Id.unwrap(marketParams.id()); | ||
|
|
||
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.
Timelocked adapter calls now clear the vault timelock via
IVaultV2(parentVault).revoke(msg.data)(lines 66‑70), butVaultV2.revokeonly allows the curator or a sentinel to call it (seeVaultV2.sollines 362‑364). When the curator executessetSkimRecipientorburnSharesafter submitting them through the vault, the adapter performs this revoke as itself and will hitUnauthorized()unless the adapter was pre-added as a sentinel (not the default). That bricks all timelocked adapter actions in production unless an extra sentinel configuration step is done.Useful? React with 👍 / 👎.