Skip to content
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

Update post-cantina #349

Merged
merged 6 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 39 additions & 69 deletions src/MetaMorpho.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import {IMorphoMarketParams} from "./interfaces/IMorphoMarketParams.sol";
import {
MarketConfig,
PendingUint192,
PendingAddress,
MarketAllocation,
IMetaMorphoBase,
IMetaMorphoStaticTyping
} from "./interfaces/IMetaMorpho.sol";
import {Id, MarketParams, Market, IMorpho} from "../lib/morpho-blue/src/interfaces/IMorpho.sol";
Expand Down Expand Up @@ -55,55 +55,51 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* IMMUTABLES */

/// @notice The address of the Morpho contract.
/// @inheritdoc IMetaMorphoBase
IMorpho public immutable MORPHO;

/* STORAGE */

/// @notice The address of the curator.
/// @inheritdoc IMetaMorphoBase
address public curator;

/// @notice Stores whether an address is an allocator or not.
/// @inheritdoc IMetaMorphoBase
mapping(address => bool) public isAllocator;

/// @notice The current guardian. Can be set even without the timelock set.
/// @inheritdoc IMetaMorphoBase
address public guardian;

/// @notice Stores the current configuration of each market.
/// @inheritdoc IMetaMorphoStaticTyping
mapping(Id => MarketConfig) public config;

/// @notice The current timelock.
/// @inheritdoc IMetaMorphoBase
uint256 public timelock;

/// @notice The pending guardian.
/// @inheritdoc IMetaMorphoStaticTyping
PendingAddress public pendingGuardian;

/// @notice Stores the pending cap for each market.
/// @inheritdoc IMetaMorphoStaticTyping
mapping(Id => PendingUint192) public pendingCap;

/// @notice The pending timelock.
/// @inheritdoc IMetaMorphoStaticTyping
PendingUint192 public pendingTimelock;

/// @notice The current fee.
/// @inheritdoc IMetaMorphoBase
uint96 public fee;

/// @notice The fee recipient.
/// @inheritdoc IMetaMorphoBase
address public feeRecipient;

/// @notice The skim recipient.
/// @inheritdoc IMetaMorphoBase
address public skimRecipient;

/// @dev Stores the order of markets on which liquidity is supplied upon deposit.
/// @dev Can contain any market. A market is skipped as soon as its supply cap is reached.
/// @inheritdoc IMetaMorphoBase
Id[] public supplyQueue;

/// @dev Stores the order of markets from which liquidity is withdrawn upon withdrawal.
/// @dev Always contain all non-zero cap markets as well as all markets on which the vault supplies liquidity,
/// without duplicate.
/// @inheritdoc IMetaMorphoBase
Id[] public withdrawQueue;

/// @notice Stores the total assets managed by this vault when the fee was last accrued.
/// @dev May be a little off `totalAssets()` after each interaction, due to some roundings.
/// @inheritdoc IMetaMorphoBase
uint256 public lastTotalAssets;

/* CONSTRUCTOR */
Expand Down Expand Up @@ -182,7 +178,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* ONLY OWNER FUNCTIONS */

/// @notice Sets `curator` to `newCurator`.
/// @inheritdoc IMetaMorphoBase
function setCurator(address newCurator) external onlyOwner {
if (newCurator == curator) revert ErrorsLib.AlreadySet();

Expand All @@ -191,7 +187,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetCurator(newCurator);
}

/// @notice Sets `newAllocator` as an allocator or not (`newIsAllocator`).
/// @inheritdoc IMetaMorphoBase
function setIsAllocator(address newAllocator, bool newIsAllocator) external onlyOwner {
if (isAllocator[newAllocator] == newIsAllocator) revert ErrorsLib.AlreadySet();

Expand All @@ -200,7 +196,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetIsAllocator(newAllocator, newIsAllocator);
}

/// @notice Sets `skimRecipient` to `newSkimRecipient`.
/// @inheritdoc IMetaMorphoBase
function setSkimRecipient(address newSkimRecipient) external onlyOwner {
if (newSkimRecipient == skimRecipient) revert ErrorsLib.AlreadySet();

Expand All @@ -209,9 +205,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetSkimRecipient(newSkimRecipient);
}

/// @notice Submits a `newTimelock`.
/// @dev In case the new timelock is higher than the current one, the timelock is set immediately.
/// @dev Warning: Submitting a timelock will overwrite the current pending timelock.
/// @inheritdoc IMetaMorphoBase
function submitTimelock(uint256 newTimelock) external onlyOwner {
if (newTimelock == timelock) revert ErrorsLib.AlreadySet();
_checkTimelockBounds(newTimelock);
Expand All @@ -229,7 +223,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
}
}

/// @notice Sets the `fee` to `newFee`.
/// @inheritdoc IMetaMorphoBase
function setFee(uint256 newFee) external onlyOwner {
if (newFee == fee) revert ErrorsLib.AlreadySet();
if (newFee > ConstantsLib.MAX_FEE) revert ErrorsLib.MaxFeeExceeded();
Expand All @@ -244,7 +238,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetFee(_msgSender(), fee);
}

