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

Autoreset flows #4

Merged
merged 3 commits into from
Feb 13, 2024
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
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion lib/openzeppelin-contracts
Submodule openzeppelin-contracts deleted from 932fdd
33 changes: 16 additions & 17 deletions src/PublicAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
}
Expand All @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions src/interfaces/IPublicAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ 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.
/// @dev Consider using the IPublicAllocator interface instead of this one.
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;
Expand Down
30 changes: 15 additions & 15 deletions test/PublicAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -60,15 +60,15 @@ 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);
}

function testConfigureFlowAccess(address sender) public {
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 {
Expand Down Expand Up @@ -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));
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand Down