diff --git a/src/PublicAllocator.sol b/src/PublicAllocator.sol index c4429c3..bf7b14b 100644 --- a/src/PublicAllocator.sol +++ b/src/PublicAllocator.sol @@ -6,10 +6,10 @@ import { } from "../lib/metamorpho/src/interfaces/IMetaMorpho.sol"; import {MarketParamsLib} from "../lib/metamorpho/lib/morpho-blue/src/libraries/MarketParamsLib.sol"; - import {MorphoBalancesLib} from "../lib/metamorpho/lib/morpho-blue/src/libraries/periphery/MorphoBalancesLib.sol"; import {Market} from "../lib/metamorpho/lib/morpho-blue/src/interfaces/IMorpho.sol"; + import {UtilsLib} from "../lib/metamorpho/lib/morpho-blue/src/libraries/UtilsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; @@ -86,34 +86,45 @@ contract PublicAllocator is IPublicAllocatorStaticTyping { if (msg.value != fee) revert ErrorsLib.IncorrectFee(); MarketAllocation[] memory allocations = new MarketAllocation[](withdrawals.length + 1); - allocations[withdrawals.length].marketParams = depositMarketParams; - allocations[withdrawals.length].assets = type(uint256).max; - + Id depositMarketId = depositMarketParams.id(); uint128 totalWithdrawn; for (uint256 i = 0; i < withdrawals.length; i++) { Id id = withdrawals[i].marketParams.id(); - MORPHO.accrueInterest(withdrawals[i].marketParams); + + // Revert if the market is elsewhere in the list, or is the deposit market. + for (uint256 j = i + 1; j < withdrawals.length; j++) { + if (Id.unwrap(id) == Id.unwrap(withdrawals[j].marketParams.id())) { + revert ErrorsLib.InconsistentWithdrawTo(); + } + } + if (Id.unwrap(id) == Id.unwrap(depositMarketId)) revert ErrorsLib.InconsistentWithdrawTo(); + uint128 withdrawnAssets = withdrawals[i].amount; totalWithdrawn += withdrawnAssets; + MORPHO.accrueInterest(withdrawals[i].marketParams); + uint256 assets = MORPHO.expectedSupplyAssets(withdrawals[i].marketParams, address(VAULT)); + allocations[i].marketParams = withdrawals[i].marketParams; - allocations[i].assets = - MORPHO.expectedSupplyAssets(withdrawals[i].marketParams, address(VAULT)) - withdrawnAssets; + allocations[i].assets = assets - withdrawnAssets; flowCap[id].maxIn += withdrawnAssets; flowCap[id].maxOut -= withdrawnAssets; + emit EventsLib.PublicWithdrawal(id, withdrawnAssets); } + allocations[withdrawals.length].marketParams = depositMarketParams; + allocations[withdrawals.length].assets = type(uint256).max; + flowCap[depositMarketId].maxIn -= totalWithdrawn; + flowCap[depositMarketId].maxOut += totalWithdrawn; + VAULT.reallocate(allocations); - Id depositMarketId = depositMarketParams.id(); - uint256 vaultSupplyInMarket = MORPHO.expectedSupplyAssets(depositMarketParams, address(VAULT)); - if (vaultSupplyInMarket > supplyCap[depositMarketId]) { + if (MORPHO.expectedSupplyAssets(depositMarketParams, address(VAULT)) > supplyCap[depositMarketId]) { revert ErrorsLib.PublicAllocatorSupplyCapExceeded(depositMarketId); } - flowCap[depositMarketId].maxIn -= totalWithdrawn; - flowCap[depositMarketId].maxOut += totalWithdrawn; + emit EventsLib.PublicReallocateTo(msg.sender, depositMarketId, totalWithdrawn); } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index f340d46..f8a3b9c 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Id} from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol"; +import {Id, MarketParams} from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol"; +import {Withdrawal} from "../interfaces/IPublicAllocator.sol"; /// @title ErrorsLib /// @author Morpho Labs @@ -38,6 +39,9 @@ library ErrorsLib { /// @notice Thrown when the value is already set. error AlreadySet(); + /// @notice Thrown when there are duplicates with nonzero assets in `withdrawTo` arguments. + error InconsistentWithdrawTo(); + /// @notice Thrown when attempting to set max inflow/outflow above the MAX_SETTABLE_FLOW_CAP. error MaxSettableFlowCapExceeded(); diff --git a/test/PublicAllocatorFactory.t.sol b/test/PublicAllocatorFactoryTest.sol similarity index 100% rename from test/PublicAllocatorFactory.t.sol rename to test/PublicAllocatorFactoryTest.sol diff --git a/test/PublicAllocator.t.sol b/test/PublicAllocatorTest.sol similarity index 93% rename from test/PublicAllocator.t.sol rename to test/PublicAllocatorTest.sol index 87e1a8e..a9a957d 100644 --- a/test/PublicAllocator.t.sol +++ b/test/PublicAllocatorTest.sol @@ -437,6 +437,33 @@ contract PublicAllocatorTest is IntegrationTest { assertEq(marketAfter - marketBefore, flow); } + function testDuplicateInWithdrawals() public { + // Set flow limits + flowCaps.push(FlowConfig(idleParams.id(), FlowCap(MAX_SETTABLE_FLOW_CAP, MAX_SETTABLE_FLOW_CAP))); + flowCaps.push(FlowConfig(allMarkets[0].id(), FlowCap(MAX_SETTABLE_FLOW_CAP, 0))); + vm.prank(OWNER); + publicAllocator.setFlowCaps(flowCaps); + + // Prepare public reallocation from 2 markets to 1 + // _setCap(allMarkets[1], CAP2); + withdrawals.push(Withdrawal(idleParams, 1e18)); + withdrawals.push(Withdrawal(idleParams, 1e18)); + vm.expectRevert(ErrorsLib.InconsistentWithdrawTo.selector); + publicAllocator.withdrawTo(withdrawals, allMarkets[0]); + } + + function testSameInWithdrawalAndDeposit() public { + // Set flow limits + flowCaps.push(FlowConfig(idleParams.id(), FlowCap(MAX_SETTABLE_FLOW_CAP, MAX_SETTABLE_FLOW_CAP))); + flowCaps.push(FlowConfig(allMarkets[0].id(), FlowCap(MAX_SETTABLE_FLOW_CAP, 0))); + vm.prank(OWNER); + publicAllocator.setFlowCaps(flowCaps); + + withdrawals.push(Withdrawal(idleParams, 1e18)); + vm.expectRevert(ErrorsLib.InconsistentWithdrawTo.selector); + publicAllocator.withdrawTo(withdrawals, idleParams); + } + function testMaxFlowCapValue() public { assertEq(MAX_SETTABLE_FLOW_CAP, type(uint128).max / 2); }