/// @notice Sets `feeRecipient` to `newFeeRecipient`.
/// @inheritdoc IMetaMorphoBase
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
if (newFeeRecipient == feeRecipient) revert ErrorsLib.AlreadySet();
if (newFeeRecipient == address(0) && fee != 0) revert ErrorsLib.ZeroFeeRecipient();
Expand All @@ -257,11 +251,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetFeeRecipient(newFeeRecipient);
}

/// @notice Submits a `newGuardian`.
/// @notice Warning: a malicious guardian could disrupt the vault's operation, and would have the power to revoke
/// any pending guardian.
/// @dev In case there is no guardian, the gardian is set immediately.
/// @dev Warning: Submitting a gardian will overwrite the current pending gardian.
/// @inheritdoc IMetaMorphoBase
function submitGuardian(address newGuardian) external onlyOwner {
if (newGuardian == guardian) revert ErrorsLib.AlreadySet();

Expand All @@ -280,9 +270,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* ONLY CURATOR FUNCTIONS */

/// @notice Submits a `newSupplyCap` for the market defined by `marketParams`.
/// @dev In case the new cap is lower than the current one, the cap is set immediately.
/// @dev Warning: Submitting a cap will overwrite the current pending cap.
/// @inheritdoc IMetaMorphoBase
function submitCap(MarketParams memory marketParams, uint256 newSupplyCap) external onlyCuratorRole {
Id id = marketParams.id();
if (marketParams.loanToken != asset()) revert ErrorsLib.InconsistentAsset(id);
Expand All @@ -303,8 +291,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
}
}

