From c8e0b33aed0ff10ee47092b7ae6abdfa0839ec1a Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 13:22:26 +0530 Subject: [PATCH 1/9] Refactor: Narrow RateChange.untilEpoch to uint64 for gas optimization --- src/RateChangeQueue.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RateChangeQueue.sol b/src/RateChangeQueue.sol index adb61bfe..5e551fd3 100644 --- a/src/RateChangeQueue.sol +++ b/src/RateChangeQueue.sol @@ -6,7 +6,7 @@ library RateChangeQueue { // The payment rate to apply uint256 rate; // The epoch up to and including which this rate will be used to settle a rail - uint256 untilEpoch; + uint64 untilEpoch; } struct Queue { @@ -17,7 +17,7 @@ library RateChangeQueue { function enqueue( Queue storage queue, uint256 rate, - uint256 untilEpoch + uint64 untilEpoch ) internal { queue.changes.push(RateChange(rate, untilEpoch)); } From 07e2467c1cc56b4260620656210dbc9103675a2c Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 16:20:50 +0530 Subject: [PATCH 2/9] refactor: use uint64 for fromEpoch, toEpoch, and settleUpto in IArbiter interface --- src/Payments.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Payments.sol b/src/Payments.sol index ad18570d..4e1e2ea4 100644 --- a/src/Payments.sol +++ b/src/Payments.sol @@ -15,7 +15,7 @@ interface IArbiter { // The actual payment amount determined by the arbiter after arbitration of a rail during settlement uint256 modifiedAmount; // The epoch up to and including which settlement should occur. - uint256 settleUpto; + uint64 settleUpto; // A placeholder note for any additional information the arbiter wants to send to the caller of `settleRail` string note; } @@ -24,9 +24,9 @@ interface IArbiter { uint256 railId, uint256 proposedAmount, // the epoch up to and including which the rail has already been settled - uint256 fromEpoch, + uint64 fromEpoch, // the epoch up to and including which arbitration is requested; payment will be arbitrated for (toEpoch - fromEpoch) epochs - uint256 toEpoch, + uint64 toEpoch, uint256 rate ) external returns (ArbitrationResult memory result); } From a73619c602a0905a55554c18101f91b4a8c4863c Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 16:24:47 +0530 Subject: [PATCH 3/9] refactor: shrink epoch fields to uint64, commission BPS to uint16, and repack Rail struct Improve storage packing and reduce per-rail gas costs. --- src/Payments.sol | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Payments.sol b/src/Payments.sol index 4e1e2ea4..20a08f6d 100644 --- a/src/Payments.sol +++ b/src/Payments.sol @@ -42,33 +42,48 @@ contract Payments is using RateChangeQueue for RateChangeQueue.Queue; // Maximum commission rate in basis points (100% = 10000 BPS) - uint256 public constant COMMISSION_MAX_BPS = 10000; + uint16 public constant COMMISSION_MAX_BPS = 10000; - uint256 public constant PAYMENT_FEE_BPS = 10; //(0.1 % fee) + uint16 public constant PAYMENT_FEE_BPS = 10; //(0.1 % fee) struct Account { uint256 funds; uint256 lockupCurrent; uint256 lockupRate; // epoch up to and including which lockup has been settled for the account - uint256 lockupLastSettledAt; + uint64 lockupLastSettledAt; } struct Rail { + // slot 0: 20 B + 8 B = 28 B used (4 B free) address token; + uint64 lockupPeriod; + + // slot 1: 20 B + 8 B = 28 B used address from; + // epoch up to and including which this rail has been settled + uint64 settledUpTo; + + // slot 2: 20 B + 8 B = 28 B used address to; + uint64 endEpoch; // Final epoch up to which the rail can be settled (0 if not terminated) + + // slot 3: 20 B + 2 B = 22 B used address operator; + // Operator commission rate in basis points (e.g., 100 BPS = 1%) + uint16 commissionRateBps; + + // slot 4: 20 B used address arbiter; + + // slot 5: 32 B used uint256 paymentRate; - uint256 lockupPeriod; + + // slot 6: 32 B used uint256 lockupFixed; - // epoch up to and including which this rail has been settled - uint256 settledUpTo; + + // RateChangeQueue.Queue is 3×32 B (mapping + head + tail) slots 7, 8, 9 RateChangeQueue.Queue rateChangeQueue; - uint256 endEpoch; // Final epoch up to which the rail can be settled (0 if not terminated) - // Operator commission rate in basis points (e.g., 100 BPS = 1%) - uint256 commissionRateBps; } struct OperatorApproval { @@ -96,12 +111,12 @@ contract Payments is address operator; address arbiter; uint256 paymentRate; - uint256 lockupPeriod; + uint64 lockupPeriod; uint256 lockupFixed; - uint256 settledUpTo; - uint256 endEpoch; + uint64 settledUpTo; + uint64 endEpoch; // Operator commission rate in basis points (e.g., 100 BPS = 1%) - uint256 commissionRateBps; + uint16 commissionRateBps; } // token => client => operator => Approval @@ -118,7 +133,7 @@ contract Payments is struct RailInfo { uint256 railId; // The rail ID bool isTerminated; // True if rail is terminated - uint256 endEpoch; // End epoch for terminated rails (0 for active rails) + uint64 endEpoch; // End epoch for terminated rails (0 for active rails) } // token => payee => array of railIds From 5975af2fab32affc62694c3ab9d4bf69c25c5efa Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 16:54:02 +0530 Subject: [PATCH 4/9] refactor: narrow epoch (from uint256 to uint64) and commision types (uint256 to unit16) in public interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Affected Signatures: - setPayeeMaxCommission(address token, uint16 maxBps) - createRail(address token, address from, address to, address arbiter, uint16 commissionRateBps) - modifyRailLockup(uint256 railId, uint64 period, uint256 lockupFixed) - settleRail(uint256 railId, uint64 untilEpoch) - getRail(uint256 railId) -> returns RailView { // narrowed: uint64 lockupPeriod; uint64 settledUpTo; uint64 endEpoch; uint16 commissionRateBps; // other fields unchanged } - getRailsForPayerAndToken(address payer, address token) → returns RailInfo[] { uint64 endEpoch; other fields unchanged } - getRailsForPayeeAndToken(address payee, address token) → returns RailInfo[] { uint64 endEpoch; other fields unchanged } - RateChangeQueue.enqueue(Queue storage, uint256 rate, uint64 untilEpoch) - removed setPayeeMaxCommission function --- src/Payments.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Payments.sol b/src/Payments.sol index 20a08f6d..cfd796e4 100644 --- a/src/Payments.sol +++ b/src/Payments.sol @@ -488,7 +488,7 @@ contract Payments is address from, address to, address arbiter, - uint256 commissionRateBps + uint16 commissionRateBps ) external nonReentrant @@ -518,7 +518,7 @@ contract Payments is rail.to = to; rail.operator = operator; rail.arbiter = arbiter; - rail.settledUpTo = block.number; + rail.settledUpTo = uint64(block.number); rail.endEpoch = 0; rail.commissionRateBps = commissionRateBps; @@ -539,7 +539,7 @@ contract Payments is /// @custom:constraint Operator must have sufficient lockup allowance to cover any increases the lockup period or the fixed lockup. function modifyRailLockup( uint256 railId, - uint256 period, + uint64 period, uint256 lockupFixed ) external @@ -560,7 +560,7 @@ contract Payments is function modifyTerminatedRailLockup( Rail storage rail, - uint256 period, + uint64 period, uint256 lockupFixed ) internal { require( @@ -595,7 +595,7 @@ contract Payments is function modifyNonTerminatedRailLockup( Rail storage rail, - uint256 period, + uint64 period, uint256 lockupFixed ) internal { Account storage payer = accounts[rail.token][rail.from]; @@ -755,7 +755,7 @@ contract Payments is uint256 newRate, uint256 oneTimePayment ) internal { - uint256 endEpoch = maxSettlementEpochForTerminatedRail(rail); + uint64 endEpoch = maxSettlementEpochForTerminatedRail(rail); require( newRate == 0 && oneTimePayment == 0, "for terminated rails beyond last settlement epoch, both new rate and one-time payment must be 0" @@ -810,7 +810,7 @@ contract Payments is // For arbitrated rails, we need to enqueue the old rate. // This ensures that the old rate is applied up to and including the current block. // The new rate will be applicable starting from the next block. - rail.rateChangeQueue.enqueue(oldRate, block.number); + rail.rateChangeQueue.enqueue(oldRate, uint64(block.number)); } } @@ -915,12 +915,12 @@ contract Payments is uint256 totalNetPayeeAmount, uint256 totalPaymentFee, uint256 totalOperatorCommission, - uint256 finalSettledEpoch, + uint64 finalSettledEpoch, string memory note ) { // Verify the current epoch is greater than the max settlement epoch - uint256 maxSettleEpoch = maxSettlementEpochForTerminatedRail( + uint64 maxSettleEpoch = maxSettlementEpochForTerminatedRail( rails[railId] ); require( @@ -942,7 +942,7 @@ contract Payments is /// @return note Additional information about the settlement (especially from arbitration). function settleRail( uint256 railId, - uint256 untilEpoch + uint64 untilEpoch ) public nonReentrant @@ -954,7 +954,7 @@ contract Payments is uint256 totalNetPayeeAmount, uint256 totalPaymentFee, uint256 totalOperatorCommission, - uint256 finalSettledEpoch, + uint64 finalSettledEpoch, string memory note ) { From 41bdc8e42fc4e5d40ea48953b8d341d35dcd26d3 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 17:46:49 +0530 Subject: [PATCH 5/9] refactor: explicit uint64/uint16 downcasts for internal functions and introduce min64 helper function - Add `min64(uint64 a, uint64 b)` to unambiguously compute the smaller of two 64-bit values. - Update settlement logic to call `min64` for all uint64 comparisons. - Ensure explicit `uint64(...)` casts when writing block numbers or division results back into 64-bit state fields. --- src/Payments.sol | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/Payments.sol b/src/Payments.sol index cfd796e4..c69d6be6 100644 --- a/src/Payments.sol +++ b/src/Payments.sol @@ -793,7 +793,7 @@ contract Payments is // If there is no arbiter, settle the rail immediately if (rail.arbiter == address(0)) { - (, , , , uint256 settledUpto, ) = settleRail(railId, block.number); + (, , , , uint256 settledUpto, ) = settleRail(railId, uint64(block.number)); require( settledUpto == block.number, "failed to settle rail up to current epoch" @@ -963,7 +963,7 @@ contract Payments is function settleRailInternal( uint256 railId, - uint256 untilEpoch, + uint64 untilEpoch, bool skipArbitration ) internal @@ -972,7 +972,7 @@ contract Payments is uint256 totalNetPayeeAmount, uint256 totalPaymentFee, uint256 totalOperatorCommission, - uint256 finalSettledEpoch, + uint64 finalSettledEpoch, string memory note ) { @@ -998,14 +998,14 @@ contract Payments is } // Calculate the maximum settlement epoch based on account lockup - uint256 maxSettlementEpoch; + uint64 maxSettlementEpoch; if (!isRailTerminated(rail)) { - maxSettlementEpoch = min(untilEpoch, payer.lockupLastSettledAt); + maxSettlementEpoch = min64(untilEpoch, payer.lockupLastSettledAt); } else { - maxSettlementEpoch = min(untilEpoch, rail.endEpoch); + maxSettlementEpoch = min64(untilEpoch, rail.endEpoch); } - uint256 startEpoch = rail.settledUpTo; + uint64 startEpoch = rail.settledUpTo; // Nothing to settle (already settled or zero-duration) if (startEpoch >= maxSettlementEpoch) { return ( @@ -1094,12 +1094,12 @@ contract Payments is uint256 totalNetPayeeAmount, uint256 totalPaymentFee, uint256 totalOperatorCommission, - uint256 finalEpoch, + uint64 finalEpoch, string memory regularNote, string memory finalizedNote ) internal - returns (uint256, uint256, uint256, uint256, uint256, string memory) + returns (uint256, uint256, uint256, uint256, uint64, string memory) { // Check if rail is a terminated rail that's now fully settled if ( @@ -1152,8 +1152,8 @@ contract Payments is function _settleWithRateChanges( uint256 railId, uint256 currentRate, - uint256 startEpoch, - uint256 targetEpoch, + uint64 startEpoch, + uint64 targetEpoch, bool skipArbitration ) internal @@ -1292,8 +1292,8 @@ contract Payments is function _settleSegment( uint256 railId, - uint256 epochStart, - uint256 epochEnd, + uint64 epochStart, + uint64 epochEnd, uint256 rate, bool skipArbitration ) @@ -1318,7 +1318,7 @@ contract Payments is // Calculate the default settlement values (without arbitration) uint256 duration = epochEnd - epochStart; uint256 settledAmount = rate * duration; - uint256 settledUntilEpoch = epochEnd; + uint64 settledUntilEpoch = epochEnd; note = ""; // If this rail has an arbiter and we're not skipping arbitration, let it decide on the final settlement amount @@ -1412,9 +1412,9 @@ contract Payments is // returns the actual epoch upto and including which the lockup was settled function settleAccountLockup( Account storage account - ) internal returns (uint256) { - uint256 currentEpoch = block.number; - uint256 elapsedTime = currentEpoch - account.lockupLastSettledAt; + ) internal returns (uint64) { + uint64 currentEpoch = uint64(block.number); + uint64 elapsedTime = currentEpoch - account.lockupLastSettledAt; if (elapsedTime <= 0) { return account.lockupLastSettledAt; @@ -1446,7 +1446,7 @@ contract Payments is } // Round down to the nearest whole epoch - uint256 fractionalEpochs = availableFunds / account.lockupRate; + uint64 fractionalEpochs = uint64(availableFunds / account.lockupRate); // Apply lockup up to this point account.lockupCurrent += account.lockupRate * fractionalEpochs; @@ -1458,7 +1458,7 @@ contract Payments is function remainingEpochsForTerminatedRail( Rail storage rail - ) internal view returns (uint256) { + ) internal view returns (uint64) { require(isRailTerminated(rail), "rail is not terminated"); // If current block beyond end epoch, return 0 @@ -1467,7 +1467,7 @@ contract Payments is } // Return the number of epochs (blocks) remaining until end epoch - return rail.endEpoch - block.number; + return rail.endEpoch - uint64(block.number); } function isRailTerminated(Rail storage rail) internal view returns (bool) { @@ -1481,7 +1481,7 @@ contract Payments is // Get the final settlement epoch for a terminated rail function maxSettlementEpochForTerminatedRail( Rail storage rail - ) internal view returns (uint256) { + ) internal view returns (uint64) { require(isRailTerminated(rail), "rail is not terminated"); return rail.endEpoch; } @@ -1685,3 +1685,7 @@ contract Payments is function min(uint256 a, uint256 b) pure returns (uint256) { return a < b ? a : b; } + +function min64(uint64 a, uint64 b) pure returns (uint64) { + return a < b ? a : b; +} \ No newline at end of file From a5a5be05f5a43eaf8f6aaf1fba1a75ab0afb4479 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 18:16:04 +0530 Subject: [PATCH 6/9] test: adapt test helpers to uint64 epoch API - PaymentsTestHelpers: - getAccountData() now unpacks lockupLastSettledAt as uint64 and return Payments.Account matching new signature - assertAccountState expectedLastSettled parameter changed to uint64 - setupRailWithParameters accepts lockupPeriod as uint64 - RailSettlementHelpers: - SettlementResult.settledUpto field changed to uint64 - settleRailAndVerify and terminateAndSettleRail signatures updated to use uint64 - Added explicit `uint64(block.number)` casts where needed --- test/helpers/PaymentsTestHelpers.sol | 6 +++--- test/helpers/RailSettlementHelpers.sol | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/helpers/PaymentsTestHelpers.sol b/test/helpers/PaymentsTestHelpers.sol index 747af5bd..7ddea980 100644 --- a/test/helpers/PaymentsTestHelpers.sol +++ b/test/helpers/PaymentsTestHelpers.sol @@ -104,7 +104,7 @@ contract PaymentsTestHelpers is Test, BaseTestHelper { uint256 funds, uint256 lockupCurrent, uint256 lockupRate, - uint256 lockupLastSettledAt + uint64 lockupLastSettledAt ) = payments.accounts(token, user); return @@ -340,7 +340,7 @@ contract PaymentsTestHelpers is Test, BaseTestHelper { address to, address railOperator, uint256 paymentRate, - uint256 lockupPeriod, + uint64 lockupPeriod, uint256 lockupFixed, address arbiter ) public returns (uint256 railId) { @@ -549,7 +549,7 @@ contract PaymentsTestHelpers is Test, BaseTestHelper { uint256 expectedFunds, uint256 expectedLockup, uint256 expectedRate, - uint256 expectedLastSettled + uint64 expectedLastSettled ) public view { Payments.Account memory account = getAccountData(user); assertEq(account.funds, expectedFunds, "Account funds incorrect"); diff --git a/test/helpers/RailSettlementHelpers.sol b/test/helpers/RailSettlementHelpers.sol index b6a5057b..39cd6f6e 100644 --- a/test/helpers/RailSettlementHelpers.sol +++ b/test/helpers/RailSettlementHelpers.sol @@ -28,7 +28,7 @@ contract RailSettlementHelpers is Test { uint256 netPayeeAmount; uint256 paymentFee; uint256 operatorCommission; - uint256 settledUpto; + uint64 settledUpto; string note; } @@ -38,7 +38,7 @@ contract RailSettlementHelpers is Test { address operator, address arbiter, uint256[] memory rates, - uint256 lockupPeriod, + uint64 lockupPeriod, uint256 lockupFixed ) public returns (uint256) { require( @@ -95,7 +95,7 @@ contract RailSettlementHelpers is Test { address to, address operator, uint256 paymentRate, - uint256 lockupPeriod, + uint64 lockupPeriod, uint256 fundAmount, uint256 fixedLockup ) public returns (uint256) { @@ -126,9 +126,9 @@ contract RailSettlementHelpers is Test { function settleRailAndVerify( uint256 railId, - uint256 untilEpoch, + uint64 untilEpoch, uint256 expectedAmount, - uint256 expectedUpto + uint64 expectedUpto ) public returns (SettlementResult memory result) { console.log("settleRailAndVerify"); // Get the rail details to identify payer and payee @@ -153,7 +153,7 @@ contract RailSettlementHelpers is Test { uint256 netPayeeAmount; uint256 paymentFee; uint256 operatorCommission; - uint256 settledUpto; + uint64 settledUpto; string memory note; vm.startPrank(payer); @@ -224,7 +224,7 @@ contract RailSettlementHelpers is Test { function terminateAndSettleRail( uint256 railId, uint256 expectedAmount, - uint256 expectedUpto + uint64 expectedUpto ) public returns (SettlementResult memory result) { // Get rail details to extract client and operator addresses Payments.RailView memory rail = payments.getRail(railId); @@ -251,7 +251,7 @@ contract RailSettlementHelpers is Test { return settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, expectedUpto ); @@ -262,7 +262,7 @@ contract RailSettlementHelpers is Test { uint256 railId, address operator, uint256 newRate, - uint256 newLockupPeriod, + uint64 newLockupPeriod, uint256 newFixedLockup ) public { Payments.RailView memory railBefore = paymentsContract.getRail(railId); From 5600d7def9132e369f1dfa419ddb425bde42f4c7 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 18:24:21 +0530 Subject: [PATCH 7/9] test: update MockArbiter to use uint64 for epoch parameters and settleUpto - Change `arbitratePayment` signature to `uint64 fromEpoch, uint64 toEpoch` per IArbiter - Convert `customUpto` storage and `setCustomValues` param to `uint64` - Compute and return 64-bit `settleUpto` values (e.g. cast reducedEndEpoch) --- test/mocks/MockArbiter.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/mocks/MockArbiter.sol b/test/mocks/MockArbiter.sol index 3ec7938e..c3ac3887 100644 --- a/test/mocks/MockArbiter.sol +++ b/test/mocks/MockArbiter.sol @@ -15,7 +15,7 @@ contract MockArbiter is IArbiter { ArbiterMode public mode = ArbiterMode.STANDARD; // Default to STANDARD mode uint256 public modificationFactor; // Percentage (0-100) for reductions uint256 public customAmount; - uint256 public customUpto; + uint64 public customUpto; string public customNote; constructor(ArbiterMode _mode) { @@ -31,7 +31,7 @@ contract MockArbiter is IArbiter { // Set custom return values for CUSTOM_RETURN mode function setCustomValues( uint256 _amount, - uint256 _upto, + uint64 _upto, string calldata _note ) external { customAmount = _amount; @@ -47,8 +47,8 @@ contract MockArbiter is IArbiter { function arbitratePayment( uint256 /* railId */, uint256 proposedAmount, - uint256 fromEpoch, - uint256 toEpoch, + uint64 fromEpoch, + uint64 toEpoch, uint256 /* rate */ ) external view override returns (ArbitrationResult memory result) { if (mode == ArbiterMode.STANDARD) { @@ -69,7 +69,7 @@ contract MockArbiter is IArbiter { } else if (mode == ArbiterMode.REDUCE_DURATION) { uint256 totalEpochs = toEpoch - fromEpoch; uint256 reducedEpochs = (totalEpochs * modificationFactor) / 100; - uint256 reducedEndEpoch = fromEpoch + reducedEpochs; + uint64 reducedEndEpoch = uint64(fromEpoch + reducedEpochs); // Calculate reduced amount proportionally uint256 reducedAmount = (proposedAmount * reducedEpochs) / From 446389bb2855746c46765c8a75c72045d83b01de Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Thu, 8 May 2025 19:34:15 +0530 Subject: [PATCH 8/9] test: adapt tests for uint64/uint16 support Update the following test files to use uint64 for epoch/block parameters and uint16 for BPS fields: - AccountLockupSettlement.t.sol - Fees.t.sol - RailGetters.t.sol - RateChangeQueue.t.sol - AccountManagement.t.sol - OperatorApproval.t.sol - RailSettlement.t.sol --- src/Payments.sol | 14 +++--- test/AccountLockupSettlement.t.sol | 18 ++++---- test/AccountManagement.t.sol | 4 +- test/Fees.t.sol | 28 ++++++------ test/OperatorApproval.t.sol | 6 +-- test/RailGetters.t.sol | 2 +- test/RailSettlement.t.sol | 73 +++++++++++++++--------------- test/RateChangeQueue.t.sol | 9 ++-- 8 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/Payments.sol b/src/Payments.sol index c69d6be6..a708a730 100644 --- a/src/Payments.sol +++ b/src/Payments.sol @@ -150,7 +150,7 @@ contract Payments is uint256 totalNetPayeeAmount; uint256 totalPaymentFee; uint256 totalOperatorCommission; - uint256 processedEpoch; + uint64 processedEpoch; string note; } @@ -818,7 +818,7 @@ contract Payments is uint256 amount, address token, address operator, - uint256 commissionRateBps + uint16 commissionRateBps ) internal returns ( @@ -1180,7 +1180,7 @@ contract Payments is // Process each segment until we reach the target epoch or hit an early exit condition while (state.processedEpoch < targetEpoch) { ( - uint256 segmentEndBoundary, + uint64 segmentEndBoundary, uint256 segmentRate ) = _getNextSegmentBoundary( rateQueue, @@ -1267,9 +1267,9 @@ contract Payments is function _getNextSegmentBoundary( RateChangeQueue.Queue storage rateQueue, uint256 currentRate, - uint256 processedEpoch, - uint256 targetEpoch - ) internal view returns (uint256 segmentEndBoundary, uint256 segmentRate) { + uint64 processedEpoch, + uint64 targetEpoch + ) internal view returns (uint64 segmentEndBoundary, uint256 segmentRate) { // Default boundary is the target we want to reach segmentEndBoundary = targetEpoch; segmentRate = currentRate; @@ -1285,7 +1285,7 @@ contract Payments is ); // Boundary is the minimum of our target or the next rate change epoch - segmentEndBoundary = min(targetEpoch, nextRateChange.untilEpoch); + segmentEndBoundary = min64(targetEpoch, nextRateChange.untilEpoch); segmentRate = nextRateChange.rate; } } diff --git a/test/AccountLockupSettlement.t.sol b/test/AccountLockupSettlement.t.sol index 59c8a39e..3bffaac6 100644 --- a/test/AccountLockupSettlement.t.sol +++ b/test/AccountLockupSettlement.t.sol @@ -46,7 +46,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT * 2, 0, 0, - block.number + uint64(block.number) ); } @@ -64,7 +64,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { USER2, OPERATOR, lockupRate, // payment rate - lockupPeriod, // lockup period + uint64(lockupPeriod), // lockup period 0, // no fixed lockup address(0) // no fixed lockup ); @@ -89,7 +89,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT * 2, expectedLockup, lockupRate, - block.number + uint64(block.number) ); } @@ -134,7 +134,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT, // expected funds expectedLockup, // expected lockup lockupRate, // expected lockup rate - expectedSettlementBlock // expected settlement block + uint64(expectedSettlementBlock) // expected settlement block ); } @@ -155,7 +155,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { USER2, OPERATOR, lockupRate, // 1 token per block - lockupPeriod, // Lockup period of 30 blocks + uint64(lockupPeriod), // Lockup period of 30 blocks initialLockup, // initial fixed lockup of 10 ether address(0) // no fixed lockup ); @@ -177,7 +177,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT * 3, // expected funds expectedLockup, // expected lockup lockupRate, // expected lockup rate - block.number // expected settlement block + uint64(block.number) // expected settlement block ); } @@ -206,7 +206,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT, DEPOSIT_AMOUNT, 0, // no payment rate - block.number + uint64(block.number) ); helper.makeDeposit(USER1, USER1, 1); // Adding more funds @@ -258,7 +258,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { USER2, OPERATOR, lockupRate, // 1 ether per block - lockupPeriod, // Lockup period of 10 blocks + uint64(lockupPeriod), // Lockup period of 10 blocks initialLockup, // 50 ether fixed lockup address(0) // no fixed lockup ); @@ -284,7 +284,7 @@ contract AccountLockupSettlementTest is Test, BaseTestHelper { 60 ether, // expected funds 60 ether, // expected lockup lockupRate, // expected lockup rate - block.number // expected settlement block + uint64(block.number) // expected settlement block ); } } diff --git a/test/AccountManagement.t.sol b/test/AccountManagement.t.sol index fde2c3e2..171d5fff 100644 --- a/test/AccountManagement.t.sol +++ b/test/AccountManagement.t.sol @@ -181,7 +181,7 @@ contract AccountManagementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT, // expected funds lockedAmount, // expected lockup 0, // expected rate (not set in this test) - block.number // expected last settled + uint64(block.number) // expected last settled ); // Try to withdraw more than unlocked funds @@ -244,7 +244,7 @@ contract AccountManagementTest is Test, BaseTestHelper { DEPOSIT_AMOUNT * 2, // expected funds 20 ether, // expected lockup (2 rails × 0.5 ether per block × 10 blocks + future lockup of 10 ether) lockupRate * 2, // expected rate (2 * 0.5 ether) - block.number // expected last settled + uint64(block.number) // expected last settled ); } } diff --git a/test/Fees.t.sol b/test/Fees.t.sol index a1a85080..0dda7211 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -175,24 +175,24 @@ contract FeesTest is Test, BaseTestHelper { // Settle rail1 (token1) settlementHelper.settleRailAndVerify( rail1Id, - block.number, + uint64(block.number), rail1FirstExpectedAmount, - block.number - ); + uint64(block.number + )); // Settle rail2 (token2) vm.prank(USER1); (uint256 settledAmount2, , , , , ) = payments.settleRail( rail2Id, - block.number - ); + uint64(block.number + )); // Settle rail3 (token3) vm.prank(USER1); (uint256 settledAmount3, , , , , ) = payments.settleRail( rail3Id, - block.number - ); + uint64(block.number + )); // Calculate expected fees based on actual settled amounts (0.1% of settled amounts) uint256 rail1FirstFee = (rail1FirstExpectedAmount * @@ -256,24 +256,24 @@ contract FeesTest is Test, BaseTestHelper { // Settle rail1 (token1) again settlementHelper.settleRailAndVerify( rail1Id, - block.number, + uint64(block.number), rail1SecondExpectedAmount, - block.number - ); + uint64(block.number + )); // Settle rail2 (token2) again vm.prank(USER1); (uint256 secondSettledAmount2, , , , , ) = payments.settleRail( rail2Id, - block.number - ); + uint64(block.number + )); // Settle rail3 (token3) again vm.prank(USER1); (uint256 secondSettledAmount3, , , , , ) = payments.settleRail( rail3Id, - block.number - ); + uint64(block.number + )); // Calculate expected fees for second round - use actual settled amounts uint256 rail1SecondFee = (rail1SecondExpectedAmount * diff --git a/test/OperatorApproval.t.sol b/test/OperatorApproval.t.sol index 2ab358cf..284e8332 100644 --- a/test/OperatorApproval.t.sol +++ b/test/OperatorApproval.t.sol @@ -240,7 +240,7 @@ contract OperatorApprovalTest is Test, BaseTestHelper { vm.stopPrank(); // 1. Set initial lockup - uint256 lockupPeriod = 5; // 5 blocks + uint64 lockupPeriod = 5; // 5 blocks uint256 initialFixedLockup = 100 ether; vm.startPrank(OPERATOR); @@ -790,7 +790,7 @@ contract OperatorApprovalTest is Test, BaseTestHelper { // Set parameters for first rail uint256 rate1 = 10 ether; - uint256 lockupPeriod1 = 5; + uint64 lockupPeriod1 = 5; uint256 fixedLockup1 = 50 ether; vm.startPrank(OPERATOR); @@ -800,7 +800,7 @@ contract OperatorApprovalTest is Test, BaseTestHelper { // Set parameters for second rail uint256 rate2 = 15 ether; - uint256 lockupPeriod2 = 3; + uint64 lockupPeriod2 = 3; uint256 fixedLockup2 = 30 ether; vm.startPrank(OPERATOR); diff --git a/test/RailGetters.t.sol b/test/RailGetters.t.sol index 548fd95b..db306c58 100644 --- a/test/RailGetters.t.sol +++ b/test/RailGetters.t.sol @@ -311,7 +311,7 @@ contract PayeeRailsTest is Test, BaseTestHelper { ); // Get the endEpoch for Rail 3 - uint256 endEpoch; + uint64 endEpoch; for (uint256 i = 0; i < initialPayeeRails.length; i++) { if (initialPayeeRails[i].railId == rail3Id) { endEpoch = initialPayeeRails[i].endEpoch; diff --git a/test/RailSettlement.t.sol b/test/RailSettlement.t.sol index 9b605f6f..1f74b375 100644 --- a/test/RailSettlement.t.sol +++ b/test/RailSettlement.t.sol @@ -60,10 +60,10 @@ contract RailSettlementTest is Test, BaseTestHelper { settlementHelper.settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - block.number - ); + uint64(block.number + )); } function testSettleRailInDebt() public { @@ -88,18 +88,18 @@ contract RailSettlementTest is Test, BaseTestHelper { // First settlement settlementHelper.settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - expectedEpoch - ); + uint64(expectedEpoch + )); // Settle again - should be a no-op since we're already settled to the expected epoch settlementHelper.settleRailAndVerify( railId, - block.number, + uint64(block.number), 0, - expectedEpoch - ); + uint64(expectedEpoch + )); // Add more funds and settle again uint256 additionalDeposit = 300 ether; @@ -111,10 +111,10 @@ contract RailSettlementTest is Test, BaseTestHelper { // Third settlement settlementHelper.settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount2, - block.number - ); + uint64(block.number + )); } //-------------------------------- @@ -147,10 +147,10 @@ contract RailSettlementTest is Test, BaseTestHelper { RailSettlementHelpers.SettlementResult memory result = settlementHelper .settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - block.number - ); + uint64(block.number + )); // Verify arbiter note assertEq( @@ -198,9 +198,9 @@ contract RailSettlementTest is Test, BaseTestHelper { RailSettlementHelpers.SettlementResult memory result = settlementHelper .settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - block.number + uint64(block.number) ); // Verify arbiter note @@ -248,10 +248,10 @@ contract RailSettlementTest is Test, BaseTestHelper { RailSettlementHelpers.SettlementResult memory result = settlementHelper .settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - block.number - ); + uint64(block.number + )); // Verify accumulated fees increased correctly uint256 feesAfter = payments.accumulatedFees(address(token)); @@ -310,10 +310,10 @@ contract RailSettlementTest is Test, BaseTestHelper { RailSettlementHelpers.SettlementResult memory result = settlementHelper .settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount, - expectedSettledUpto - ); + uint64(expectedSettledUpto + )); // Verify arbiter note assertEq( @@ -347,7 +347,7 @@ contract RailSettlementTest is Test, BaseTestHelper { // Attempt settlement with malicious arbiter - should revert vm.prank(USER1); vm.expectRevert("arbiter settled beyond segment end"); - payments.settleRail(railId, block.number); + payments.settleRail(railId, uint64(block.number)); // Set the arbiter to return invalid amount but valid settlement duration arbiter.setMode(MockArbiter.ArbiterMode.CUSTOM_RETURN); @@ -355,7 +355,7 @@ contract RailSettlementTest is Test, BaseTestHelper { uint256 invalidAmount = proposedAmount * 2; // Double the correct amount arbiter.setCustomValues( invalidAmount, - block.number, + uint64(block.number), "Attempting excessive payment" ); @@ -364,7 +364,7 @@ contract RailSettlementTest is Test, BaseTestHelper { vm.expectRevert( "arbiter modified amount exceeds maximum for settled duration" ); - payments.settleRail(railId, block.number); + payments.settleRail(railId, uint64(block.number)); } //-------------------------------- @@ -373,7 +373,7 @@ contract RailSettlementTest is Test, BaseTestHelper { function testRailTerminationAndSettlement() public { uint256 rate = 10 ether; - uint256 lockupPeriod = 5; + uint64 lockupPeriod = 5; uint256 railId = helper.setupRailWithParameters( USER1, USER2, @@ -391,10 +391,10 @@ contract RailSettlementTest is Test, BaseTestHelper { uint256 expectedAmount1 = rate * 3; // 3 blocks * 10 ether settlementHelper.settleRailAndVerify( railId, - block.number, + uint64(block.number), expectedAmount1, - block.number - ); + uint64(block.number + )); // Terminate the rail vm.prank(OPERATOR); @@ -422,10 +422,9 @@ contract RailSettlementTest is Test, BaseTestHelper { // Final settlement after termination vm.prank(USER1); - (uint256 settledAmount, uint256 netPayeeAmount, uint256 paymentFee, uint256 totalOperatorCommission, uint256 settledUpto,) = - payments.settleRail(railId, block.number); + payments.settleRail(railId, uint64(block.number)); // Verify that total settled amount is equal to the sum of net payee amount, payment fee, and operator commission assertEq(settledAmount, netPayeeAmount + paymentFee + totalOperatorCommission, "Mismatch in settled amount breakdown"); @@ -483,7 +482,7 @@ contract RailSettlementTest is Test, BaseTestHelper { // Settle immediately without advancing blocks - should be a no-op RailSettlementHelpers.SettlementResult memory result = settlementHelper - .settleRailAndVerify(railId, block.number, 0, block.number); + .settleRailAndVerify(railId, uint64(block.number), 0, uint64(block.number)); console.log("result.note", result.note); @@ -523,7 +522,7 @@ contract RailSettlementTest is Test, BaseTestHelper { ); // Create rail with 2% operator commission (200 BPS) - uint256 operatorCommissionBps = 200; + uint16 operatorCommissionBps = 200; uint256 railId; vm.startPrank(OPERATOR); railId = payments.createRail( @@ -537,7 +536,7 @@ contract RailSettlementTest is Test, BaseTestHelper { // Set rail parameters using modify functions uint256 rate = 10 ether; - uint256 lockupPeriod = 5; + uint64 lockupPeriod = 5; vm.startPrank(OPERATOR); payments.modifyRailPayment(railId, rate, 0); payments.modifyRailLockup(railId, lockupPeriod, 0); // no fixed lockup @@ -564,7 +563,7 @@ contract RailSettlementTest is Test, BaseTestHelper { uint256 operatorCommission, uint256 settledUpto, - ) = payments.settleRail(railId, block.number); + ) = payments.settleRail(railId, uint64(block.number)); vm.stopPrank(); // --- Expected Calculations --- @@ -690,7 +689,7 @@ contract RailSettlementTest is Test, BaseTestHelper { vm.prank(USER1); (uint256 newSettledAmount, , uint256 newPaymentFee, , , ) = payments - .settleRail(railId, block.number); + .settleRail(railId, uint64(block.number)); uint256 expectedNewFee = (newSettledAmount * payments.PAYMENT_FEE_BPS()) / payments.COMMISSION_MAX_BPS(); diff --git a/test/RateChangeQueue.t.sol b/test/RateChangeQueue.t.sol index 58b0ff13..b980a847 100644 --- a/test/RateChangeQueue.t.sol +++ b/test/RateChangeQueue.t.sol @@ -26,7 +26,7 @@ contract RateChangeQueueTest is Test { function createSingleItemQueue( uint256 rate, - uint256 untilEpoch + uint64 untilEpoch ) internal returns (RateChangeQueue.RateChange memory) { createEmptyQueue(); RateChangeQueue.enqueue(queue(), rate, untilEpoch); @@ -36,7 +36,7 @@ contract RateChangeQueueTest is Test { function createMultiItemQueue( uint256[] memory rates, - uint256[] memory untilEpochs + uint64[] memory untilEpochs ) internal returns (RateChangeQueue.RateChange[] memory) { require( rates.length == untilEpochs.length, @@ -61,7 +61,7 @@ contract RateChangeQueueTest is Test { createEmptyQueue(); // Create cycles of filling and emptying - for (uint256 i = 0; i < cycles; i++) { + for (uint64 i = 0; i < cycles; i++) { // Fill with 3 items RateChangeQueue.enqueue(queue(), 100 + i, 5 + i); RateChangeQueue.enqueue(queue(), 200 + i, 6 + i); @@ -203,9 +203,10 @@ contract RateChangeQueueTest is Test { // Test with max uint values uint256 maxUint = type(uint256).max; + uint64 maxUint64 = type(uint64).max; RateChangeQueue.RateChange memory maxItem = createSingleItemQueue( maxUint, - maxUint + maxUint64 ); RateChangeQueue.RateChange memory peekedMax = RateChangeQueue.peek( queue() From 1591fd79351b36bc720a673f53b2e4537e702a38 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Fri, 9 May 2025 11:01:08 +0530 Subject: [PATCH 9/9] refactor settleRailAndVerify: unpack into named SettlementResult to fix 'stack too deep' --- test/helpers/RailSettlementHelpers.sol | 46 ++++++-------------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/test/helpers/RailSettlementHelpers.sol b/test/helpers/RailSettlementHelpers.sol index 39cd6f6e..ebf0fb13 100644 --- a/test/helpers/RailSettlementHelpers.sol +++ b/test/helpers/RailSettlementHelpers.sol @@ -149,39 +149,25 @@ contract RailSettlementHelpers is Test { console.log("payeeFundsBefore", payeeAccountBefore.funds); console.log("payeeLockupBefore", payeeAccountBefore.lockupCurrent); - uint256 settlementAmount; - uint256 netPayeeAmount; - uint256 paymentFee; - uint256 operatorCommission; - uint64 settledUpto; - string memory note; - vm.startPrank(payer); - ( - settlementAmount, - netPayeeAmount, - paymentFee, - operatorCommission, - settledUpto, - note - ) = payments.settleRail(railId, untilEpoch); + (result.totalAmount, result.netPayeeAmount, result.paymentFee, result.operatorCommission, result.settledUpto, result.note) = payments.settleRail(railId, untilEpoch); vm.stopPrank(); - console.log("settlementAmount", settlementAmount); - console.log("netPayeeAmount", netPayeeAmount); - console.log("paymentFee", paymentFee); - console.log("operatorCommission", operatorCommission); - console.log("settledUpto", settledUpto); - console.log("note", note); + console.log("settlementAmount", result.totalAmount); + console.log("netPayeeAmount", result.netPayeeAmount); + console.log("paymentFee", result.paymentFee); + console.log("operatorCommission", result.operatorCommission); + console.log("settledUpto", result.settledUpto); + console.log("note", result.note); // Verify results assertEq( - settlementAmount, + result.totalAmount, expectedAmount, "Settlement amount doesn't match expected" ); assertEq( - settledUpto, + result.settledUpto, expectedUpto, "Settled upto doesn't match expected" ); @@ -198,27 +184,17 @@ contract RailSettlementHelpers is Test { assertEq( payerAccountBefore.funds - payerAccountAfter.funds, - settlementAmount, + result.totalAmount, "Payer's balance reduction doesn't match settlement amount" ); assertEq( payeeAccountAfter.funds - payeeAccountBefore.funds, - netPayeeAmount, + result.netPayeeAmount, "Payee's balance increase doesn't match net payee amount" ); rail = payments.getRail(railId); assertEq(rail.settledUpTo, expectedUpto, "Rail settled upto incorrect"); - - return - SettlementResult( - settlementAmount, - netPayeeAmount, - paymentFee, - operatorCommission, - settledUpto, - note - ); } function terminateAndSettleRail(