Skip to content

Commit

Permalink
Merge branch 'feat/flows-and-eth-fee' into feat/reallocate-by-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
adhusson committed Feb 15, 2024
2 parents 94d3fcc + 4cdf138 commit 8b616ae
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 58 deletions.
46 changes: 24 additions & 22 deletions src/PublicAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {SharesMathLib} from "../lib/metamorpho/lib/morpho-blue/src/libraries/Sha

import {Market} from "../lib/metamorpho/lib/morpho-blue/src/interfaces/IMorpho.sol";
import {UtilsLib} from "./libraries/UtilsLib.sol";
import {Ownable2Step, Ownable} from "../lib/metamorpho/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol";

import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
Expand All @@ -27,28 +26,39 @@ import {
IPublicAllocatorStaticTyping
} from "./interfaces/IPublicAllocator.sol";

contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {
contract PublicAllocator is IPublicAllocatorStaticTyping {
using MorphoLib for IMorpho;
using MorphoBalancesLib for IMorpho;
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;
using UtilsLib for uint256;
using UtilsLib for uint128;

/// CONSTANTS ///

address public immutable OWNER;
IMorpho public immutable MORPHO;
IMetaMorpho public immutable VAULT;

/// STORAGE ///

uint256 public fee;
IMetaMorpho public immutable VAULT;
IMorpho public immutable MORPHO;
mapping(Id => FlowCap) public flowCap;
mapping(Id => uint256) public supplyCap;

/// MODIFIER ///

modifier onlyOwner() {
if (msg.sender != OWNER) revert ErrorsLib.NotOwner();
_;
}

/// CONSTRUCTOR ///

constructor(address owner, address vault) Ownable(owner) {
if (vault == address(0)) {
revert ErrorsLib.ZeroAddress();
}
constructor(address newOwner, address vault) {
if (newOwner == address(0)) revert ErrorsLib.ZeroAddress();
if (vault == address(0)) revert ErrorsLib.ZeroAddress();
OWNER = newOwner;
VAULT = IMetaMorpho(vault);
MORPHO = VAULT.MORPHO();
}
Expand All @@ -59,9 +69,7 @@ contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {
external
payable
{
if (msg.value != fee) {
revert ErrorsLib.FeeTooLow();
}
if (msg.value != fee) revert ErrorsLib.IncorrectFee(msg.value);

MarketAllocation[] memory allocations = new MarketAllocation[](withdrawals.length + 1);
allocations[withdrawals.length].marketParams = depositMarketParams;
Expand Down Expand Up @@ -90,30 +98,24 @@ contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {

Id depositMarketId = depositMarketParams.id();
uint256 depositAssets = MORPHO.expectedSupplyAssets(depositMarketParams, address(VAULT));
if (depositAssets > supplyCap[depositMarketId]) {
revert ErrorsLib.PublicAllocatorSupplyCapExceeded(depositMarketId);
}
if (depositAssets > supplyCap[depositMarketId]) revert ErrorsLib.PublicAllocatorSupplyCapExceeded(depositMarketId);
flowCap[depositMarketId].maxIn -= totalWithdrawn;
flowCap[depositMarketId].maxOut = (flowCap[depositMarketId].maxOut).saturatingAdd(totalWithdrawn);
emit EventsLib.PublicReallocateTo(_msgSender(), fee, depositMarketId, totalWithdrawn);
emit EventsLib.PublicReallocateTo(msg.sender, fee, depositMarketId, totalWithdrawn);
}

/// OWNER ONLY ///

function setFee(uint256 _fee) external onlyOwner {
if (fee == _fee) {
revert ErrorsLib.AlreadySet();
}
if (fee == _fee) revert ErrorsLib.AlreadySet();
fee = _fee;
emit EventsLib.SetFee(_fee);
}

function transferFee(address payable feeRecipient) external onlyOwner {
uint256 balance = address(this).balance;
if (address(this).balance > 0) {
feeRecipient.transfer(address(this).balance);
emit EventsLib.SetFee(balance);
}
feeRecipient.transfer(balance);
emit EventsLib.TransferFee(balance);
}

// Set flow cap
Expand Down
15 changes: 5 additions & 10 deletions src/interfaces/IPublicAllocator.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.21;

import {
IMetaMorpho,
IMorpho,
MarketAllocation,
Id,
IOwnable,
MarketParams
} from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol";
import {IMetaMorpho, IMorpho, MarketAllocation, Id, MarketParams} from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol";

struct FlowCap {
uint128 maxIn;
Expand All @@ -33,9 +26,11 @@ struct Withdrawal {
/// @dev This interface is used for factorizing IPublicAllocatorStaticTyping and IPublicAllocator.
/// @dev Consider using the IPublicAllocator interface instead of this one.
interface IPublicAllocatorBase {
function fee() external view returns (uint256);
function OWNER() external view returns (address);
function VAULT() external view returns (IMetaMorpho);
function MORPHO() external view returns (IMorpho);

function fee() external view returns (uint256);
function supplyCap(Id) external view returns (uint256);

function withdrawTo(Withdrawal[] calldata withdrawals, MarketParams calldata depositMarketParams)
Expand All @@ -58,6 +53,6 @@ interface IPublicAllocatorStaticTyping is IPublicAllocatorBase {
/// @custom:contact [email protected]
/// @dev Use this interface for PublicAllocator to have access to all the functions with the appropriate function
/// signatures.
interface IPublicAllocator is IOwnable, IPublicAllocatorBase {
interface IPublicAllocator is IPublicAllocatorBase {
function flowCap(Id) external view returns (FlowCap memory);
}
7 changes: 5 additions & 2 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {Id} from "../../lib/metamorpho/src/interfaces/IMetaMorpho.sol";
/// @custom:contact [email protected]
/// @notice Library exposing error messages.
library ErrorsLib {
/// @notice Thrown when the `msg.sender` is not the `owner`.
error NotOwner();

/// @notice Thrown when the address passed is the zero address.
error ZeroAddress();

Expand All @@ -20,8 +23,8 @@ library ErrorsLib {
/// @notice Thrown when flow configuration for market `id` has min flow > max flow
error InconsistentFlowConfig(Id id);

/// @notice Thrown when the reallocation fee given is too low
error FeeTooLow();
/// @notice Thrown when the reallocation fee given is wrong
error IncorrectFee(uint givenFee);

/// @notice Thrown when the fee recipient fails to receive the fee
error FeeTransferFail();
Expand Down
8 changes: 4 additions & 4 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ library EventsLib {

/// @notice Emitted when the owner changes the `fee`
event SetFee(uint256 fee);

/// @notice Emitted when the owner updates some flow caps
/// @notice Emitted when the owner transfers the fee.
event TransferFee(uint256 amount);
/// @notice Emitted when the owner updates some flow caps.
event SetFlowCaps(FlowConfig[] flowCaps);

/// @notice Emitted when the owner updates some supply caps
/// @notice Emitted when the owner updates some supply caps.
event SetSupplyCaps(SupplyConfig[] supplyCaps);
}
47 changes: 27 additions & 20 deletions test/PublicAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ contract PublicAllocatorTest is IntegrationTest {
}

function testOwner() public {
assertEq(publicAllocator.owner(), address(OWNER));
assertEq(publicAllocator.OWNER(), address(OWNER));
}

function testDeployAddressZeroFail() public {
vm.expectRevert(ErrorsLib.ZeroAddress.selector);
new PublicAllocator(address(0), address(vault));
vm.expectRevert(ErrorsLib.ZeroAddress.selector);
new PublicAllocator(OWNER, address(0));
}

function testReallocateCapZeroOutflowByDefault(uint128 flow) public {
Expand Down Expand Up @@ -91,28 +98,28 @@ contract PublicAllocatorTest is IntegrationTest {
flowCaps.push(FlowConfig(idleParams.id(), FlowCap(0, 0)));

vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
vm.expectRevert(ErrorsLib.NotOwner.selector);
publicAllocator.setFlowCaps(flowCaps);
}

function testTransferFeeAccessFail(address sender, address payable recipient) public {
vm.assume(sender != OWNER);
vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
vm.expectRevert(ErrorsLib.NotOwner.selector);
publicAllocator.transferFee(recipient);
}

function testSetFeeAccessFail(address sender, uint256 fee) public {
vm.assume(sender != OWNER);
vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
vm.expectRevert(ErrorsLib.NotOwner.selector);
publicAllocator.setFee(fee);
}

function testSetCapAccessFail(address sender, Id id, uint256 cap) public {
vm.assume(sender != OWNER);
vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
vm.expectRevert(ErrorsLib.NotOwner.selector);
supplyCaps.push(SupplyConfig(id, cap));
publicAllocator.setSupplyCaps(supplyCaps);
}
Expand Down Expand Up @@ -192,6 +199,11 @@ contract PublicAllocatorTest is IntegrationTest {
vm.prank(OWNER);
publicAllocator.setFlowCaps(flowCaps);

if (fee != publicAllocator.fee()) {
vm.prank(OWNER);
publicAllocator.setFee(fee);
}

withdrawals.push(Withdrawal(idleParams, flow));
withdrawals.push(Withdrawal(allMarkets[1], flow));

Expand Down Expand Up @@ -243,26 +255,25 @@ contract PublicAllocatorTest is IntegrationTest {
publicAllocator.withdrawTo(withdrawals, allMarkets[0]);
}

function testFeeAmountSuccess(uint256 requiredFee, uint256 givenFee) public {
function testFeeAmountSuccess(uint256 requiredFee) public {
vm.assume(requiredFee != publicAllocator.fee());
vm.prank(OWNER);
publicAllocator.setFee(requiredFee);

givenFee = bound(givenFee, requiredFee, type(uint256).max);
vm.deal(address(this), givenFee);
vm.deal(address(this), requiredFee);

publicAllocator.withdrawTo{value: givenFee}(withdrawals, allMarkets[0]);
publicAllocator.withdrawTo{value: requiredFee}(withdrawals, allMarkets[0]);
}

function testFeeAmountFail(uint256 requiredFee, uint256 givenFee) public {
vm.assume(requiredFee > 0);
vm.assume(requiredFee != givenFee);

vm.prank(OWNER);
publicAllocator.setFee(requiredFee);

givenFee = bound(givenFee, 0, requiredFee - 1);
vm.deal(address(this), givenFee);
vm.expectRevert(ErrorsLib.FeeTooLow.selector);
vm.expectRevert(abi.encodeWithSelector(ErrorsLib.IncorrectFee.selector,givenFee));

publicAllocator.withdrawTo{value: givenFee}(withdrawals, allMarkets[0]);
}
Expand All @@ -271,22 +282,22 @@ contract PublicAllocatorTest is IntegrationTest {
vm.prank(OWNER);
publicAllocator.setFee(0.001 ether);

publicAllocator.withdrawTo{value: 0.01 ether}(withdrawals, allMarkets[0]);
publicAllocator.withdrawTo{value: 0.005 ether}(withdrawals, allMarkets[0]);
publicAllocator.withdrawTo{value: 0.001 ether}(withdrawals, allMarkets[0]);
publicAllocator.withdrawTo{value: 0.001 ether}(withdrawals, allMarkets[0]);

uint256 before = address(this).balance;

vm.prank(OWNER);
publicAllocator.transferFee(payable(address(this)));

assertEq(address(this).balance - before, 0.01 ether + 0.005 ether, "wrong fee transferred");
assertEq(address(this).balance - before, 2 * 0.001 ether, "wrong fee transferred");
}

function testTransferFeeFail() public {
vm.prank(OWNER);
publicAllocator.setFee(0.001 ether);

publicAllocator.withdrawTo{value: 0.01 ether}(withdrawals, allMarkets[0]);
publicAllocator.withdrawTo{value: 0.001 ether}(withdrawals, allMarkets[0]);

CantReceive cr = new CantReceive();
vm.expectRevert("cannot receive");
Expand All @@ -296,11 +307,7 @@ contract PublicAllocatorTest is IntegrationTest {

function testTransferOKOnZerobalance() public {
vm.prank(OWNER);
publicAllocator.setFee(0.001 ether);

CantReceive cr = new CantReceive();
vm.prank(OWNER);
publicAllocator.transferFee(payable(address(cr)));
publicAllocator.transferFee(payable(address(this)));
}

receive() external payable {}
Expand Down

0 comments on commit 8b616ae

Please sign in to comment.