diff --git a/README.md b/README.md index 0e4aea77..373af883 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ MetaMorpho is a protocol for noncustodial risk management on top of [Morpho Blue It enables anyone to create a vault depositing liquidity into multiple Morpho Blue markets. It offers a seamless experience similar to Aave and Compound. -Users of MetaMorpho are liquidity providers that want to earn from borrowing interest without having to actively manage the risk of their position. +Users of MetaMorpho are liquidity providers who want to earn from borrowing interest without having to actively manage the risk of their position. The active management of the deposited assets is the responsibility of a set of different roles (owner, curator and allocators). These roles are primarily responsible for enabling and disabling markets on Morpho Blue and managing the allocation of users’ funds. @@ -182,6 +182,50 @@ If one of the allocators starts setting the withdraw queue and/or supply queue t ## Getting Started +### Package installation + +```bash +npm install @morpho-org/metamorpho +``` + +```bash +yarn add @morpho-org/metamorpho +``` + +### Usage + +Bundle a supply cap raise and a reallocation to the market: + +```typescript +import { MetaMorphoAction } from "@morpho-org/metamorpho"; + +const marketParams1 = { + collateralToken: "0x...", + loanToken: "0x...", + irm: "0x...", + oracle: "0x...", + lltv: 86_0000000000000000n, +}; + +const marketParams2 = { + collateralToken: "0x...", + loanToken: marketParams1.loanToken, + irm: "0x...", + oracle: "0x...", + lltv: 96_5000000000000000n, +}; + +await metamorpho.connect(curator).multicall([ + MetaMorphoAction.acceptCap(marketParams2), + MetaMorphoAction.reallocate([ + { marketParams: marketParams1, assets: 600_000000000000000000n }, + { marketParams: marketParams2, assets: 100_000000000000000000n }, + ]), +]); +``` + +## Development + Install dependencies: `yarn` Run forge tests: `yarn test:forge` diff --git a/certora/README.md b/certora/README.md index c2156b68..89a11274 100644 --- a/certora/README.md +++ b/certora/README.md @@ -26,7 +26,7 @@ certoraRun certora/confs/Range.conf --rule timelockInRange # High-level description A MetaMorpho vault is an ERC4626 vault that defines a list of Morpho Blue markets to allocate its funds. -See [`README.md`](../README.md) for a in depth description of MetaMorpho. +See [`README.md`](../README.md) for an in depth description of MetaMorpho. ## ERC20 tokens and transfers @@ -38,7 +38,7 @@ The verification is done for the most common ERC20 implementations, for which we - [ERC20Standard](dispatch/ERC20Standard.sol) which respects the standard and reverts in case of insufficient funds or in case of insufficient allowance. - [ERC20NoRevert](dispatch/ERC20NoRevert.sol) which respects the standard but does not revert (and returns false instead). -- [ERC20USDT](dispatch/ERC20USDT.sol) which does not strictly respects the standard because it omits the return value of the `transfer` and `transferFrom` functions. +- [ERC20USDT](dispatch/ERC20USDT.sol) which does not strictly respect the standard because it omits the return value of the `transfer` and `transferFrom` functions. ## Roles @@ -90,7 +90,7 @@ Note also that these properties are ensured to always hold, because the contract ### Consistent asset -For deposit and withdraw, it is checked that markets have a the same loan token as the loan token of the vault. +For deposit and withdraw, it is checked that markets have the same loan token as the loan token of the vault. We say that such market has a consistent asset. The following function defined in [`ConsistentState.spec`](specs/ConsistentState.spec) is verified to always return `true` and contributes to verifying the property above. @@ -120,7 +120,7 @@ function isInWithdrawQueueIsEnabled(uint256 i) returns bool { } ``` -Thorough the code of MetaMorpho, this enabled flag is checked to be set to true, which is an efficient check that does not require to go through the whole withdraw queue. +Throughout the code of MetaMorpho, this enabled flag is checked to be set to true, which is an efficient check that does not require to go through the whole withdraw queue. This check also ensures that such markets have some properties, since verifying those are necessary to be added to the withdraw queue. For example, markets added to the withdraw queue necessarily have a consistent asset. @@ -143,11 +143,11 @@ rule newSupplyQueueEnsuresPositiveCap(env e, MetaMorphoHarness.Id[] newSupplyQue The previous rule ensures that when setting a new supply queue, each market has a positive supply cap. Markets can have a positive supply cap only if they are created on Morpho Blue. -We can prove then that each market of the supply queue has been created on Morpho, because if a market has ben created it cannot be "destroyed" later. +We can prove then that each market of the supply queue has been created on Morpho, because if a market has been created it cannot be "destroyed" later. ## Liveness -The liveness properties ensures that some crucial actions cannot be blocked. +The liveness properties ensure that some crucial actions cannot be blocked. It is notably useful to show that in case of an emergency, it is still possible to make salvaging transactions. The `canPauseSupply` rule in [`Liveness.spec`](specs/Liveness.spec) shows that it is always possible to prevent new deposits. This is done by first setting the supply queue as the empty queue and checking that it does not revert: diff --git a/certora/confs/ConsistentState.conf b/certora/confs/ConsistentState.conf index 5da45284..4d064514 100644 --- a/certora/confs/ConsistentState.conf +++ b/certora/confs/ConsistentState.conf @@ -10,11 +10,7 @@ "verify": "MetaMorphoHarness:certora/specs/ConsistentState.spec", "loop_iter": "2", "optimistic_loop": true, - "prover_args": [ - "-depth 3", - "-mediumTimeout 20", - "-timeout 300", - ], + "smt_timeout": "600", "rule_sanity": "basic", "server": "production", "msg": "MetaMorpho Consistent State" diff --git a/certora/confs/Enabled.conf b/certora/confs/Enabled.conf index 650f7489..703bacd5 100644 --- a/certora/confs/Enabled.conf +++ b/certora/confs/Enabled.conf @@ -11,7 +11,9 @@ "-depth 3", "-mediumTimeout 20", "-timeout 1000", + "-smt_easy_LIA true", ], + "smt_timeout": "7000", "rule_sanity": "basic", "server": "production", "msg": "MetaMorpho Enabled" diff --git a/certora/confs/Liveness.conf b/certora/confs/Liveness.conf index ed583baf..f391344c 100644 --- a/certora/confs/Liveness.conf +++ b/certora/confs/Liveness.conf @@ -16,10 +16,9 @@ "loop_iter": "2", "optimistic_loop": true, "prover_args": [ - "-depth 3", - "-mediumTimeout 20", - "-timeout 120", + "-smt_easy_LIA true", ], + "smt_timeout": "7000", "rule_sanity": "basic", "server": "production", "msg": "MetaMorpho Liveness" diff --git a/certora/confs/Range.conf b/certora/confs/Range.conf index 383bcc8d..13b869d3 100644 --- a/certora/confs/Range.conf +++ b/certora/confs/Range.conf @@ -7,9 +7,11 @@ "loop_iter": "2", "optimistic_loop": true, "prover_args": [ - "-depth 3", - "-mediumTimeout 20", + "-depth 0", "-timeout 300", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", ], "rule_sanity": "basic", "server": "production", diff --git a/certora/specs/ConsistentState.spec b/certora/specs/ConsistentState.spec index 7e825094..fd2a7c15 100644 --- a/certora/specs/ConsistentState.spec +++ b/certora/specs/ConsistentState.spec @@ -11,7 +11,7 @@ methods { function Util.withdrawnAssets(address, MetaMorphoHarness.Id, uint256, uint256) external returns (uint256) envfree; } -// Check that fee cannot accrue to an unset fee recipient. +// Check that the fee cannot accrue to an unset fee recipient. invariant noFeeToUnsetFeeRecipient() feeRecipient() == 0 => fee() == 0; diff --git a/certora/specs/LastUpdated.spec b/certora/specs/LastUpdated.spec index 4fee4e59..8e36b3b9 100644 --- a/certora/specs/LastUpdated.spec +++ b/certora/specs/LastUpdated.spec @@ -19,10 +19,10 @@ function hasGuardianRole(address user) returns bool { return user == owner() || user == guardian(); } -// Check that any market with positive cap is created on Morpho Blue. +// Check that any market with a positive cap is created on Morpho Blue. // The corresponding invariant is difficult to verify because it requires to check properties on MetaMorpho and on Blue at the same time: // - on MetaMorpho, that it holds when the cap is positive for the first time -// - on Blue, that a created market always has positive last update +// - on Blue, that a created market always has a positive last update function hasPositiveSupplyCapIsUpdated(MetaMorphoHarness.Id id) returns bool { return config_(id).cap > 0 => Morpho.lastUpdate(id) > 0; } diff --git a/certora/specs/Roles.spec b/certora/specs/Roles.spec index e04fb8d3..0d1f8fec 100644 --- a/certora/specs/Roles.spec +++ b/certora/specs/Roles.spec @@ -13,7 +13,7 @@ methods { function guardian() external returns(address) envfree; function isAllocator(address target) external returns(bool) envfree; - // Sumarize Morpho external calls, as they don't depend on the authorization system of MetaMorpho. + // Summarize Morpho external calls, as they don't depend on the authorization system of MetaMorpho. function _.idToMarketParams(MetaMorphoHarness.Id) external => CONSTANT; function _.supplyShares(MetaMorphoHarness.Id, address) external => CONSTANT; function _.accrueInterest(MetaMorphoHarness.MarketParams) external => CONSTANT; diff --git a/certora/specs/Timelock.spec b/certora/specs/Timelock.spec index 734e93e3..f2435d5d 100644 --- a/certora/specs/Timelock.spec +++ b/certora/specs/Timelock.spec @@ -97,7 +97,7 @@ rule capIncreaseTime(env e_next, method f, calldataarg args) { f(e_next, args); if (e_next.block.timestamp < nextTime) { - // Check that cap cannot increase. + // Check that the cap cannot increase. assert config_(id).cap <= prevCap; // Increasing nextCapIncreaseTime with an interaction; assert nextCapIncreaseTime(e_next, id) >= nextCapIncreaseTimeBeforeInteraction; diff --git a/package.json b/package.json index 3b82f83e..40c41d8f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@morpho-org/metamorpho", "description": "MetaMorpho multicall encoder", "license": "GPL-2.0-or-later", - "version": "1.0.0", + "version": "1.1.0", "main": "lib/index.js", "bin": "lib/cli.js", "files": [ diff --git a/pkg/MetaMorphoAction.ts b/pkg/MetaMorphoAction.ts index ce0933f2..92f1d798 100644 --- a/pkg/MetaMorphoAction.ts +++ b/pkg/MetaMorphoAction.ts @@ -2,119 +2,219 @@ import { BigNumberish } from "ethers"; import { MetaMorpho__factory } from "types"; import { MarketAllocationStruct, MarketParamsStruct } from "types/src/MetaMorpho"; -export type MetaMorphoCall = string; +const METAMORPHO_IFC = MetaMorpho__factory.createInterface(); -export class MetaMorphoAction { - private static METAMORPHO_IFC = MetaMorpho__factory.createInterface(); +export type MetaMorphoCall = string; +export namespace MetaMorphoAction { /* CONFIGURATION */ - static setCurator(newCurator: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setCurator", [newCurator]); + /** + * Encodes a call to a MetaMorpho vault to set the curator. + * @param newCurator The address of the new curator. + */ + export function setCurator(newCurator: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setCurator", [newCurator]); } - static setIsAllocator(newAllocator: string, newIsAllocator: boolean): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setIsAllocator", [newAllocator, newIsAllocator]); + /** + * Encodes a call to a MetaMorpho vault to enable or disable an allocator. + * @param newAllocator The address of the allocator. + * @param newIsAllocator Whether the allocator should be enabled or disabled. + */ + export function setIsAllocator(newAllocator: string, newIsAllocator: boolean): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setIsAllocator", [newAllocator, newIsAllocator]); } - static setFeeRecipient(newFeeRecipient: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setFeeRecipient", [newFeeRecipient]); + /** + * Encode a call to a MetaMorpho vault to set the fee recipient. + * @param newFeeRecipient The address of the new fee recipient. + */ + export function setFeeRecipient(newFeeRecipient: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setFeeRecipient", [newFeeRecipient]); } - static setSkimRecipient(newSkimRecipient: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setSkimRecipient", [newSkimRecipient]); + /** + * Encode a call to a MetaMorpho vault to set the skim recipient. + * @param newSkimRecipient The address of the new skim recipient. + */ + export function setSkimRecipient(newSkimRecipient: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setSkimRecipient", [newSkimRecipient]); } - static setFee(fee: BigNumberish): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setFee", [fee]); + /** + * Encode a call to a MetaMorpho vault to set the fee. + * @param fee The new fee percentage (in WAD). + */ + export function setFee(fee: BigNumberish): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setFee", [fee]); } /* TIMELOCK */ - static submitTimelock(newTimelock: BigNumberish): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("submitTimelock", [newTimelock]); + /** + * Encodes a call to a MetaMorpho vault to submit a new timelock. + * @param newTimelock The new timelock (in seconds). + */ + export function submitTimelock(newTimelock: BigNumberish): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("submitTimelock", [newTimelock]); } - static acceptTimelock(): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("acceptTimelock"); + /** + * Encodes a call to a MetaMorpho vault to accept the pending timelock. + */ + export function acceptTimelock(): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("acceptTimelock"); } - static revokePendingTimelock(): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("revokePendingTimelock"); + /** + * Encodes a call to a MetaMorpho vault to revoke the pending timelock. + */ + export function revokePendingTimelock(): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("revokePendingTimelock"); } /* SUPPLY CAP */ - static submitCap(marketParams: MarketParamsStruct, newSupplyCap: BigNumberish): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("submitCap", [marketParams, newSupplyCap]); + /** + * Encodes a call to a MetaMorpho vault to submit a new supply cap. + * @param marketParams The market params of the market of which to submit a supply cap. + * @param newSupplyCap The new supply cap. + */ + export function submitCap(marketParams: MarketParamsStruct, newSupplyCap: BigNumberish): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("submitCap", [marketParams, newSupplyCap]); } - static acceptCap(marketParams: MarketParamsStruct): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("acceptCap", [marketParams]); + /** + * Encodes a call to a MetaMorpho vault to accept the pending supply cap. + * @param marketParams The market params of the market of which to accept the pending supply cap. + */ + export function acceptCap(marketParams: MarketParamsStruct): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("acceptCap", [marketParams]); } - static revokePendingCap(id: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("revokePendingCap", [id]); + /** + * Encodes a call to a MetaMorpho vault to revoke the pending supply cap. + * @param id The id of the market of which to revoke the pending supply cap. + */ + export function revokePendingCap(id: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("revokePendingCap", [id]); } /* FORCED MARKET REMOVAL */ - static submitMarketRemoval(marketParams: MarketParamsStruct): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("submitMarketRemoval", [marketParams]); + /** + * Encodes a call to a MetaMorpho vault to submit a market removal. + * @param marketParams The market params of the market to remove. + */ + export function submitMarketRemoval(marketParams: MarketParamsStruct): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("submitMarketRemoval", [marketParams]); } - static revokePendingMarketRemoval(id: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("revokePendingMarketRemoval", [id]); + /** + * Encodes a call to a MetaMorpho vault to accept the pending market removal. + * @param id The id of the market of which to accept the removal. + */ + export function revokePendingMarketRemoval(id: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("revokePendingMarketRemoval", [id]); } /* GUARDIAN */ - static submitGuardian(newGuardian: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("submitGuardian", [newGuardian]); + /** + * Encodes a call to a MetaMorpho vault to submit a new guardian. + * @param newGuardian The address of the new guardian. + */ + export function submitGuardian(newGuardian: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("submitGuardian", [newGuardian]); } - static acceptGuardian(): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("acceptGuardian"); + /** + * Encodes a call to a MetaMorpho vault to accept the pending guardian. + */ + export function acceptGuardian(): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("acceptGuardian"); } - static revokePendingGuardian(): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("revokePendingGuardian"); + /** + * Encodes a call to a MetaMorpho vault to revoke the pending guardian. + */ + export function revokePendingGuardian(): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("revokePendingGuardian"); } /* MANAGEMENT */ - static skim(erc20: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("skim", [erc20]); + /** + * Encodes a call to a MetaMorpho vault to skim ERC20 tokens. + * @param erc20 The address of the ERC20 token to skim. + */ + export function skim(erc20: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("skim", [erc20]); } - static setSupplyQueue(supplyQueue: string[]): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("setSupplyQueue", [supplyQueue]); + /** + * Encodes a call to a MetaMorpho vault to set the supply queue. + * @param supplyQueue The new supply queue. + */ + export function setSupplyQueue(supplyQueue: string[]): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("setSupplyQueue", [supplyQueue]); } - static updateWithdrawQueue(indexes: BigNumberish[]): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("updateWithdrawQueue", [indexes]); + /** + * Encodes a call to a MetaMorpho vault to update the withdraw queue. + * @param indexes The indexes of each market in the previous withdraw queue, in the new withdraw queue's order. + */ + export function updateWithdrawQueue(indexes: BigNumberish[]): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("updateWithdrawQueue", [indexes]); } - static reallocate(allocations: MarketAllocationStruct[]): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("reallocate", [allocations]); + /** + * Encodes a call to a MetaMorpho vault to reallocate the vault's liquidity across enabled markets. + * @param allocations The new target allocations of each market. + */ + export function reallocate(allocations: MarketAllocationStruct[]): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("reallocate", [allocations]); } /* ERC4626 */ - static mint(shares: BigNumberish, receiver: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("mint", [shares, receiver]); - } - - static deposit(assets: BigNumberish, receiver: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("deposit", [assets, receiver]); - } - - static withdraw(assets: BigNumberish, receiver: string, owner: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("withdraw", [assets, receiver, owner]); - } - - static redeem(shares: BigNumberish, receiver: string, owner: string): MetaMorphoCall { - return MetaMorphoAction.METAMORPHO_IFC.encodeFunctionData("redeem", [shares, receiver, owner]); + /** + * Encodes a call to a MetaMorpho vault to mint shares. + * @param shares The amount of shares to mint. + * @param receiver The address of the receiver of the shares. + */ + export function mint(shares: BigNumberish, receiver: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("mint", [shares, receiver]); + } + + /** + * Encodes a call to a MetaMorpho vault to deposit assets. + * @param assets The amount of assets to deposit. + * @param receiver The address of the receiver of the shares. + */ + export function deposit(assets: BigNumberish, receiver: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("deposit", [assets, receiver]); + } + + /** + * Encodes a call to a MetaMorpho vault to withdraw assets. + * @param assets The amount of assets to withdraw. + * @param receiver The address of the receiver of the assets. + * @param owner The address of the owner of the shares to redeem. + */ + export function withdraw(assets: BigNumberish, receiver: string, owner: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("withdraw", [assets, receiver, owner]); + } + + /** + * Encodes a call to a MetaMorpho vault to redeem shares. + * @param shares The amount of shares to redeem. + * @param receiver The address of the receiver of the assets. + * @param owner The address of the owner of the shares to redeem. + */ + export function redeem(shares: BigNumberish, receiver: string, owner: string): MetaMorphoCall { + return METAMORPHO_IFC.encodeFunctionData("redeem", [shares, receiver, owner]); } } diff --git a/src/interfaces/IMetaMorpho.sol b/src/interfaces/IMetaMorpho.sol index 4e8f648c..359987de 100644 --- a/src/interfaces/IMetaMorpho.sol +++ b/src/interfaces/IMetaMorpho.sol @@ -164,8 +164,6 @@ interface IMetaMorphoBase { function updateWithdrawQueue(uint256[] calldata indexes) external; /// @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 @@ -174,6 +172,8 @@ interface IMetaMorphoBase { /// - Withdrawals from markets that are expected to be withdrawn from during reallocation. /// @dev Sender is expected to pass `assets = type(uint256).max` with the last MarketAllocation of `allocations` to /// supply all the remaining withdrawn liquidity, which would ensure that `totalWithdrawn` = `totalSupplied`. + /// @dev A supply in a reallocation step will make the reallocation revert if the amount is greater than the net + /// amount from previous steps (i.e. total withdrawn minus total supplied). function reallocate(MarketAllocation[] calldata allocations) external; }