Skip to content

Commit

Permalink
fix: Prevent scheduling of windows with duplicate starting times (SC-…
Browse files Browse the repository at this point in the history
…13253) (#50)

* fix: prevent duplicate window starting times

* fix: Update error messages (#51)

* fix: update error messages from im to rm

* fix: rename test file
  • Loading branch information
vbidin authored Aug 2, 2023
1 parent d17553c commit 5960d4c
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 27 deletions.
20 changes: 11 additions & 9 deletions contracts/RecapitalizationModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ contract RecapitalizationModule is IRecapitalizationModule {
/**************************************************************************************************************************************/

modifier onlyClaimer {
require(IGlobalsLike(_globals()).isInstanceOf("RECAPITALIZATION_CLAIMER", msg.sender), "IM:NOT_CLAIMER");
require(IGlobalsLike(_globals()).isInstanceOf("RECAPITALIZATION_CLAIMER", msg.sender), "RM:NOT_CLAIMER");

_;
}

modifier onlyGovernor {
require(msg.sender == IGlobalsLike(_globals()).governor(), "IM:NOT_GOVERNOR");
require(msg.sender == IGlobalsLike(_globals()).governor(), "RM:NOT_GOVERNOR");

_;
}
Expand All @@ -79,7 +79,7 @@ contract RecapitalizationModule is IRecapitalizationModule {
IGlobalsLike globals_ = IGlobalsLike(_globals());
bool isScheduledCall_ = globals_.isValidScheduledCall(msg.sender, address(this), functionId_, msg.data);

require(isScheduledCall_, "IM:NOT_SCHEDULED");
require(isScheduledCall_, "RM:NOT_SCHEDULED");

globals_.unscheduleCall(msg.sender, functionId_, msg.data);

Expand All @@ -97,7 +97,7 @@ contract RecapitalizationModule is IRecapitalizationModule {
uint256 claimableAmount_
) = _claimable(lastClaimedWindowId, lastClaimedTimestamp, uint32(block.timestamp));

require(claimableAmount_ > 0, "IM:C:ZERO_CLAIM");
require(claimableAmount_ > 0, "RM:C:ZERO_CLAIM");

lastClaimedTimestamp = uint32(block.timestamp);
lastClaimedWindowId = lastClaimableWindowId_;
Expand All @@ -107,13 +107,15 @@ contract RecapitalizationModule is IRecapitalizationModule {
IMapleTokenLike(token).mint(IGlobalsLike(_globals()).mapleTreasury(), amountClaimed_ = claimableAmount_);
}

function schedule(uint32[] memory windowStarts_, uint208[] memory issuanceRates_) external onlyGovernor onlyScheduled("IM:SCHEDULE") {
function schedule(uint32[] memory windowStarts_, uint208[] memory issuanceRates_) external onlyGovernor onlyScheduled("RM:SCHEDULE") {
_validateWindows(windowStarts_, issuanceRates_);

// Find at which point in the linked list to insert the new windows.
uint16 insertionWindowId_ = _findInsertionPoint(windowStarts_[0]);
uint16 newWindowId_ = lastScheduledWindowId + 1;

require(windowStarts_[0] > windows[insertionWindowId_].windowStart, "RM:S:DUPLICATE_WINDOW");

windows[insertionWindowId_].nextWindowId = newWindowId_;

// Create all the new windows and link them up to each other.
Expand Down Expand Up @@ -222,12 +224,12 @@ contract RecapitalizationModule is IRecapitalizationModule {
}

function _validateWindows(uint32[] memory windowStarts_, uint208[] memory issuanceRates_) internal view {
require(windowStarts_.length > 0 && issuanceRates_.length > 0, "IM:VW:EMPTY_ARRAY");
require(windowStarts_.length == issuanceRates_.length, "IM:VW:LENGTH_MISMATCH");
require(windowStarts_[0] >= block.timestamp, "IM:VW:OUT_OF_DATE");
require(windowStarts_.length > 0 && issuanceRates_.length > 0, "RM:VW:EMPTY_ARRAY");
require(windowStarts_.length == issuanceRates_.length, "RM:VW:LENGTH_MISMATCH");
require(windowStarts_[0] >= block.timestamp, "RM:VW:OUT_OF_DATE");

for (uint256 index_ = 0; index_ < windowStarts_.length - 1; ++index_) {
require(windowStarts_[index_] < windowStarts_[index_ + 1], "IM:VW:OUT_OF_ORDER");
require(windowStarts_[index_] < windowStarts_[index_ + 1], "RM:VW:OUT_OF_ORDER");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ contract RecapitalizationModuleIntegrationTest is TestBase {
uint208[] memory rates = new uint208[](1);
rates[0] = 1e18;

globals.scheduleCall(address(module), "IM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, times, rates));
globals.scheduleCall(address(module), "RM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, times, rates));

module.schedule(times, rates);
vm.stopPrank();
Expand All @@ -56,7 +56,7 @@ contract RecapitalizationModuleIntegrationTest is TestBase {
}

function test_recapitalizationModule_claim_notClaimer() external {
vm.expectRevert("IM:NOT_CLAIMER");
vm.expectRevert("RM:NOT_CLAIMER");
module.claim();
}

Expand Down Expand Up @@ -98,7 +98,7 @@ contract RecapitalizationModuleIntegrationTest is TestBase {
rates[0] = 0.5e18;

vm.startPrank(governor);
globals.scheduleCall(address(module), "IM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, times, rates));
globals.scheduleCall(address(module), "RM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, times, rates));
module.schedule(times, rates);
vm.stopPrank();

Expand Down
2 changes: 1 addition & 1 deletion tests/invariants/ModuleHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ contract ModuleHandler is TestBase {
vm.prank(governor);
mapleGlobals.scheduleCall(
address(recapitalizationModule),
"IM:SCHEDULE",
"RM:SCHEDULE",
abi.encodeWithSelector(recapitalizationModule.schedule.selector, windowStarts, issuanceRates)
);

Expand Down
76 changes: 62 additions & 14 deletions tests/unit/RecapitalizationModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ contract RecapitalizationModuleTestBase is TestBase {

function expectUnscheduleCall() internal {
globals.__expectCall();
globals.unscheduleCall(governor, "IM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, windowStarts, issuanceRates));
globals.unscheduleCall(governor, "RM:SCHEDULE", abi.encodeWithSelector(module.schedule.selector, windowStarts, issuanceRates));
}

}
Expand Down Expand Up @@ -91,18 +91,18 @@ contract ClaimTests is RecapitalizationModuleTestBase {
function test_claim_noClaimer() external {
globals.__setIsInstance(false);

vm.expectRevert("IM:NOT_CLAIMER");
vm.expectRevert("RM:NOT_CLAIMER");
module.claim();
}

function test_claim_zeroClaim_atomic() external {
vm.expectRevert("IM:C:ZERO_CLAIM");
vm.expectRevert("RM:C:ZERO_CLAIM");
module.claim();
}

function test_claim_zeroClaim_afterWarp() external {
vm.warp(start + 10 days);
vm.expectRevert("IM:C:ZERO_CLAIM");
vm.expectRevert("RM:C:ZERO_CLAIM");
module.claim();
}

Expand All @@ -114,7 +114,7 @@ contract ClaimTests is RecapitalizationModuleTestBase {
module.schedule(windowStarts, issuanceRates);

vm.warp(start + 10 days);
vm.expectRevert("IM:C:ZERO_CLAIM");
vm.expectRevert("RM:C:ZERO_CLAIM");
module.claim();
}

Expand All @@ -126,7 +126,7 @@ contract ClaimTests is RecapitalizationModuleTestBase {
module.schedule(windowStarts, issuanceRates);

vm.warp(start + 10 days);
vm.expectRevert("IM:C:ZERO_CLAIM");
vm.expectRevert("RM:C:ZERO_CLAIM");
module.claim();
}

Expand Down Expand Up @@ -393,33 +393,33 @@ contract ScheduleTests is RecapitalizationModuleTestBase {

function test_schedule_notGovernor() external {
vm.stopPrank();
vm.expectRevert("IM:NOT_GOVERNOR");
vm.expectRevert("RM:NOT_GOVERNOR");
module.schedule(windowStarts, issuanceRates);
}

function test_schedule_notScheduled() external {
globals.__setIsValidScheduledCall(false);

vm.expectRevert("IM:NOT_SCHEDULED");
vm.expectRevert("RM:NOT_SCHEDULED");
module.schedule(windowStarts, issuanceRates);
}

function test_schedule_noArrays() external {
vm.expectRevert("IM:VW:EMPTY_ARRAY");
vm.expectRevert("RM:VW:EMPTY_ARRAY");
module.schedule(windowStarts, issuanceRates);
}

function test_schedule_noWindowStarts() external {
issuanceRates.push(1e18);

vm.expectRevert("IM:VW:EMPTY_ARRAY");
vm.expectRevert("RM:VW:EMPTY_ARRAY");
module.schedule(windowStarts, issuanceRates);
}

function test_schedule_noIssuanceRates() external {
windowStarts.push(start);

vm.expectRevert("IM:VW:EMPTY_ARRAY");
vm.expectRevert("RM:VW:EMPTY_ARRAY");
module.schedule(windowStarts, issuanceRates);
}

Expand All @@ -429,15 +429,15 @@ contract ScheduleTests is RecapitalizationModuleTestBase {

issuanceRates.push(1e18);

vm.expectRevert("IM:VW:LENGTH_MISMATCH");
vm.expectRevert("RM:VW:LENGTH_MISMATCH");
module.schedule(windowStarts, issuanceRates);
}

function test_schedule_outOfDate() external {
windowStarts.push(start - 1 seconds);
issuanceRates.push(1e18);

vm.expectRevert("IM:VW:OUT_OF_DATE");
vm.expectRevert("RM:VW:OUT_OF_DATE");
module.schedule(windowStarts, issuanceRates);
}

Expand All @@ -448,7 +448,7 @@ contract ScheduleTests is RecapitalizationModuleTestBase {
issuanceRates.push(0.95e18);
issuanceRates.push(1e18);

vm.expectRevert("IM:VW:OUT_OF_ORDER");
vm.expectRevert("RM:VW:OUT_OF_ORDER");
module.schedule(windowStarts, issuanceRates);
}

Expand Down Expand Up @@ -624,6 +624,54 @@ contract ScheduleTests is RecapitalizationModuleTestBase {
assertWindow(4, 0, start + 120 days, 0.99e18);
}

function test_schedule_duplicate() public {
windowStarts.push(start);
windowStarts.push(start + 1 seconds);

issuanceRates.push(100);
issuanceRates.push(150);

// Schedule the initial windows.
module.schedule(windowStarts, issuanceRates);

assertEq(module.lastScheduledWindowId(), 2);

assertWindow(0, 1, 0, 0);
assertWindow(1, 2, start, 100);
assertWindow(2, 0, start + 1 seconds, 150);

vm.warp(start + 1 seconds);
module.claim();

assertEq(module.lastClaimedTimestamp(), start + 1 seconds);
assertEq(module.lastClaimedWindowId(), 2);

windowStarts.pop();
windowStarts.pop();
windowStarts.push(start + 1 seconds);

issuanceRates.pop();
issuanceRates.pop();
issuanceRates.push(200);

// Schedule a window with a duplicate starting time.
vm.expectRevert("RM:S:DUPLICATE_WINDOW");
module.schedule(windowStarts, issuanceRates);

windowStarts.pop();
windowStarts.push(start + 2 seconds);

// Schedule a window with a valid starting time.
module.schedule(windowStarts, issuanceRates);

assertEq(module.lastScheduledWindowId(), 3);

assertWindow(0, 1, 0, 0);
assertWindow(1, 2, start, 100);
assertWindow(2, 3, start + 1 seconds, 150);
assertWindow(3, 0, start + 2 seconds, 200);
}

}

contract ViewFunctionTests is RecapitalizationModuleTestBase {
Expand Down

0 comments on commit 5960d4c

Please sign in to comment.