diff --git a/.gitmodules b/.gitmodules index ddacf97..2064c73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "lib/metamorpho"] path = lib/metamorpho url = https://github.com/morpho-org/metamorpho -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/openzeppelin/openzeppelin-contracts diff --git a/lib/metamorpho b/lib/metamorpho index 32d5ae7..4ca254a 160000 --- a/lib/metamorpho +++ b/lib/metamorpho @@ -1 +1 @@ -Subproject commit 32d5ae71af4d8560cfde6fcd5b41d798c9c06406 +Subproject commit 4ca254a2f6ba74437ff35a012ed3814056123644 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index 932fddf..0000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 diff --git a/src/PublicAllocator.sol b/src/PublicAllocator.sol index 35ea746..f7c251c 100644 --- a/src/PublicAllocator.sol +++ b/src/PublicAllocator.sol @@ -5,7 +5,12 @@ import { Id, IMorpho, IMetaMorpho, MarketAllocation, MarketParams } from "../lib/metamorpho/src/interfaces/IMetaMorpho.sol"; import { - MarketParamsLib, MorphoLib, MorphoBalancesLib, SharesMathLib, Market + MarketParamsLib, + MorphoLib, + MorphoBalancesLib, + SharesMathLib, + Market, + UtilsLib } from "../lib/metamorpho/src/MetaMorpho.sol"; import {Ownable2Step, Ownable} from "../lib/metamorpho/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol"; import {Multicall} from "../lib/metamorpho/lib/openzeppelin-contracts/contracts/utils/Multicall.sol"; @@ -16,13 +21,13 @@ contract PublicAllocator is Ownable2Step, Multicall, IPublicAllocatorStaticTypin using MorphoLib for IMorpho; using MarketParamsLib for MarketParams; using SharesMathLib for uint256; + using UtilsLib for uint256; /// STORAGE /// uint256 fee; IMetaMorpho public immutable VAULT; IMorpho public immutable MORPHO; - mapping(Id => int256) public flows; mapping(Id => FlowCaps) public flowCaps; mapping(Id => uint256) public supplyCaps; // using IMorpho @@ -59,17 +64,15 @@ contract PublicAllocator is Ownable2Step, Multicall, IPublicAllocatorStaticTypin if (newShares.toAssetsUp(market.totalSupplyAssets, market.totalSupplyShares) > supplyCaps[id]) { revert ErrorsLib.PublicAllocatorSupplyCapExceeded(id); } - flows[id] += - int256((newShares - shares[i]).toAssetsUp(market.totalSupplyAssets, market.totalSupplyShares)); - if (flows[id] > int256(uint256(flowCaps[id].inflow))) { - revert ErrorsLib.InflowCapExceeded(id); - } + uint128 inflow = + (newShares - shares[i]).toAssetsUp(market.totalSupplyAssets, market.totalSupplyShares).toUint128(); + flowCaps[id].maxIn -= inflow; + flowCaps[id].maxOut += inflow; } else { - flows[id] -= - int256((shares[i] - newShares).toAssetsUp(market.totalSupplyAssets, market.totalSupplyShares)); - if (flows[id] < -int256(uint256(flowCaps[id].outflow))) { - revert ErrorsLib.OutflowCapExceeded(id); - } + uint128 outflow = + (shares[i] - newShares).toAssetsUp(market.totalSupplyAssets, market.totalSupplyShares).toUint128(); + flowCaps[id].maxIn += outflow; + flowCaps[id].maxOut -= outflow; } } } @@ -89,14 +92,10 @@ contract PublicAllocator is Ownable2Step, Multicall, IPublicAllocatorStaticTypin } } - // Set flow caps and optionally reset a flow. + // Set flow cap // Flows are rounded up from shares at every reallocation, so small errors may accumulate. function setFlow(FlowConfig calldata flowConfig) external onlyOwner { flowCaps[flowConfig.id] = flowConfig.caps; - - if (flowConfig.resetFlow) { - flows[flowConfig.id] = 0; - } } // Set supply cap. Public reallocation will not be able to increase supply if it ends above its cap. diff --git a/src/interfaces/IPublicAllocator.sol b/src/interfaces/IPublicAllocator.sol index 472dcd7..ba15f8a 100644 --- a/src/interfaces/IPublicAllocator.sol +++ b/src/interfaces/IPublicAllocator.sol @@ -11,14 +11,13 @@ import { } from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol"; struct FlowCaps { - uint128 outflow; - uint128 inflow; + uint128 maxIn; + uint128 maxOut; } struct FlowConfig { Id id; FlowCaps caps; - bool resetFlow; } /// @dev This interface is used for factorizing IPublicAllocatorStaticTyping and IPublicAllocator. @@ -26,7 +25,6 @@ struct FlowConfig { interface IPublicAllocatorBase { function VAULT() external view returns (IMetaMorpho); function MORPHO() external view returns (IMorpho); - function flows(Id) external view returns (int256); function supplyCaps(Id) external view returns (uint256); function reallocate(MarketAllocation[] calldata allocations) external payable; diff --git a/test/PublicAllocator.t.sol b/test/PublicAllocator.t.sol index 5eae4ee..abeb42b 100644 --- a/test/PublicAllocator.t.sol +++ b/test/PublicAllocator.t.sol @@ -50,7 +50,7 @@ contract PublicAllocatorTest is IntegrationTest { vm.assume(flow != 0); allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow)); allocations.push(MarketAllocation(allMarkets[0], flow)); - vm.expectRevert(abi.encodeWithSelector(PAErrorsLib.OutflowCapExceeded.selector, idleParams.id())); + vm.expectRevert(stdError.arithmeticError); publicAllocator.reallocate(allocations); } @@ -60,7 +60,7 @@ contract PublicAllocatorTest is IntegrationTest { deal(address(loanToken), address(vault), flow); allocations.push(MarketAllocation(allMarkets[0], flow)); allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow)); - vm.expectRevert(abi.encodeWithSelector(PAErrorsLib.InflowCapExceeded.selector, allMarkets[0].id())); + vm.expectRevert(stdError.arithmeticError); publicAllocator.reallocate(allocations); } @@ -68,7 +68,7 @@ contract PublicAllocatorTest is IntegrationTest { vm.assume(sender != OWNER); vm.prank(sender); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender)); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, 0), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, 0))); } function testTransferFeeAccess(address sender, address recipient) public { @@ -97,9 +97,9 @@ contract PublicAllocatorTest is IntegrationTest { vm.assume(flow != 0); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(flow, 0), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, flow))); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, flow), false)); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(flow, 0))); allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow)); allocations.push(MarketAllocation(allMarkets[0], flow)); @@ -116,18 +116,18 @@ contract PublicAllocatorTest is IntegrationTest { vm.assume(flow != 0); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(flow, 0), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, flow))); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, flow), false)); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(flow, 0))); allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow)); allocations.push(MarketAllocation(allMarkets[0], flow)); publicAllocator.reallocate(allocations); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(flow, 0), true)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, flow))); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, flow), true)); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(flow, 0))); delete allocations; @@ -203,8 +203,8 @@ contract PublicAllocatorTest is IntegrationTest { vm.startPrank(OWNER); publicAllocator.setCap(allMarkets[0].id(), cap); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(type(uint128).max, 0), false)); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, type(uint128).max), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, type(uint128).max))); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(type(uint128).max, 0))); vm.stopPrank(); // Should work at cap @@ -228,9 +228,9 @@ contract PublicAllocatorTest is IntegrationTest { // Remove flow limits vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(type(uint128).max, 0), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, type(uint128).max))); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, type(uint128).max), false)); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(type(uint128).max, 0))); // Set supply above future public allocator cap allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow)); @@ -258,9 +258,9 @@ contract PublicAllocatorTest is IntegrationTest { // Remove flow limits vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(type(uint128).max, 0), false)); + publicAllocator.setFlow(FlowConfig(idleParams.id(), FlowCaps(0, type(uint128).max))); vm.prank(OWNER); - publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(0, type(uint128).max), false)); + publicAllocator.setFlow(FlowConfig(allMarkets[0].id(), FlowCaps(type(uint128).max, 0))); // Set supply above future public allocator cap allocations.push(MarketAllocation(idleParams, INITIAL_DEPOSIT - flow));