Skip to content

Commit

Permalink
CS5.7 - Prevent executing recurring swaps for missed intervals
Browse files Browse the repository at this point in the history
  • Loading branch information
kevincheng96 committed Oct 17, 2024
1 parent 3dba570 commit 8402619
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 2 deletions.
8 changes: 6 additions & 2 deletions src/RecurringSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,12 @@ contract RecurringSwap is QuarkScript {
revert SwapWindowNotOpen(nextSwapTime, block.timestamp);
}

// Update nextSwapTime
write(hashedConfig, bytes32(nextSwapTime + config.interval));
// Update the next swap time, ensuring that it is some time in the future
uint256 updatedNextSwapTime = nextSwapTime;
while (updatedNextSwapTime <= block.timestamp) {
updatedNextSwapTime += config.interval;
}
write(hashedConfig, bytes32(updatedNextSwapTime));

(uint256 amountIn, uint256 amountOut) = _calculateSwapAmounts(config);
(uint256 actualAmountIn, uint256 actualAmountOut) =
Expand Down
52 changes: 52 additions & 0 deletions test/RecurringSwap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,58 @@ contract RecurringSwapTest is Test {
assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap1 + 2 * amountToSwap2);
}

function testRecurringSwapCannotSwapMultipleTimesForMissedWindows() public {
// gas: disable gas metering except while executing operations
vm.pauseGasMetering();

deal(USDC, address(aliceWallet), 100_000e6);
uint40 swapInterval = 86_400; // 1 day interval
uint256 amountToSwap = 10 ether;
RecurringSwap.SwapConfig memory swapConfig = _createSwapConfig({
startTime: block.timestamp,
swapInterval: swapInterval,
amount: amountToSwap,
isExactOut: true
});
(QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper()
.newReplayableOpWithCalldata(
aliceWallet,
recurringSwap,
abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig),
ScriptType.ScriptAddress,
2
);
op.expiry = type(uint256).max;
(uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op);

assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 0 ether);

// gas: meter execute
vm.resumeGasMetering();
// 1. Execute recurring swap for the first time
aliceWallet.executeQuarkOperation(op, v1, r1, s1);

assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap);

// 2. Skip a few swap intervals by warping past multiple swap intervals
vm.warp(block.timestamp + 10 * swapInterval);

// 3. Execute recurring swap a second time
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v1, r1, s1);

assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap);

// 4. Cannot buy again at the current timestamp even though we skipped a few swap intervals
vm.expectRevert(
abi.encodeWithSelector(
RecurringSwap.SwapWindowNotOpen.selector, block.timestamp + swapInterval, block.timestamp
)
);
aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[2], v1, r1, s1);

assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap);
}

function testRevertsForInvalidInput() public {
// gas: disable gas metering except while executing operations
vm.pauseGasMetering();
Expand Down

0 comments on commit 8402619

Please sign in to comment.