/// @notice Submits a forced market removal from the vault, eventually losing all funds supplied to the market.
/// @dev Warning: Submitting a forced removal will overwrite the timestamp at which the market will be removable.
/// @inheritdoc IMetaMorphoBase
function submitMarketRemoval(Id id) external onlyCuratorRole {
if (config[id].removableAt != 0) revert ErrorsLib.AlreadySet();
if (!config[id].enabled) revert ErrorsLib.MarketNotEnabled();
Expand All @@ -319,9 +306,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* ONLY ALLOCATOR FUNCTIONS */

/// @notice Sets `supplyQueue` to `newSupplyQueue`.
/// @param newSupplyQueue is an array of enabled markets, and can contain duplicate markets, but it would only
/// increase the cost of depositing to the vault.
/// @inheritdoc IMetaMorphoBase
function setSupplyQueue(Id[] calldata newSupplyQueue) external onlyAllocatorRole {
uint256 length = newSupplyQueue.length;

Expand All @@ -336,13 +321,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetSupplyQueue(_msgSender(), newSupplyQueue);
}

/// @notice Sets the withdraw queue as a permutation of the previous one, although markets with both zero cap and
/// zero vault's supply can be removed from the permutation.
/// @notice This is the only entry point to disable a market.
/// @notice Removing a market requires the vault to have 0 supply on it; but anyone can supply on behalf of the
/// vault so the call to `sortWithdrawQueue` can be griefed by a frontrun. To circumvent this, the allocator can
/// simply bundle a reallocation that withdraws max from this market with a call to `sortWithdrawQueue`.
/// @param indexes The indexes of each market in the previous withdraw queue, in the new withdraw queue's order.
/// @inheritdoc IMetaMorphoBase
function updateWithdrawQueue(uint256[] calldata indexes) external onlyAllocatorRole {
uint256 newLength = indexes.length;
uint256 currLength = withdrawQueue.length;
Expand Down Expand Up @@ -384,16 +363,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetWithdrawQueue(_msgSender(), newWithdrawQueue);
}

/// @notice Reallocates the vault's liquidity so as to reach a given allocation of assets on each given market.
/// @notice The allocator can withdraw from any market, even if it's not in the withdraw queue, as long as the loan
/// token of the market is the same as the vault's asset.
/// @dev The behavior of the reallocation can be altered by state changes, including:
/// - Deposits on the vault that supplies to markets that are expected to be supplied to during reallocation.
/// - Withdrawals from the vault that withdraws from markets that are expected to be withdrawn from during
/// reallocation.
/// - Donations to the vault on markets that are expected to be supplied to during reallocation.
/// - Withdrawals from markets that are expected to be withdrawn from during reallocation.
/// @dev Any additional liquidity withdrawn during reallocation will be kept idle.
/// @inheritdoc IMetaMorphoBase
function reallocate(MarketAllocation[] calldata allocations) external onlyAllocatorRole {
uint256 totalSupplied;
uint256 totalWithdrawn;
Expand Down Expand Up @@ -447,7 +417,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* REVOKE FUNCTIONS */

/// @notice Revokes the pending timelock.
/// @inheritdoc IMetaMorphoBase
function revokePendingTimelock() external onlyGuardianRole {
if (pendingTimelock.validAt == 0) revert ErrorsLib.NoPendingValue();

Expand All @@ -456,7 +426,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.RevokePendingTimelock(_msgSender());
}

/// @notice Revokes the pending guardian.
/// @inheritdoc IMetaMorphoBase
function revokePendingGuardian() external onlyGuardianRole {
if (pendingGuardian.validAt == 0) revert ErrorsLib.NoPendingValue();

Expand All @@ -465,7 +435,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.RevokePendingGuardian(_msgSender());
}

/// @notice Revokes the pending cap of the market defined by `id`.
/// @inheritdoc IMetaMorphoBase
function revokePendingCap(Id id) external onlyCuratorOrGuardianRole {
if (pendingCap[id].validAt == 0) revert ErrorsLib.NoPendingValue();

Expand All @@ -474,7 +444,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.RevokePendingCap(_msgSender(), id);
}

/// @notice Revokes the pending removal of the market defined by `id`.
/// @inheritdoc IMetaMorphoBase
function revokePendingMarketRemoval(Id id) external onlyCuratorOrGuardianRole {
delete config[id].removableAt;

Expand All @@ -483,33 +453,33 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/* EXTERNAL */

/// @notice Returns the length of the supply queue.
/// @inheritdoc IMetaMorphoBase
function supplyQueueLength() external view returns (uint256) {
return supplyQueue.length;
}

/// @notice Returns the length of the withdraw queue.
/// @inheritdoc IMetaMorphoBase
function withdrawQueueLength() external view returns (uint256) {
return withdrawQueue.length;
}

/// @notice Accepts the pending timelock.
/// @inheritdoc IMetaMorphoBase
function acceptTimelock() external afterTimelock(pendingTimelock.validAt) {
_setTimelock(pendingTimelock.value);
}

/// @notice Accepts the pending guardian.
/// @inheritdoc IMetaMorphoBase
function acceptGuardian() external afterTimelock(pendingGuardian.validAt) {
_setGuardian(pendingGuardian.value);
}

/// @notice Accepts the pending cap of the market defined by `id`.
/// @inheritdoc IMetaMorphoBase
function acceptCap(Id id) external afterTimelock(pendingCap[id].validAt) {
// Safe "unchecked" cast because pendingCap <= type(uint184).max.
_setCap(id, uint184(pendingCap[id].value));
}

/// @notice Skims the vault `token` balance to `skimRecipient`.
/// @inheritdoc IMetaMorphoBase
function skim(address token) external {
if (skimRecipient == address(0)) revert ErrorsLib.ZeroAddress();

Expand Down Expand Up @@ -721,7 +691,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

/// @dev Returns the market params of the market defined by `id`.
function _marketParams(Id id) internal view returns (MarketParams memory) {
return IMorphoMarketParams(address(MORPHO)).idToMarketParams(id);
return MORPHO.idToMarketParams(id);
}

/// @dev Accrues interest on Morpho Blue and returns the vault's assets & corresponding shares supplied on the
Expand Down
22 changes: 10 additions & 12 deletions src/MetaMorphoFactory.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import {IMetaMorpho} from "./interfaces/IMetaMorpho.sol";
import {IMetaMorphoFactory} from "./interfaces/IMetaMorphoFactory.sol";

import {EventsLib} from "./libraries/EventsLib.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";

Expand All @@ -10,15 +13,15 @@ import {MetaMorpho} from "./MetaMorpho.sol";
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice This contract allows to create MetaMorpho vaults, and to index them easily.
contract MetaMorphoFactory {
contract MetaMorphoFactory is IMetaMorphoFactory {
/* IMMUTABLES */

/// @notice The address of the Morpho contract.
/// @inheritdoc IMetaMorphoFactory
address public immutable MORPHO;

/* STORAGE */

/// @notice Whether a MetaMorpho vault was created with the factory.
/// @inheritdoc IMetaMorphoFactory
mapping(address => bool) public isMetaMorpho;

/* CONSTRUCTOR */
Expand All @@ -33,22 +36,17 @@ contract MetaMorphoFactory {

/* EXTERNAL */

/// @notice Creates a new MetaMorpho vault.
/// @param initialOwner The owner of the vault.
/// @param initialTimelock The initial timelock of the vault.
/// @param asset The address of the underlying asset.
/// @param name The name of the vault.
/// @param symbol The symbol of the vault.
/// @param salt The salt to use for the MetaMorpho vault's CREATE2 address.
/// @inheritdoc IMetaMorphoFactory
function createMetaMorpho(
address initialOwner,
uint256 initialTimelock,
address asset,
string memory name,
string memory symbol,
bytes32 salt
) external returns (MetaMorpho metaMorpho) {
metaMorpho = new MetaMorpho{salt: salt}(initialOwner, MORPHO, initialTimelock, asset, name, symbol);
) external returns (IMetaMorpho metaMorpho) {
metaMorpho =
IMetaMorpho(address(new MetaMorpho{salt: salt}(initialOwner, MORPHO, initialTimelock, asset, name, symbol)));

isMetaMorpho[address(metaMorpho)] = true;

Expand Down
Loading
Loading