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

feat: curator role #6

Closed
wants to merge 8 commits into from
Closed
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
31 changes: 27 additions & 4 deletions src/PublicAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {
IMorpho public immutable MORPHO;
mapping(Id => FlowCap) public flowCap;
mapping(Id => uint256) public supplyCap;
mapping(address => bool) public isCurator;

/// CONSTRUCTOR ///

Expand All @@ -47,6 +48,18 @@ contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {
MORPHO = VAULT.MORPHO();
}

/// MODIFIERS ///

/// @dev Reverts if the caller doesn't have the curator role.
modifier onlyCuratorRole() {
address sender = _msgSender();
if (!isCurator[sender] && sender != owner()) {
revert ErrorsLib.NotCuratorRole(sender);
}

_;
}

/// PUBLIC ///

function reallocate(MarketAllocation[] calldata allocations) external payable {
Expand Down Expand Up @@ -105,22 +118,32 @@ contract PublicAllocator is Ownable2Step, IPublicAllocatorStaticTyping {
}
}

function setIsCurator(address account, bool accountIsCurator) external onlyOwner {
if (isCurator[account] == accountIsCurator) {
revert ErrorsLib.AlreadySet();
}
isCurator[account] = accountIsCurator;
emit EventsLib.SetIsCurator(account, accountIsCurator);
}

/// CURATOR ROLE ONLY ///

// Set flow cap
// Flows are rounded up from shares at every reallocation, so small errors may accumulate.
function setFlowCaps(FlowConfig[] calldata flowCaps) external onlyOwner {
function setFlowCaps(FlowConfig[] calldata flowCaps) external onlyCuratorRole {
for (uint256 i = 0; i < flowCaps.length; ++i) {
flowCap[flowCaps[i].id] = flowCaps[i].cap;
}

emit EventsLib.SetFlowCaps(flowCaps);
emit EventsLib.SetFlowCaps(_msgSender(), flowCaps);
}

// Set supply cap. Public reallocation will not be able to increase supply if it ends above its cap.
function setSupplyCaps(SupplyConfig[] calldata supplyCaps) external onlyOwner {
function setSupplyCaps(SupplyConfig[] calldata supplyCaps) external onlyCuratorRole {
for (uint256 i = 0; i < supplyCaps.length; ++i) {
supplyCap[supplyCaps[i].id] = supplyCaps[i].cap;
}

emit EventsLib.SetSupplyCaps(supplyCaps);
emit EventsLib.SetSupplyCaps(_msgSender(), supplyCaps);
}
}
2 changes: 2 additions & 0 deletions src/interfaces/IPublicAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ interface IPublicAllocatorBase {
function VAULT() external view returns (IMetaMorpho);
function MORPHO() external view returns (IMorpho);
function supplyCap(Id) external view returns (uint256);
function isCurator(address) external view returns (bool);

function reallocate(MarketAllocation[] calldata allocations) external payable;
function setFee(uint256 _fee) external;
function transferFee(address payable feeRecipient) external;
function setFlowCaps(FlowConfig[] calldata _flowCaps) external;
function setSupplyCaps(SupplyConfig[] calldata _supplyCaps) external;
function setIsCurator(address account, bool accountIsCurator) external;
}

/// @dev This interface is inherited by PublicAllocator so that function signatures are checked by the compiler.
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ library ErrorsLib {
/// @notice Thrown when the maximum uint128 is exceeded.
error MaxUint128Exceeded();

/// @notice Thrown when the caller doesn't have the allocator role.
error NotCuratorRole(address sender);

/// @notice Thrown when the value is already set.
error AlreadySet();
}
7 changes: 5 additions & 2 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ library EventsLib {
event SetFee(uint256 fee);

/// @notice Emitted when the owner updates some flow caps
event SetFlowCaps(FlowConfig[] flowCaps);
event SetFlowCaps(address sender, FlowConfig[] flowCaps);

/// @notice Emitted when the owner updates some supply caps
event SetSupplyCaps(SupplyConfig[] supplyCaps);
event SetSupplyCaps(address sender, SupplyConfig[] supplyCaps);

/// @notice Emitted when the owner changes a curator status
event SetIsCurator(address account, bool accountIsCurator);
}
60 changes: 51 additions & 9 deletions test/PublicAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ contract CantReceive {
contract PublicAllocatorTest is IntegrationTest {
IPublicAllocator public publicAllocator;
MarketAllocation[] internal allocations;
address internal PUBLIC_ALLOCATOR_CURATOR = makeAddr("PublicAllocatorCurator");
FlowConfig[] internal flowCaps;
SupplyConfig[] internal supplyCaps;

Expand Down Expand Up @@ -58,6 +59,10 @@ contract PublicAllocatorTest is IntegrationTest {
vm.prank(OWNER);
publicAllocator.setSupplyCaps(supplyCaps);
delete supplyCaps;

// Set PublicAllocator's curator
vm.prank(OWNER);
publicAllocator.setIsCurator(PUBLIC_ALLOCATOR_CURATOR, true);
}

function testOwner() public {
Expand All @@ -83,14 +88,21 @@ contract PublicAllocatorTest is IntegrationTest {

function testConfigureFlowAccessFail(address sender) public {
vm.assume(sender != OWNER);

vm.assume(sender != PUBLIC_ALLOCATOR_CURATOR);
flowCaps.push(FlowConfig(idleParams.id(), FlowCap(0, 0)));

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

function testSetIsCuratorAccessFail(address sender, address curator, bool isCurator) public {
vm.assume(sender != OWNER);
vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
publicAllocator.setIsCurator(curator, isCurator);
}

function testTransferFeeAccessFail(address sender, address payable recipient) public {
vm.assume(sender != OWNER);
vm.prank(sender);
Expand All @@ -105,10 +117,20 @@ contract PublicAllocatorTest is IntegrationTest {
publicAllocator.setFee(fee);
}

function testCuratorAccessSuccess() public {
flowCaps.push(FlowConfig(idleParams.id(), FlowCap(0, 0)));
vm.prank(PUBLIC_ALLOCATOR_CURATOR);
publicAllocator.setFlowCaps(flowCaps);
supplyCaps.push(SupplyConfig(idleParams.id(), 0));
vm.prank(PUBLIC_ALLOCATOR_CURATOR);
publicAllocator.setSupplyCaps(supplyCaps);
}

function testSetCapAccessFail(address sender, Id id, uint256 cap) public {
vm.assume(sender != OWNER);
vm.assume(sender != PUBLIC_ALLOCATOR_CURATOR);
vm.prank(sender);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender));
vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NotCuratorRole.selector, sender));
supplyCaps.push(SupplyConfig(id, cap));
publicAllocator.setSupplyCaps(supplyCaps);
}
Expand All @@ -131,14 +153,32 @@ contract PublicAllocatorTest is IntegrationTest {
publicAllocator.setFee(fee);
}

function testSetFlowCaps(uint128 in0, uint128 out0, uint128 in1, uint128 out1) public {
function testSetIsCurator(address account) public {
bool isCurator = !publicAllocator.isCurator(account);
vm.expectEmit(address(publicAllocator));
emit EventsLib.SetIsCurator(account, isCurator);
vm.prank(OWNER);
publicAllocator.setIsCurator(account, isCurator);
assertEq(publicAllocator.isCurator(account), isCurator);
}

function testSetIsCuratorAlreadySet(address account) public {
bool isCurator = publicAllocator.isCurator(account);
vm.expectRevert(ErrorsLib.AlreadySet.selector);
vm.prank(OWNER);
publicAllocator.setIsCurator(account, isCurator);
}

function testSetFlowCaps(uint128 in0, uint128 out0, uint128 in1, uint128 out1, bool useOwner) public {
flowCaps.push(FlowConfig(idleParams.id(), FlowCap(in0, out0)));
flowCaps.push(FlowConfig(allMarkets[0].id(), FlowCap(in1, out1)));

address sender = useOwner ? OWNER : PUBLIC_ALLOCATOR_CURATOR;

vm.expectEmit(address(publicAllocator));
emit EventsLib.SetFlowCaps(flowCaps);
emit EventsLib.SetFlowCaps(sender, flowCaps);

vm.prank(OWNER);
vm.prank(sender);
publicAllocator.setFlowCaps(flowCaps);

FlowCap memory flowCap;
Expand All @@ -151,14 +191,16 @@ contract PublicAllocatorTest is IntegrationTest {
assertEq(flowCap.maxOut, out1);
}

function testSetSupplyCaps(uint256 cap0, uint256 cap1) public {
function testSetSupplyCaps(uint256 cap0, uint256 cap1, bool useOwner) public {
supplyCaps.push(SupplyConfig(idleParams.id(), cap0));
supplyCaps.push(SupplyConfig(allMarkets[0].id(), cap1));

address sender = useOwner ? OWNER : PUBLIC_ALLOCATOR_CURATOR;

vm.expectEmit(address(publicAllocator));
emit EventsLib.SetSupplyCaps(supplyCaps);
emit EventsLib.SetSupplyCaps(sender, supplyCaps);

vm.prank(OWNER);
vm.prank(sender);
publicAllocator.setSupplyCaps(supplyCaps);

uint256 cap;
Expand Down