From 282a39f4c653fb2e06b676852a8a53b25ba7eb23 Mon Sep 17 00:00:00 2001 From: Ruohan Date: Mon, 22 Jun 2026 15:41:55 -0700 Subject: [PATCH 1/6] replaced bps with feeAmount and added supporting logic --- src/AuthCaptureEscrow.sol | 57 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/AuthCaptureEscrow.sol b/src/AuthCaptureEscrow.sol index 9f374cf..4bc611c 100644 --- a/src/AuthCaptureEscrow.sol +++ b/src/AuthCaptureEscrow.sol @@ -41,9 +41,9 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { uint48 authorizationExpiry; /// @dev Timestamp when a successful payment can no longer be refunded uint48 refundExpiry; - /// @dev Minimum fee percentage in basis points + /// @dev Minimum fee rate in basis points; bounds the absolute fee at capture as amount * minFeeBps / 10_000 uint16 minFeeBps; - /// @dev Maximum fee percentage in basis points + /// @dev Maximum fee rate in basis points; bounds the absolute fee at capture as amount * maxFeeBps / 10_000 uint16 maxFeeBps; /// @dev Address that receives the fee portion of payments, if 0 then operator can set at capture address feeReceiver; @@ -80,7 +80,7 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { PaymentInfo paymentInfo, uint256 amount, address tokenCollector, - uint16 feeBps, + uint256 feeAmount, address feeReceiver ); @@ -90,7 +90,7 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { ); /// @notice Emitted when payment is captured from escrow - event PaymentCaptured(bytes32 indexed paymentInfoHash, uint256 amount, uint16 feeBps, address feeReceiver); + event PaymentCaptured(bytes32 indexed paymentInfoHash, uint256 amount, uint256 feeAmount, address feeReceiver); /// @notice Emitted when an authorized payment is voided, returning any escrowed funds to the payer event PaymentVoided(bytes32 indexed paymentInfoHash, uint256 amount); @@ -128,8 +128,8 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// @notice Fee bps range invalid due to min > max error InvalidFeeBpsRange(uint16 minFeeBps, uint16 maxFeeBps); - /// @notice Fee bps outside of allowed range - error FeeBpsOutOfRange(uint16 feeBps, uint16 minFeeBps, uint16 maxFeeBps); + /// @notice Fee amount outside of payer-approved bounds + error FeeAmountOutOfRange(uint256 feeAmount, uint256 minFee, uint256 maxFee); /// @notice Fee receiver is zero address with a non-zero fee error ZeroFeeReceiver(); @@ -195,21 +195,21 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// @param amount Amount to charge and capture /// @param tokenCollector Address of the token collector /// @param collectorData Data to pass to the token collector - /// @param feeBps Fee percentage to apply (must be within min/max range) + /// @param feeAmount Absolute fee in token units (must fall within payer-approved bounds) /// @param feeReceiver Address to receive fees (should match the paymentInfo.feeReceiver unless that is 0 in which case it can be any address) function charge( PaymentInfo calldata paymentInfo, uint256 amount, address tokenCollector, bytes calldata collectorData, - uint16 feeBps, + uint256 feeAmount, address feeReceiver ) external nonReentrant onlySender(paymentInfo.operator) validAmount(amount) { // Check payment info valid _validatePayment(paymentInfo, amount); // Check fee parameters valid - _validateFee(paymentInfo, feeBps, feeReceiver); + _validateFee(paymentInfo, amount, feeAmount, feeReceiver); // Check payment not already collected bytes32 paymentInfoHash = getHash(paymentInfo); @@ -218,13 +218,13 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { // Set payment state with refundable amount paymentState[paymentInfoHash] = PaymentState({hasCollectedPayment: true, capturableAmount: 0, refundableAmount: uint120(amount)}); - emit PaymentCharged(paymentInfoHash, paymentInfo, amount, tokenCollector, feeBps, feeReceiver); + emit PaymentCharged(paymentInfoHash, paymentInfo, amount, tokenCollector, feeAmount, feeReceiver); // Transfer tokens into escrow _collectTokens(paymentInfo, amount, tokenCollector, collectorData, TokenCollector.CollectorType.Payment); // Transfer tokens to receiver and fee receiver - _distributeTokens(paymentInfo.token, paymentInfo.receiver, amount, feeBps, feeReceiver); + _distributeTokens(paymentInfo.token, paymentInfo.receiver, amount, feeAmount, feeReceiver); } /// @notice Transfers funds from payer to escrow @@ -262,16 +262,16 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// /// @param paymentInfo PaymentInfo struct /// @param amount Amount to capture - /// @param feeBps Fee percentage to apply (must be within min/max range) + /// @param feeAmount Absolute fee in token units (must fall within payer-approved bounds) /// @param feeReceiver Address to receive fees (should match the paymentInfo.feeReceiver unless that is 0 in which case it can be any address) - function capture(PaymentInfo calldata paymentInfo, uint256 amount, uint16 feeBps, address feeReceiver) + function capture(PaymentInfo calldata paymentInfo, uint256 amount, uint256 feeAmount, address feeReceiver) external nonReentrant onlySender(paymentInfo.operator) validAmount(amount) { // Check fee parameters valid - _validateFee(paymentInfo, feeBps, feeReceiver); + _validateFee(paymentInfo, amount, feeAmount, feeReceiver); // Check before authorization expiry if (block.timestamp >= paymentInfo.authorizationExpiry) { @@ -289,10 +289,10 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { state.capturableAmount -= uint120(amount); state.refundableAmount += uint120(amount); paymentState[paymentInfoHash] = state; - emit PaymentCaptured(paymentInfoHash, amount, feeBps, feeReceiver); + emit PaymentCaptured(paymentInfoHash, amount, feeAmount, feeReceiver); // Transfer tokens to receiver and fee receiver - _distributeTokens(paymentInfo.token, paymentInfo.receiver, amount, feeBps, feeReceiver); + _distributeTokens(paymentInfo.token, paymentInfo.receiver, amount, feeAmount, feeReceiver); } /// @notice Permanently voids a payment authorization @@ -459,13 +459,11 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// @param token Token to transfer /// @param receiver Address to receive payment /// @param amount Total amount to split between payment and fees - /// @param feeBps Fee percentage in basis points + /// @param feeAmount Absolute fee in token units /// @param feeReceiver Address to receive fees - function _distributeTokens(address token, address receiver, uint256 amount, uint16 feeBps, address feeReceiver) + function _distributeTokens(address token, address receiver, uint256 amount, uint256 feeAmount, address feeReceiver) internal { - uint256 feeAmount = amount * feeBps / _MAX_FEE_BPS; - // Send fee portion if non-zero if (feeAmount > 0) _sendTokens(msg.sender, token, feeReceiver, feeAmount); @@ -507,18 +505,25 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// @notice Validates attempted fee adheres to constraints set by payment info /// /// @param paymentInfo PaymentInfo struct - /// @param feeBps Fee percentage in basis points + /// @param amount Capture or charge amount used to compute fee bounds + /// @param feeAmount Absolute fee in token units /// @param feeReceiver Address to receive fees - function _validateFee(PaymentInfo calldata paymentInfo, uint16 feeBps, address feeReceiver) internal pure { + function _validateFee(PaymentInfo calldata paymentInfo, uint256 amount, uint256 feeAmount, address feeReceiver) + internal + pure + { uint16 minFeeBps = paymentInfo.minFeeBps; uint16 maxFeeBps = paymentInfo.maxFeeBps; address configuredFeeReceiver = paymentInfo.feeReceiver; - // Check fee bps within [min, max] - if (feeBps < minFeeBps || feeBps > maxFeeBps) revert FeeBpsOutOfRange(feeBps, minFeeBps, maxFeeBps); + uint256 minFee = amount * minFeeBps / _MAX_FEE_BPS; + uint256 maxFee = amount * maxFeeBps / _MAX_FEE_BPS; + + // Check fee amount within payer-approved bounds + if (feeAmount < minFee || feeAmount > maxFee) revert FeeAmountOutOfRange(feeAmount, minFee, maxFee); - // Check fee recipient only zero address if zero fee bps - if (feeReceiver == address(0) && feeBps > 0) revert ZeroFeeReceiver(); + // Check fee recipient only zero address if zero fee + if (feeReceiver == address(0) && feeAmount > 0) revert ZeroFeeReceiver(); // Check fee receiver matches payment info if non-zero if (configuredFeeReceiver != address(0) && configuredFeeReceiver != feeReceiver) { From 6be9835c5b5cffea0962763cdcf3714a8626e0ef Mon Sep 17 00:00:00 2001 From: Ruohan Date: Mon, 22 Jun 2026 15:45:21 -0700 Subject: [PATCH 2/6] Update tests for absolute feeAmount capture and charge API. Co-authored-by: Cursor --- test/base/AuthCaptureEscrowBase.sol | 4 + test/gas/gasBenchmark.t.sol | 9 +- test/src/PaymentEscrow/capture.t.sol | 110 +++++++++++------- test/src/PaymentEscrow/charge.t.sol | 90 +++++++------- .../e2eCoinbaseSmartWallet.t.sol | 8 +- test/src/PaymentEscrow/reentrancy.t.sol | 4 +- test/src/PaymentEscrow/refund.t.sol | 6 +- 7 files changed, 136 insertions(+), 95 deletions(-) diff --git a/test/base/AuthCaptureEscrowBase.sol b/test/base/AuthCaptureEscrowBase.sol index 4d9c248..411d80c 100644 --- a/test/base/AuthCaptureEscrowBase.sol +++ b/test/base/AuthCaptureEscrowBase.sol @@ -203,4 +203,8 @@ contract AuthCaptureEscrowBase is Test, DeployPermit2 { paymentInfo.payer = payer; return hash; } + + function _feeAmount(uint256 amount, uint16 feeBps) internal pure returns (uint256) { + return amount * feeBps / 10_000; + } } diff --git a/test/gas/gasBenchmark.t.sol b/test/gas/gasBenchmark.t.sol index 6117916..6bcf057 100644 --- a/test/gas/gasBenchmark.t.sol +++ b/test/gas/gasBenchmark.t.sol @@ -8,6 +8,7 @@ import {AuthCaptureEscrowBase} from "../base/AuthCaptureEscrowBase.sol"; contract GasBenchmarkBase is AuthCaptureEscrowBase { uint120 internal constant BENCHMARK_AMOUNT = 100e6; uint16 internal constant BENCHMARK_FEE_BPS = 100; // 1% + uint256 internal constant BENCHMARK_FEE_AMOUNT = BENCHMARK_AMOUNT * BENCHMARK_FEE_BPS / 10_000; AuthCaptureEscrow.PaymentInfo internal paymentInfo; bytes internal signature; @@ -22,7 +23,7 @@ contract GasBenchmarkBase is AuthCaptureEscrowBase { mockERC3009Token.mint(payerEOA, 1e6); vm.startPrank(operator); authCaptureEscrow.authorize(warmupInfo, 1e6, address(erc3009PaymentCollector), warmupSignature); - authCaptureEscrow.capture(warmupInfo, 1e6, BENCHMARK_FEE_BPS, feeReceiver); // make sure token store is deployed before subsequent tests + authCaptureEscrow.capture(warmupInfo, 1e6, 1e6 * BENCHMARK_FEE_BPS / 10_000, feeReceiver); // make sure token store is deployed before subsequent tests vm.stopPrank(); // Create and sign payment info @@ -45,7 +46,7 @@ contract ChargeGasBenchmark is GasBenchmarkBase { function test_charge_benchmark() public { vm.prank(operator); authCaptureEscrow.charge( - paymentInfo, BENCHMARK_AMOUNT, address(erc3009PaymentCollector), signature, BENCHMARK_FEE_BPS, feeReceiver + paymentInfo, BENCHMARK_AMOUNT, address(erc3009PaymentCollector), signature, BENCHMARK_FEE_AMOUNT, feeReceiver ); } } @@ -61,7 +62,7 @@ contract CaptureGasBenchmark is GasBenchmarkBase { function test_capture_benchmark() public { vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, BENCHMARK_AMOUNT, BENCHMARK_FEE_BPS, feeReceiver); + authCaptureEscrow.capture(paymentInfo, BENCHMARK_AMOUNT, BENCHMARK_FEE_AMOUNT, feeReceiver); } } @@ -106,7 +107,7 @@ contract RefundGasBenchmark is GasBenchmarkBase { vm.prank(operator); authCaptureEscrow.authorize(paymentInfo, BENCHMARK_AMOUNT, address(erc3009PaymentCollector), signature); vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, BENCHMARK_AMOUNT, BENCHMARK_FEE_BPS, feeReceiver); + authCaptureEscrow.capture(paymentInfo, BENCHMARK_AMOUNT, BENCHMARK_FEE_AMOUNT, feeReceiver); // Give operator tokens for refund and approve collector mockERC3009Token.mint(operator, BENCHMARK_AMOUNT); diff --git a/test/src/PaymentEscrow/capture.t.sol b/test/src/PaymentEscrow/capture.t.sol index 76b7c1e..f018c8f 100644 --- a/test/src/PaymentEscrow/capture.t.sol +++ b/test/src/PaymentEscrow/capture.t.sol @@ -25,7 +25,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(sender); vm.expectRevert(abi.encodeWithSelector(AuthCaptureEscrow.InvalidSender.selector, sender, paymentInfo.operator)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); } function test_reverts_whenValueIsZero() public { @@ -33,7 +33,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert(AuthCaptureEscrow.ZeroAmount.selector); - authCaptureEscrow.capture(paymentInfo, 0, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, 0, _feeAmount(1, FEE_BPS), paymentInfo.feeReceiver); } function test_reverts_whenAmountOverflows(uint256 overflowValue) public { @@ -45,7 +45,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.expectRevert( abi.encodeWithSelector(AuthCaptureEscrow.AmountOverflow.selector, overflowValue, type(uint120).max) ); - authCaptureEscrow.capture(paymentInfo, overflowValue, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, overflowValue, _feeAmount(1, FEE_BPS), paymentInfo.feeReceiver); } function test_reverts_whenAfterAuthorizationExpiry( @@ -76,7 +76,7 @@ contract CaptureTest is AuthCaptureEscrowBase { AuthCaptureEscrow.AfterAuthorizationExpiry.selector, block.timestamp, authorizationExpiry ) ); - authCaptureEscrow.capture(paymentInfo, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); } function test_reverts_whenInsufficientAuthorization(uint120 authorizedAmount) public { @@ -102,7 +102,7 @@ contract CaptureTest is AuthCaptureEscrowBase { AuthCaptureEscrow.InsufficientAuthorization.selector, paymentInfoHash, authorizedAmount, captureAmount ) ); - authCaptureEscrow.capture(paymentInfo, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); } function test_reverts_receiverSender(uint120 authorizedAmount) public { @@ -123,7 +123,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.expectRevert( abi.encodeWithSelector(AuthCaptureEscrow.InvalidSender.selector, paymentInfo.receiver, paymentInfo.operator) ); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); } function test_succeeds_withFullAmount(uint120 authorizedAmount) public { @@ -144,7 +144,7 @@ contract CaptureTest is AuthCaptureEscrowBase { // Then capture the full amount vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); // Verify balances assertEq(mockERC3009Token.balanceOf(receiver), receiverExpectedBalance); @@ -171,7 +171,7 @@ contract CaptureTest is AuthCaptureEscrowBase { // Then capture partial amount vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); // Verify balances and state address operatorTokenStore = authCaptureEscrow.getTokenStore(operator); @@ -197,11 +197,11 @@ contract CaptureTest is AuthCaptureEscrowBase { // First capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, firstCaptureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, firstCaptureAmount, _feeAmount(firstCaptureAmount, FEE_BPS), paymentInfo.feeReceiver); // Second capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, secondCaptureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, secondCaptureAmount, _feeAmount(secondCaptureAmount, FEE_BPS), paymentInfo.feeReceiver); // Calculate fees for each capture separately to match contract behavior uint256 firstFeesAmount = firstCaptureAmount * FEE_BPS / 10_000; @@ -235,25 +235,27 @@ contract CaptureTest is AuthCaptureEscrowBase { // Record expected event vm.expectEmit(true, false, false, true); emit AuthCaptureEscrow.PaymentCaptured( - paymentInfoHash, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver + paymentInfoHash, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver ); // Execute capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); } - function test_reverts_whenFeeBpsBelowMin( + function test_reverts_whenFeeAmountBelowMin( uint120 authorizedAmount, uint16 minFeeBps, - uint16 maxFeeBps, - uint16 captureFeeBps + uint16 maxFeeBps ) public { - // Assume reasonable bounds for fees vm.assume(authorizedAmount > 0); - vm.assume(minFeeBps > 0 && minFeeBps <= 5000); // Max 50% + vm.assume(minFeeBps > 0 && minFeeBps <= 5000); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 5000); - vm.assume(captureFeeBps < minFeeBps); // Must be below min to trigger revert + + uint256 minFee = _feeAmount(authorizedAmount, minFeeBps); + vm.assume(minFee > 0); + uint256 captureFeeAmount = minFee - 1; + uint256 maxFee = _feeAmount(authorizedAmount, maxFeeBps); mockERC3009Token.mint(payerEOA, authorizedAmount); @@ -268,23 +270,26 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector(AuthCaptureEscrow.FeeBpsOutOfRange.selector, captureFeeBps, minFeeBps, maxFeeBps) + abi.encodeWithSelector( + AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee + ) ); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, paymentInfo.feeReceiver); } - function test_reverts_whenFeeBpsAboveMax( + function test_reverts_whenFeeAmountAboveMax( uint120 authorizedAmount, uint16 minFeeBps, - uint16 maxFeeBps, - uint16 captureFeeBps + uint16 maxFeeBps ) public { - // Assume reasonable bounds for fees vm.assume(authorizedAmount > 0); - vm.assume(minFeeBps <= 5000); // Max 50% + vm.assume(minFeeBps <= 5000); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 5000); - vm.assume(captureFeeBps > maxFeeBps); // Must be above max to trigger revert - vm.assume(captureFeeBps <= 10_000); // But still within uint16 reasonable bounds + + uint256 minFee = _feeAmount(authorizedAmount, minFeeBps); + uint256 maxFee = _feeAmount(authorizedAmount, maxFeeBps); + vm.assume(maxFee < type(uint256).max); + uint256 captureFeeAmount = maxFee + 1; mockERC3009Token.mint(payerEOA, authorizedAmount); @@ -299,9 +304,11 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector(AuthCaptureEscrow.FeeBpsOutOfRange.selector, captureFeeBps, minFeeBps, maxFeeBps) + abi.encodeWithSelector( + AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee + ) ); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, paymentInfo.feeReceiver); } function test_reverts_whenFeeReceiverInvalid( @@ -311,7 +318,6 @@ contract CaptureTest is AuthCaptureEscrowBase { uint16 captureFeeBps, address invalidFeeReceiver ) public { - // Assume reasonable bounds for fees vm.assume(authorizedAmount > 0); vm.assume(minFeeBps > 0); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps < 10000); @@ -319,6 +325,8 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.assume(invalidFeeReceiver != address(0)); vm.assume(invalidFeeReceiver != feeReceiver); + uint256 captureFeeAmount = _feeAmount(authorizedAmount, captureFeeBps); + mockERC3009Token.mint(payerEOA, authorizedAmount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo(payerEOA, authorizedAmount); @@ -335,7 +343,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.expectRevert( abi.encodeWithSelector(AuthCaptureEscrow.InvalidFeeReceiver.selector, invalidFeeReceiver, feeReceiver) ); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeBps, invalidFeeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, invalidFeeReceiver); } function test_reverts_ifSendTokensReverts_undeployedTokenStore(uint120 authorizedAmount, bytes calldata revertData) @@ -358,7 +366,7 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(MockRevertOnTransferToken.CustomRevert.selector, revertData)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); } @@ -376,7 +384,7 @@ contract CaptureTest is AuthCaptureEscrowBase { authorizedAmount, address(erc3009PaymentCollector), initialSignature, - initialPaymentInfo.minFeeBps, + _feeAmount(authorizedAmount, FEE_BPS), initialPaymentInfo.feeReceiver ); @@ -397,7 +405,7 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(MockRevertOnTransferToken.CustomRevert.selector, revertData)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); } @@ -418,7 +426,7 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, revertingToken)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); } @@ -447,11 +455,35 @@ contract CaptureTest is AuthCaptureEscrowBase { address newFeeRecipient = address(0xdead); + uint256 captureFeeAmount = _feeAmount(authorizedAmount, captureFeeBps); + + vm.prank(operator); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, newFeeRecipient); + + assertEq(mockERC3009Token.balanceOf(newFeeRecipient), captureFeeAmount); + assertEq(mockERC3009Token.balanceOf(receiver), authorizedAmount - captureFeeAmount); + } + + /// @dev Capture $54.23 with 3% + $0.30 flat fee rounded to $1.93 (cent-aligned in 6-decimal USDC) + function test_succeeds_withCentAlignedFeeAmount() public { + uint120 authorizedAmount = 54_230_000; // $54.23 + uint256 centAlignedFee = 1_930_000; // $1.93 + + mockERC3009Token.mint(payerEOA, authorizedAmount); + + AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo(payerEOA, authorizedAmount); + paymentInfo.minFeeBps = 300; // 3% lower bound + paymentInfo.maxFeeBps = 400; // 4% upper bound accommodates 3% + $0.30 flat + + bytes memory signature = _signERC3009ReceiveWithAuthorizationStruct(paymentInfo, payer_EOA_PK); + + vm.prank(operator); + authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); + vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeBps, newFeeRecipient); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, centAlignedFee, paymentInfo.feeReceiver); - uint256 feeAmount = (uint256(authorizedAmount) * uint256(captureFeeBps)) / 10_000; - assertEq(mockERC3009Token.balanceOf(newFeeRecipient), feeAmount); - assertEq(mockERC3009Token.balanceOf(receiver), authorizedAmount - feeAmount); + assertEq(mockERC3009Token.balanceOf(feeReceiver), centAlignedFee); + assertEq(mockERC3009Token.balanceOf(receiver), authorizedAmount - centAlignedFee); } } diff --git a/test/src/PaymentEscrow/charge.t.sol b/test/src/PaymentEscrow/charge.t.sol index 8a59a23..e36a309 100644 --- a/test/src/PaymentEscrow/charge.t.sol +++ b/test/src/PaymentEscrow/charge.t.sol @@ -13,7 +13,7 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert(AuthCaptureEscrow.ZeroAmount.selector); authCaptureEscrow.charge( - paymentInfo, 0, address(erc3009PaymentCollector), signature, paymentInfo.minFeeBps, paymentInfo.feeReceiver + paymentInfo, 0, address(erc3009PaymentCollector), signature, _feeAmount(1, FEE_BPS), paymentInfo.feeReceiver ); } @@ -33,7 +33,7 @@ contract ChargeTest is AuthCaptureEscrowBase { overflowValue, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(overflowValue, FEE_BPS), paymentInfo.feeReceiver ); } @@ -58,7 +58,7 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -81,7 +81,7 @@ contract ChargeTest is AuthCaptureEscrowBase { chargeAmount, address(erc3009PaymentCollector), "", - paymentInfo.minFeeBps, + _feeAmount(chargeAmount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -113,7 +113,7 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -150,7 +150,7 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -187,7 +187,7 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -214,7 +214,7 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); } @@ -237,11 +237,11 @@ contract ChargeTest is AuthCaptureEscrowBase { amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, FEE_BPS), paymentInfo.feeReceiver ); - uint256 feeAmount = amount * FEE_BPS / 10_000; + uint256 feeAmount = _feeAmount(amount, FEE_BPS); assertEq(mockERC3009Token.balanceOf(receiver), amount - feeAmount); assertEq(mockERC3009Token.balanceOf(feeReceiver), feeAmount); assertEq(mockERC3009Token.balanceOf(payerEOA), payerBalanceBefore - amount); @@ -267,11 +267,11 @@ contract ChargeTest is AuthCaptureEscrowBase { chargeAmount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(chargeAmount, FEE_BPS), paymentInfo.feeReceiver ); - uint256 feeAmount = chargeAmount * FEE_BPS / 10_000; + uint256 feeAmount = _feeAmount(chargeAmount, FEE_BPS); assertEq(mockERC3009Token.balanceOf(receiver), chargeAmount - feeAmount); assertEq(mockERC3009Token.balanceOf(feeReceiver), feeAmount); assertEq(mockERC3009Token.balanceOf(payerEOA), payerBalanceBefore - chargeAmount); @@ -297,7 +297,7 @@ contract ChargeTest is AuthCaptureEscrowBase { paymentInfo, valueToCharge, address(erc3009PaymentCollector), - paymentInfo.minFeeBps, + _feeAmount(valueToCharge, FEE_BPS), paymentInfo.feeReceiver ); @@ -308,7 +308,7 @@ contract ChargeTest is AuthCaptureEscrowBase { valueToCharge, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(valueToCharge, FEE_BPS), paymentInfo.feeReceiver ); } @@ -333,7 +333,7 @@ contract ChargeTest is AuthCaptureEscrowBase { chargeAmount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(chargeAmount, FEE_BPS), paymentInfo.feeReceiver ); @@ -362,14 +362,15 @@ contract ChargeTest is AuthCaptureEscrowBase { authCaptureEscrow.refund(paymentInfo, chargeAmount, address(operatorRefundCollector), ""); } - function test_reverts_whenFeeBpsBelowMin(uint120 amount, uint16 minFeeBps, uint16 maxFeeBps, uint16 captureFeeBps) - public - { - // Assume reasonable bounds for fees + function test_reverts_whenFeeAmountBelowMin(uint120 amount, uint16 minFeeBps, uint16 maxFeeBps) public { vm.assume(amount > 0); vm.assume(minFeeBps > 0); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps < 10000); - vm.assume(captureFeeBps < minFeeBps); // Must be below min to trigger revert + + uint256 minFee = _feeAmount(amount, minFeeBps); + vm.assume(minFee > 0); + uint256 captureFeeAmount = minFee - 1; + uint256 maxFee = _feeAmount(amount, maxFeeBps); mockERC3009Token.mint(payerEOA, amount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = @@ -381,24 +382,24 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector(AuthCaptureEscrow.FeeBpsOutOfRange.selector, captureFeeBps, minFeeBps, maxFeeBps) + abi.encodeWithSelector( + AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee + ) ); authCaptureEscrow.charge( - paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeBps, paymentInfo.feeReceiver + paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, paymentInfo.feeReceiver ); } - function test_charge_reverts_whenFeeBpsAboveMax( - uint120 amount, - uint16 minFeeBps, - uint16 maxFeeBps, - uint16 captureFeeBps - ) public { - // Assume reasonable bounds for fees + function test_charge_reverts_whenFeeAmountAboveMax(uint120 amount, uint16 minFeeBps, uint16 maxFeeBps) public { vm.assume(amount > 0); - vm.assume(maxFeeBps < 10000); // Keep maxFeeBps within valid range + vm.assume(maxFeeBps < 10000); vm.assume(minFeeBps <= maxFeeBps); - vm.assume(captureFeeBps > maxFeeBps && captureFeeBps <= 10000); // Must be above max but within bounds + + uint256 minFee = _feeAmount(amount, minFeeBps); + uint256 maxFee = _feeAmount(amount, maxFeeBps); + vm.assume(maxFee < type(uint256).max); + uint256 captureFeeAmount = maxFee + 1; AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({payer: payerEOA, maxAmount: amount, token: address(mockERC3009Token)}); @@ -409,10 +410,12 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector(AuthCaptureEscrow.FeeBpsOutOfRange.selector, captureFeeBps, minFeeBps, maxFeeBps) + abi.encodeWithSelector( + AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee + ) ); authCaptureEscrow.charge( - paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeBps, paymentInfo.feeReceiver + paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, paymentInfo.feeReceiver ); } @@ -423,9 +426,11 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.assume(minFeeBps > 0); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 10000); + uint256 captureFeeAmount = _feeAmount(amount, minFeeBps); + AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({payer: payerEOA, maxAmount: amount, token: address(mockERC3009Token)}); - paymentInfo.feeReceiver = address(0); // Allow operator to set fee recipient + paymentInfo.feeReceiver = address(0); paymentInfo.minFeeBps = minFeeBps; paymentInfo.maxFeeBps = maxFeeBps; @@ -434,7 +439,7 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert(AuthCaptureEscrow.ZeroFeeReceiver.selector); authCaptureEscrow.charge( - paymentInfo, amount, address(erc3009PaymentCollector), signature, minFeeBps, address(0) + paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, address(0) ); } @@ -445,15 +450,13 @@ contract ChargeTest is AuthCaptureEscrowBase { uint16 captureFeeBps, address newFeeRecipient ) public { - // Assume reasonable bounds for fees vm.assume(amount > 0); vm.assume(minFeeBps > 0); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 10000); - vm.assume(captureFeeBps >= minFeeBps && captureFeeBps <= maxFeeBps); // Must be within range + vm.assume(captureFeeBps >= minFeeBps && captureFeeBps <= maxFeeBps); mockERC3009Token.mint(payerEOA, amount); - // Ensure newFeeRecipient is not zero address or other special addresses assumePayable(newFeeRecipient); vm.assume(newFeeRecipient != address(0)); vm.assume(newFeeRecipient != address(authCaptureEscrow)); @@ -461,19 +464,20 @@ contract ChargeTest is AuthCaptureEscrowBase { AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({payer: payerEOA, maxAmount: amount, token: address(mockERC3009Token)}); - paymentInfo.feeReceiver = address(0); // Allow operator to set fee recipient + paymentInfo.feeReceiver = address(0); paymentInfo.minFeeBps = minFeeBps; paymentInfo.maxFeeBps = maxFeeBps; bytes memory signature = _signERC3009ReceiveWithAuthorizationStruct(paymentInfo, payer_EOA_PK); + uint256 captureFeeAmount = _feeAmount(amount, captureFeeBps); + vm.prank(operator); authCaptureEscrow.charge( - paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeBps, newFeeRecipient + paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, newFeeRecipient ); - uint256 feeAmount = (uint256(amount) * uint256(captureFeeBps)) / 10_000; - assertEq(mockERC3009Token.balanceOf(newFeeRecipient), feeAmount); - assertEq(mockERC3009Token.balanceOf(receiver), amount - feeAmount); + assertEq(mockERC3009Token.balanceOf(newFeeRecipient), captureFeeAmount); + assertEq(mockERC3009Token.balanceOf(receiver), amount - captureFeeAmount); } } diff --git a/test/src/PaymentEscrow/e2eCoinbaseSmartWallet.t.sol b/test/src/PaymentEscrow/e2eCoinbaseSmartWallet.t.sol index 328fd22..2024ce4 100644 --- a/test/src/PaymentEscrow/e2eCoinbaseSmartWallet.t.sol +++ b/test/src/PaymentEscrow/e2eCoinbaseSmartWallet.t.sol @@ -25,11 +25,11 @@ contract AuthCaptureEscrowSmartWalletE2ETest is AuthCaptureEscrowSmartWalletBase amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, paymentInfo.minFeeBps), paymentInfo.feeReceiver ); - uint256 feeAmount = uint256(amount) * FEE_BPS / 10_000; + uint256 feeAmount = _feeAmount(amount, FEE_BPS); assertEq(mockERC3009Token.balanceOf(receiver), amount - feeAmount); assertEq(mockERC3009Token.balanceOf(feeReceiver), feeAmount); } @@ -57,11 +57,11 @@ contract AuthCaptureEscrowSmartWalletE2ETest is AuthCaptureEscrowSmartWalletBase amount, address(erc3009PaymentCollector), signature, - paymentInfo.minFeeBps, + _feeAmount(amount, paymentInfo.minFeeBps), paymentInfo.feeReceiver ); - uint256 feeAmount = uint256(amount) * FEE_BPS / 10_000; + uint256 feeAmount = _feeAmount(amount, FEE_BPS); assertEq(mockERC3009Token.balanceOf(receiver), amount - feeAmount); assertEq(mockERC3009Token.balanceOf(feeReceiver), feeAmount); } diff --git a/test/src/PaymentEscrow/reentrancy.t.sol b/test/src/PaymentEscrow/reentrancy.t.sol index 36650fe..239d0c6 100644 --- a/test/src/PaymentEscrow/reentrancy.t.sol +++ b/test/src/PaymentEscrow/reentrancy.t.sol @@ -62,10 +62,10 @@ contract ReentrancyApproveTest is AuthCaptureEscrowSmartWalletBase { console.log("After authorize attempt"); vm.expectRevert(); // expect revert because authorize never happened - authCaptureEscrow.capture(paymentInfo, 10 ether, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, 10 ether, 0, paymentInfo.feeReceiver); paymentInfo.salt += 1; // set up the second unique paymentInfo vm.expectRevert(); // expect revert because we've fixed the reentrancy - authCaptureEscrow.capture(paymentInfo, 10 ether, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, 10 ether, 0, paymentInfo.feeReceiver); vm.stopPrank(); console.log("After attack Attacker Balance:", mockERC3009Token.balanceOf(attacker)); diff --git a/test/src/PaymentEscrow/refund.t.sol b/test/src/PaymentEscrow/refund.t.sol index f2f9396..63c4903 100644 --- a/test/src/PaymentEscrow/refund.t.sol +++ b/test/src/PaymentEscrow/refund.t.sol @@ -71,7 +71,7 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture partial amount vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, captureAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); // Fund operator for refund @@ -100,7 +100,7 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture the payment vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); // Fund the operator for refund @@ -136,7 +136,7 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture the payment vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, paymentInfo.minFeeBps, paymentInfo.feeReceiver); + authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); vm.stopPrank(); // Fund operator for refund From 0dcb6ffe40e48f96722f44604c949ef0b1a80cf2 Mon Sep 17 00:00:00 2001 From: Ruohan Date: Mon, 22 Jun 2026 15:56:34 -0700 Subject: [PATCH 3/6] testing config fixes --- test/gas/gasBenchmark.t.sol | 4 ++-- test/src/PaymentEscrow/charge.t.sol | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/gas/gasBenchmark.t.sol b/test/gas/gasBenchmark.t.sol index 6bcf057..fc36452 100644 --- a/test/gas/gasBenchmark.t.sol +++ b/test/gas/gasBenchmark.t.sol @@ -8,7 +8,7 @@ import {AuthCaptureEscrowBase} from "../base/AuthCaptureEscrowBase.sol"; contract GasBenchmarkBase is AuthCaptureEscrowBase { uint120 internal constant BENCHMARK_AMOUNT = 100e6; uint16 internal constant BENCHMARK_FEE_BPS = 100; // 1% - uint256 internal constant BENCHMARK_FEE_AMOUNT = BENCHMARK_AMOUNT * BENCHMARK_FEE_BPS / 10_000; + uint256 internal constant BENCHMARK_FEE_AMOUNT = uint256(BENCHMARK_AMOUNT) * uint256(BENCHMARK_FEE_BPS) / 10_000; AuthCaptureEscrow.PaymentInfo internal paymentInfo; bytes internal signature; @@ -23,7 +23,7 @@ contract GasBenchmarkBase is AuthCaptureEscrowBase { mockERC3009Token.mint(payerEOA, 1e6); vm.startPrank(operator); authCaptureEscrow.authorize(warmupInfo, 1e6, address(erc3009PaymentCollector), warmupSignature); - authCaptureEscrow.capture(warmupInfo, 1e6, 1e6 * BENCHMARK_FEE_BPS / 10_000, feeReceiver); // make sure token store is deployed before subsequent tests + authCaptureEscrow.capture(warmupInfo, 1e6, (uint256(1_000_000) * uint256(BENCHMARK_FEE_BPS)) / 10_000, feeReceiver); // make sure token store is deployed before subsequent tests vm.stopPrank(); // Create and sign payment info diff --git a/test/src/PaymentEscrow/charge.t.sol b/test/src/PaymentEscrow/charge.t.sol index e36a309..7b4fd01 100644 --- a/test/src/PaymentEscrow/charge.t.sol +++ b/test/src/PaymentEscrow/charge.t.sol @@ -33,7 +33,7 @@ contract ChargeTest is AuthCaptureEscrowBase { overflowValue, address(erc3009PaymentCollector), signature, - _feeAmount(overflowValue, FEE_BPS), + _feeAmount(1, FEE_BPS), paymentInfo.feeReceiver ); } @@ -426,7 +426,10 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.assume(minFeeBps > 0); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 10000); - uint256 captureFeeAmount = _feeAmount(amount, minFeeBps); + uint256 minFee = _feeAmount(amount, minFeeBps); + uint256 maxFee = _feeAmount(amount, maxFeeBps); + vm.assume(maxFee > 0); + uint256 captureFeeAmount = minFee > 0 ? minFee : maxFee; AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({payer: payerEOA, maxAmount: amount, token: address(mockERC3009Token)}); From f4b3bed559d6f821cfb413984e2a3d367922500b Mon Sep 17 00:00:00 2001 From: Ruohan Date: Tue, 23 Jun 2026 09:59:07 -0700 Subject: [PATCH 4/6] Add ci Foundry profile and apply forge fmt --- foundry.toml | 6 ++ src/AuthCaptureEscrow.sol | 7 +- src/collectors/ERC3009PaymentCollector.sol | 3 +- src/collectors/Permit2PaymentCollector.sol | 4 +- src/interfaces/IMulticall3.sol | 5 +- test/base/AuthCaptureEscrowBase.sol | 5 +- .../base/AuthCaptureEscrowSmartWalletBase.sol | 6 +- test/gas/gasBenchmark.t.sol | 11 ++- test/src/PaymentEscrow/authorize.t.sol | 4 +- test/src/PaymentEscrow/capture.t.sol | 68 +++++++++++-------- test/src/PaymentEscrow/charge.t.sol | 8 +-- test/src/PaymentEscrow/refund.t.sol | 12 +++- .../SpendPermissionPaymentCollector.t.sol | 12 +--- 13 files changed, 82 insertions(+), 69 deletions(-) diff --git a/foundry.toml b/foundry.toml index 90dbd61..831be66 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,6 +4,12 @@ out = "out" libs = ["lib"] evm_version = "cancun" +[profile.ci] +src = "src" +out = "out" +libs = ["lib"] +evm_version = "cancun" + [profile.deploy] src = "src" out = "out" diff --git a/src/AuthCaptureEscrow.sol b/src/AuthCaptureEscrow.sol index 4bc611c..6068120 100644 --- a/src/AuthCaptureEscrow.sol +++ b/src/AuthCaptureEscrow.sol @@ -392,9 +392,7 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { /// @return The operator's token store address function getTokenStore(address operator) public view returns (address) { return LibClone.predictDeterministicAddress({ - implementation: tokenStoreImplementation, - salt: bytes32(bytes20(operator)), - deployer: address(this) + implementation: tokenStoreImplementation, salt: bytes32(bytes20(operator)), deployer: address(this) }); } @@ -440,8 +438,7 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { } else if (tokenStore.code.length == 0) { // Call failed from undeployed TokenStore, deploy and try again tokenStore = LibClone.cloneDeterministic({ - implementation: tokenStoreImplementation, - salt: bytes32(bytes20(operator)) + implementation: tokenStoreImplementation, salt: bytes32(bytes20(operator)) }); emit TokenStoreCreated(operator, tokenStore); TokenStore(tokenStore).sendTokens(token, recipient, amount); diff --git a/src/collectors/ERC3009PaymentCollector.sol b/src/collectors/ERC3009PaymentCollector.sol index f143868..a0a455d 100644 --- a/src/collectors/ERC3009PaymentCollector.sol +++ b/src/collectors/ERC3009PaymentCollector.sol @@ -39,7 +39,8 @@ contract ERC3009PaymentCollector is TokenCollector, ERC6492SignatureHandler { uint256 maxAmount = paymentInfo.maxAmount; // Pull tokens into this contract - IERC3009(token).receiveWithAuthorization({ + IERC3009(token) + .receiveWithAuthorization({ from: payer, to: address(this), value: maxAmount, diff --git a/src/collectors/Permit2PaymentCollector.sol b/src/collectors/Permit2PaymentCollector.sol index e4ced64..7cb604d 100644 --- a/src/collectors/Permit2PaymentCollector.sol +++ b/src/collectors/Permit2PaymentCollector.sol @@ -42,7 +42,9 @@ contract Permit2PaymentCollector is TokenCollector, ERC6492SignatureHandler { ) internal override { permit2.permitTransferFrom({ permit: ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({token: paymentInfo.token, amount: paymentInfo.maxAmount}), + permitted: ISignatureTransfer.TokenPermissions({ + token: paymentInfo.token, amount: paymentInfo.maxAmount + }), nonce: uint256(_getHashPayerAgnostic(paymentInfo)), deadline: paymentInfo.preApprovalExpiry }), diff --git a/src/interfaces/IMulticall3.sol b/src/interfaces/IMulticall3.sol index 57a27ea..f232db1 100644 --- a/src/interfaces/IMulticall3.sol +++ b/src/interfaces/IMulticall3.sol @@ -26,10 +26,7 @@ interface IMulticall3 { bytes returnData; } - function aggregate(Call[] calldata calls) - external - payable - returns (uint256 blockNumber, bytes[] memory returnData); + function aggregate(Call[] calldata calls) external payable returns (uint256 blockNumber, bytes[] memory returnData); function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); diff --git a/test/base/AuthCaptureEscrowBase.sol b/test/base/AuthCaptureEscrowBase.sol index 411d80c..304889e 100644 --- a/test/base/AuthCaptureEscrowBase.sol +++ b/test/base/AuthCaptureEscrowBase.sol @@ -161,8 +161,9 @@ contract AuthCaptureEscrowBase is Test, DeployPermit2 { uint256 validBefore, bytes32 nonce ) internal view returns (bytes32) { - bytes32 structHash = - keccak256(abi.encode(_RECEIVE_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce)); + bytes32 structHash = keccak256( + abi.encode(_RECEIVE_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce) + ); return keccak256(abi.encodePacked("\x19\x01", IERC3009(token).DOMAIN_SEPARATOR(), structHash)); } diff --git a/test/base/AuthCaptureEscrowSmartWalletBase.sol b/test/base/AuthCaptureEscrowSmartWalletBase.sol index 4da0435..ab9f00d 100644 --- a/test/base/AuthCaptureEscrowSmartWalletBase.sol +++ b/test/base/AuthCaptureEscrowSmartWalletBase.sol @@ -172,11 +172,7 @@ contract AuthCaptureEscrowSmartWalletBase is AuthCaptureEscrowBase { uint256 nonce = uint256(hashPortion); return MagicSpend.WithdrawRequest({ - asset: address(0), - amount: 0, - nonce: nonce, - expiry: type(uint48).max, - signature: new bytes(0) + asset: address(0), amount: 0, nonce: nonce, expiry: type(uint48).max, signature: new bytes(0) }); } diff --git a/test/gas/gasBenchmark.t.sol b/test/gas/gasBenchmark.t.sol index fc36452..c56d742 100644 --- a/test/gas/gasBenchmark.t.sol +++ b/test/gas/gasBenchmark.t.sol @@ -23,7 +23,9 @@ contract GasBenchmarkBase is AuthCaptureEscrowBase { mockERC3009Token.mint(payerEOA, 1e6); vm.startPrank(operator); authCaptureEscrow.authorize(warmupInfo, 1e6, address(erc3009PaymentCollector), warmupSignature); - authCaptureEscrow.capture(warmupInfo, 1e6, (uint256(1_000_000) * uint256(BENCHMARK_FEE_BPS)) / 10_000, feeReceiver); // make sure token store is deployed before subsequent tests + authCaptureEscrow.capture( + warmupInfo, 1e6, (uint256(1_000_000) * uint256(BENCHMARK_FEE_BPS)) / 10_000, feeReceiver + ); // make sure token store is deployed before subsequent tests vm.stopPrank(); // Create and sign payment info @@ -46,7 +48,12 @@ contract ChargeGasBenchmark is GasBenchmarkBase { function test_charge_benchmark() public { vm.prank(operator); authCaptureEscrow.charge( - paymentInfo, BENCHMARK_AMOUNT, address(erc3009PaymentCollector), signature, BENCHMARK_FEE_AMOUNT, feeReceiver + paymentInfo, + BENCHMARK_AMOUNT, + address(erc3009PaymentCollector), + signature, + BENCHMARK_FEE_AMOUNT, + feeReceiver ); } } diff --git a/test/src/PaymentEscrow/authorize.t.sol b/test/src/PaymentEscrow/authorize.t.sol index f24c56a..60cc1e6 100644 --- a/test/src/PaymentEscrow/authorize.t.sol +++ b/test/src/PaymentEscrow/authorize.t.sol @@ -321,9 +321,7 @@ contract AuthorizeTest is AuthCaptureEscrowSmartWalletBase { vm.assume(maxAmount >= amount && amount > 0); mockERC3009Token.mint(address(smartWalletDeployed), amount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({ - payer: address(smartWalletDeployed), - maxAmount: maxAmount, - token: address(mockERC3009Token) + payer: address(smartWalletDeployed), maxAmount: maxAmount, token: address(mockERC3009Token) }); // Create and sign the spend permission diff --git a/test/src/PaymentEscrow/capture.t.sol b/test/src/PaymentEscrow/capture.t.sol index f018c8f..8ecbe85 100644 --- a/test/src/PaymentEscrow/capture.t.sol +++ b/test/src/PaymentEscrow/capture.t.sol @@ -25,7 +25,9 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(sender); vm.expectRevert(abi.encodeWithSelector(AuthCaptureEscrow.InvalidSender.selector, sender, paymentInfo.operator)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); } function test_reverts_whenValueIsZero() public { @@ -76,7 +78,9 @@ contract CaptureTest is AuthCaptureEscrowBase { AuthCaptureEscrow.AfterAuthorizationExpiry.selector, block.timestamp, authorizationExpiry ) ); - authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver + ); } function test_reverts_whenInsufficientAuthorization(uint120 authorizedAmount) public { @@ -102,7 +106,9 @@ contract CaptureTest is AuthCaptureEscrowBase { AuthCaptureEscrow.InsufficientAuthorization.selector, paymentInfoHash, authorizedAmount, captureAmount ) ); - authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver + ); } function test_reverts_receiverSender(uint120 authorizedAmount) public { @@ -123,7 +129,9 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.expectRevert( abi.encodeWithSelector(AuthCaptureEscrow.InvalidSender.selector, paymentInfo.receiver, paymentInfo.operator) ); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); } function test_succeeds_withFullAmount(uint120 authorizedAmount) public { @@ -144,7 +152,9 @@ contract CaptureTest is AuthCaptureEscrowBase { // Then capture the full amount vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); // Verify balances assertEq(mockERC3009Token.balanceOf(receiver), receiverExpectedBalance); @@ -171,7 +181,9 @@ contract CaptureTest is AuthCaptureEscrowBase { // Then capture partial amount vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver + ); // Verify balances and state address operatorTokenStore = authCaptureEscrow.getTokenStore(operator); @@ -197,11 +209,15 @@ contract CaptureTest is AuthCaptureEscrowBase { // First capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, firstCaptureAmount, _feeAmount(firstCaptureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, firstCaptureAmount, _feeAmount(firstCaptureAmount, FEE_BPS), paymentInfo.feeReceiver + ); // Second capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, secondCaptureAmount, _feeAmount(secondCaptureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, secondCaptureAmount, _feeAmount(secondCaptureAmount, FEE_BPS), paymentInfo.feeReceiver + ); // Calculate fees for each capture separately to match contract behavior uint256 firstFeesAmount = firstCaptureAmount * FEE_BPS / 10_000; @@ -240,14 +256,12 @@ contract CaptureTest is AuthCaptureEscrowBase { // Execute capture vm.prank(operator); - authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver + ); } - function test_reverts_whenFeeAmountBelowMin( - uint120 authorizedAmount, - uint16 minFeeBps, - uint16 maxFeeBps - ) public { + function test_reverts_whenFeeAmountBelowMin(uint120 authorizedAmount, uint16 minFeeBps, uint16 maxFeeBps) public { vm.assume(authorizedAmount > 0); vm.assume(minFeeBps > 0 && minFeeBps <= 5000); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 5000); @@ -270,18 +284,12 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector( - AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee - ) + abi.encodeWithSelector(AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee) ); authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, paymentInfo.feeReceiver); } - function test_reverts_whenFeeAmountAboveMax( - uint120 authorizedAmount, - uint16 minFeeBps, - uint16 maxFeeBps - ) public { + function test_reverts_whenFeeAmountAboveMax(uint120 authorizedAmount, uint16 minFeeBps, uint16 maxFeeBps) public { vm.assume(authorizedAmount > 0); vm.assume(minFeeBps <= 5000); vm.assume(maxFeeBps >= minFeeBps && maxFeeBps <= 5000); @@ -304,9 +312,7 @@ contract CaptureTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector( - AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee - ) + abi.encodeWithSelector(AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee) ); authCaptureEscrow.capture(paymentInfo, authorizedAmount, captureFeeAmount, paymentInfo.feeReceiver); } @@ -366,7 +372,9 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(MockRevertOnTransferToken.CustomRevert.selector, revertData)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); } @@ -405,7 +413,9 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(MockRevertOnTransferToken.CustomRevert.selector, revertData)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); } @@ -426,7 +436,9 @@ contract CaptureTest is AuthCaptureEscrowBase { authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(preApprovalPaymentCollector), ""); vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, revertingToken)); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); } diff --git a/test/src/PaymentEscrow/charge.t.sol b/test/src/PaymentEscrow/charge.t.sol index 7b4fd01..a6a0e21 100644 --- a/test/src/PaymentEscrow/charge.t.sol +++ b/test/src/PaymentEscrow/charge.t.sol @@ -382,9 +382,7 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector( - AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee - ) + abi.encodeWithSelector(AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee) ); authCaptureEscrow.charge( paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, paymentInfo.feeReceiver @@ -410,9 +408,7 @@ contract ChargeTest is AuthCaptureEscrowBase { vm.prank(operator); vm.expectRevert( - abi.encodeWithSelector( - AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee - ) + abi.encodeWithSelector(AuthCaptureEscrow.FeeAmountOutOfRange.selector, captureFeeAmount, minFee, maxFee) ); authCaptureEscrow.charge( paymentInfo, amount, address(erc3009PaymentCollector), signature, captureFeeAmount, paymentInfo.feeReceiver diff --git a/test/src/PaymentEscrow/refund.t.sol b/test/src/PaymentEscrow/refund.t.sol index 63c4903..6e09727 100644 --- a/test/src/PaymentEscrow/refund.t.sol +++ b/test/src/PaymentEscrow/refund.t.sol @@ -71,7 +71,9 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture partial amount vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, captureAmount, _feeAmount(captureAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); // Fund operator for refund @@ -100,7 +102,9 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture the payment vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); // Fund the operator for refund @@ -136,7 +140,9 @@ contract RefundTest is AuthCaptureEscrowBase { // First confirm and capture the payment vm.startPrank(operator); authCaptureEscrow.authorize(paymentInfo, authorizedAmount, address(erc3009PaymentCollector), signature); - authCaptureEscrow.capture(paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver); + authCaptureEscrow.capture( + paymentInfo, authorizedAmount, _feeAmount(authorizedAmount, FEE_BPS), paymentInfo.feeReceiver + ); vm.stopPrank(); // Fund operator for refund diff --git a/test/src/collectors/SpendPermissionPaymentCollector.t.sol b/test/src/collectors/SpendPermissionPaymentCollector.t.sol index 81c6c5d..8a3c946 100644 --- a/test/src/collectors/SpendPermissionPaymentCollector.t.sol +++ b/test/src/collectors/SpendPermissionPaymentCollector.t.sol @@ -25,9 +25,7 @@ contract SpendPermissionPaymentCollectorTest is AuthCaptureEscrowSmartWalletBase MockERC3009Token(address(mockERC3009Token)).mint(address(smartWalletDeployed), amount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({ - payer: address(smartWalletDeployed), - maxAmount: amount, - token: address(mockERC3009Token) + payer: address(smartWalletDeployed), maxAmount: amount, token: address(mockERC3009Token) }); address tokenStore = authCaptureEscrow.getTokenStore(paymentInfo.operator); @@ -47,9 +45,7 @@ contract SpendPermissionPaymentCollectorTest is AuthCaptureEscrowSmartWalletBase MockERC3009Token(address(mockERC3009Token)).mint(address(smartWalletDeployed), amount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({ - payer: address(smartWalletDeployed), - maxAmount: amount, - token: address(mockERC3009Token) + payer: address(smartWalletDeployed), maxAmount: amount, token: address(mockERC3009Token) }); address tokenStore = authCaptureEscrow.getTokenStore(paymentInfo.operator); @@ -69,9 +65,7 @@ contract SpendPermissionPaymentCollectorTest is AuthCaptureEscrowSmartWalletBase mockERC3009Token.mint(address(magicSpend), amount); AuthCaptureEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({ - payer: address(smartWalletDeployed), - maxAmount: amount, - token: address(mockERC3009Token) + payer: address(smartWalletDeployed), maxAmount: amount, token: address(mockERC3009Token) }); address tokenStore = authCaptureEscrow.getTokenStore(paymentInfo.operator); From 850a0a2b3bcb7942f7f10ff6ff799a65041f9d69 Mon Sep 17 00:00:00 2001 From: Ruohan Date: Wed, 24 Jun 2026 09:50:25 -0700 Subject: [PATCH 5/6] remove local variable --- src/AuthCaptureEscrow.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/AuthCaptureEscrow.sol b/src/AuthCaptureEscrow.sol index 6068120..1845ede 100644 --- a/src/AuthCaptureEscrow.sol +++ b/src/AuthCaptureEscrow.sol @@ -509,12 +509,10 @@ contract AuthCaptureEscrow is ReentrancyGuardTransient { internal pure { - uint16 minFeeBps = paymentInfo.minFeeBps; - uint16 maxFeeBps = paymentInfo.maxFeeBps; address configuredFeeReceiver = paymentInfo.feeReceiver; - uint256 minFee = amount * minFeeBps / _MAX_FEE_BPS; - uint256 maxFee = amount * maxFeeBps / _MAX_FEE_BPS; + uint256 minFee = amount * paymentInfo.minFeeBps / _MAX_FEE_BPS; + uint256 maxFee = amount * paymentInfo.maxFeeBps / _MAX_FEE_BPS; // Check fee amount within payer-approved bounds if (feeAmount < minFee || feeAmount > maxFee) revert FeeAmountOutOfRange(feeAmount, minFee, maxFee); From 3a2ea7eced1de3b70b71fc3f7d8975867c35ec95 Mon Sep 17 00:00:00 2001 From: Ruohan Date: Wed, 24 Jun 2026 09:53:40 -0700 Subject: [PATCH 6/6] formatting --- src/interfaces/IMulticall3.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interfaces/IMulticall3.sol b/src/interfaces/IMulticall3.sol index f232db1..57a27ea 100644 --- a/src/interfaces/IMulticall3.sol +++ b/src/interfaces/IMulticall3.sol @@ -26,7 +26,10 @@ interface IMulticall3 { bytes returnData; } - function aggregate(Call[] calldata calls) external payable returns (uint256 blockNumber, bytes[] memory returnData); + function aggregate(Call[] calldata calls) + external + payable + returns (uint256 blockNumber, bytes[] memory returnData); function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);