@@ -26,8 +26,12 @@ import {
2626/// @dev Markets get removed from the marketIds when the allocation is zero, but it doesn't mean that the adapter has
2727/// zero shares on the market.
2828/// @dev This adapter can only be used for markets with the adaptive curve irm.
29+ /// @dev Before adding the adapter to the vault, its timelocks must be properly set.
2930/// @dev Donated shares are lost forever.
3031///
32+ /// TIMELOCKS
33+ /// @dev The system is the same as the one used in VaultV2. Dev comments in VaultV2.sol on timelocks also apply here.
34+ ///
3135/// BURN SHARES
3236/// @dev When submitting burnShares, it's recommended to put the caps of the market to zero to avoid losing more.
3337/// @dev Burning shares takes time, so reactive depositors might be able to exit before the share price reduction.
@@ -46,18 +50,25 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 {
4650 bytes32 public immutable adapterId;
4751 address public immutable adaptiveCurveIrm;
4852
49- /* STORAGE */
53+ /* TIMELOCKS STORAGE */
54+
55+ mapping (bytes4 selector = > uint256 ) public timelock;
56+ mapping (bytes4 selector = > bool ) public abdicated;
57+ mapping (bytes data = > uint256 ) public executableAt;
58+
59+ /* OTHER STORAGE */
5060
5161 address public skimRecipient;
5262 bytes32 [] public marketIds;
5363 mapping (bytes32 marketId = > uint256 ) public supplyShares;
54- mapping (bytes32 marketId = > uint256 ) public burnSharesExecutableAt;
64+
65+ /* GETTERS */
5566
5667 function marketIdsLength () external view returns (uint256 ) {
5768 return marketIds.length ;
5869 }
5970
60- /* FUNCTIONS */
71+ /* CONSTRUCTOR */
6172
6273 constructor (address _parentVault , address _morpho , address _adaptiveCurveIrm ) {
6374 factory = msg .sender ;
@@ -70,53 +81,103 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 {
7081 SafeERC20Lib.safeApprove (asset, _parentVault, type (uint256 ).max);
7182 }
7283
73- function setSkimRecipient (address newSkimRecipient ) external {
74- require (msg .sender == IVaultV2 (parentVault).owner (), NotAuthorized ());
75- skimRecipient = newSkimRecipient;
76- emit SetSkimRecipient (newSkimRecipient);
77- }
84+ /* TIMELOCKS FUNCTIONS */
7885
79- /// @dev Skims the adapter's balance of `token` and sends it to `skimRecipient`.
80- /// @dev This is useful to handle rewards that the adapter has earned.
81- function skim (address token ) external {
82- require (msg .sender == skimRecipient, NotAuthorized ());
83- uint256 balance = IERC20 (token).balanceOf (address (this ));
84- SafeERC20Lib.safeTransfer (token, skimRecipient, balance);
85- emit Skim (token, balance);
86+ /// @dev Will revert if the timelock value is type(uint256).max or any value that overflows when added to the block
87+ /// timestamp.
88+ function submit (bytes calldata data ) external {
89+ require (msg .sender == IVaultV2 (parentVault).curator (), Unauthorized ());
90+ require (executableAt[data] == 0 , DataAlreadyPending ());
91+
92+ bytes4 selector = bytes4 (data);
93+ uint256 _timelock = selector == IMorphoMarketV1AdapterV2.decreaseTimelock.selector
94+ ? timelock[bytes4 (data[4 :8 ])]
95+ : timelock[selector];
96+ executableAt[data] = block .timestamp + _timelock;
97+ emit Submit (selector, data, executableAt[data]);
8698 }
8799
88- function submitBurnShares (bytes32 marketId ) external {
89- require (msg .sender == IVaultV2 (parentVault).curator (), NotAuthorized ());
90- require (burnSharesExecutableAt[marketId] == 0 , AlreadyPending ());
91- burnSharesExecutableAt[marketId] =
92- block .timestamp + IVaultV2 (parentVault).timelock (IVaultV2.removeAdapter.selector );
93- emit SubmitBurnShares (marketId, burnSharesExecutableAt[marketId]);
100+ function timelocked () internal {
101+ bytes4 selector = bytes4 (msg .data );
102+ require (executableAt[msg .data ] != 0 , DataNotTimelocked ());
103+ require (block .timestamp >= executableAt[msg .data ], TimelockNotExpired ());
104+ require (! abdicated[selector], Abdicated ());
105+ executableAt[msg .data ] = 0 ;
106+ emit Accept (selector, msg .data );
94107 }
95108
96- function revokeBurnShares ( bytes32 marketId ) external {
109+ function revoke ( bytes calldata data ) external {
97110 require (
98111 msg .sender == IVaultV2 (parentVault).curator () || IVaultV2 (parentVault).isSentinel (msg .sender ),
99- NotAuthorized ()
112+ Unauthorized ()
100113 );
101- require (burnSharesExecutableAt[marketId] != 0 , NotPending ());
102- burnSharesExecutableAt[marketId] = 0 ;
103- emit RevokeBurnShares (marketId);
114+ require (executableAt[data] != 0 , DataNotTimelocked ());
115+ executableAt[data] = 0 ;
116+ bytes4 selector = bytes4 (data);
117+ emit Revoke (msg .sender , selector, data);
118+ }
119+
120+ /* CURATOR FUNCTIONS */
121+
122+ /// @dev This function requires great caution because it can irreversibly disable submit for a selector.
123+ /// @dev Existing pending operations submitted before increasing a timelock can still be executed at the initial
124+ /// executableAt.
125+ function increaseTimelock (bytes4 selector , uint256 newDuration ) external {
126+ timelocked ();
127+ require (selector != IMorphoMarketV1AdapterV2.decreaseTimelock.selector , AutomaticallyTimelocked ());
128+ require (newDuration >= timelock[selector], TimelockNotIncreasing ());
129+
130+ timelock[selector] = newDuration;
131+ emit IncreaseTimelock (selector, newDuration);
132+ }
133+
134+ function decreaseTimelock (bytes4 selector , uint256 newDuration ) external {
135+ timelocked ();
136+ require (selector != IMorphoMarketV1AdapterV2.decreaseTimelock.selector , AutomaticallyTimelocked ());
137+ require (newDuration <= timelock[selector], TimelockNotDecreasing ());
138+
139+ timelock[selector] = newDuration;
140+ emit DecreaseTimelock (selector, newDuration);
141+ }
142+
143+ /// @dev This function requires great caution because it will irreversibly disable submit for a selector.
144+ /// @dev Existing pending operations submitted before increasing a timelock can not be executed at the initial
145+ /// executableAt.
146+ function abdicate (bytes4 selector ) external {
147+ timelocked ();
148+ abdicated[selector] = true ;
149+ emit Abdicate (selector);
150+ }
151+
152+ function setSkimRecipient (address newSkimRecipient ) external {
153+ timelocked ();
154+ skimRecipient = newSkimRecipient;
155+ emit SetSkimRecipient (newSkimRecipient);
104156 }
105157
106158 /// @dev Deallocate 0 from the vault after burning shares to update the allocation there.
107159 function burnShares (bytes32 marketId ) external {
108- require (burnSharesExecutableAt[marketId] != 0 , NotTimelocked ());
109- require (block .timestamp >= burnSharesExecutableAt[marketId], TimelockNotExpired ());
110- burnSharesExecutableAt[marketId] = 0 ;
160+ timelocked ();
111161 uint256 supplySharesBefore = supplyShares[marketId];
112162 supplyShares[marketId] = 0 ;
113163 emit BurnShares (marketId, supplySharesBefore);
114164 }
115165
166+ /* OTHER FUNCTIONS */
167+
168+ /// @dev Skims the adapter's balance of `token` and sends it to `skimRecipient`.
169+ /// @dev This is useful to handle rewards that the adapter has earned.
170+ function skim (address token ) external {
171+ require (msg .sender == skimRecipient, Unauthorized ());
172+ uint256 balance = IERC20 (token).balanceOf (address (this ));
173+ SafeERC20Lib.safeTransfer (token, skimRecipient, balance);
174+ emit Skim (token, balance);
175+ }
176+
116177 /// @dev Returns the ids of the allocation and the change in allocation.
117178 function allocate (bytes memory data , uint256 assets , bytes4 , address ) external returns (bytes32 [] memory , int256 ) {
118179 MarketParams memory marketParams = abi.decode (data, (MarketParams));
119- require (msg .sender == parentVault, NotAuthorized ());
180+ require (msg .sender == parentVault, Unauthorized ());
120181 require (marketParams.loanToken == asset, LoanAssetMismatch ());
121182 require (marketParams.irm == adaptiveCurveIrm, IrmMismatch ());
122183 bytes32 marketId = Id.unwrap (marketParams.id ());
@@ -145,7 +206,7 @@ contract MorphoMarketV1AdapterV2 is IMorphoMarketV1AdapterV2 {
145206 returns (bytes32 [] memory , int256 )
146207 {
147208 MarketParams memory marketParams = abi.decode (data, (MarketParams));
148- require (msg .sender == parentVault, NotAuthorized ());
209+ require (msg .sender == parentVault, Unauthorized ());
149210 require (marketParams.loanToken == asset, LoanAssetMismatch ());
150211 require (marketParams.irm == adaptiveCurveIrm, IrmMismatch ());
151212 bytes32 marketId = Id.unwrap (marketParams.id ());
0 commit comments