Skip to content

Commit a87a61a

Browse files
authored
Audit 4 minor fixes (#65)
* fix issue 9: add comment above paymentInfo.payer = payer * fix issue 7: comment about failure to reset preapproval * fix issue 5: simplify imports and remappings * use standard ReentrancyGuard * add check for preApprovalExpiry in preApprove * use transient reentrancy guard on all chains * add comment to preapproval expiry check * remove triple slash
1 parent 496d607 commit a87a61a

6 files changed

Lines changed: 29 additions & 5 deletions

File tree

remappings.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
@openzeppelin/contracts/=lib/spend-permissions/lib/openzeppelin-contracts/contracts/
2-
@openzeppelin/contracts/security/=lib/spend-permissions/lib/magicspend/lib/openzeppelin-contracts/contracts/utils/
32
FreshCryptoLib/=lib/spend-permissions/lib/webauthn-sol/lib/FreshCryptoLib/solidity/src/
43
account-abstraction/=lib/spend-permissions/lib/account-abstraction/contracts/
54
ds-test/=lib/spend-permissions/lib/webauthn-sol/lib/forge-std/lib/ds-test/src/

src/PaymentEscrow.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,9 @@ contract PaymentEscrow is ReentrancyGuardTransient {
498498
revert InvalidFeeReceiver(feeReceiver, configuredFeeReceiver);
499499
}
500500
}
501+
502+
/// @dev Override to use transient reentrancy guard on all chains
503+
function _useTransientReentrancyGuardOnlyOnMainnet() internal view virtual override returns (bool) {
504+
return false;
505+
}
501506
}

src/TokenStore.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0
22
pragma solidity ^0.8.28;
33

4-
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
4+
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
65

76
/// @title TokenStore
87
/// @notice Holds funds for a single operator's payments

src/collectors/PreApprovalPaymentCollector.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ contract PreApprovalPaymentCollector is TokenCollector {
4040
// Check sender is buyer
4141
if (msg.sender != paymentInfo.payer) revert PaymentEscrow.InvalidSender(msg.sender, paymentInfo.payer);
4242

43+
// Check pre-approval expiry has not passed
44+
if (block.timestamp >= paymentInfo.preApprovalExpiry) {
45+
revert PaymentEscrow.AfterPreApprovalExpiry(uint48(block.timestamp), paymentInfo.preApprovalExpiry);
46+
}
47+
4348
// Check has not already pre-approved
4449
bytes32 paymentInfoHash = paymentEscrow.getHash(paymentInfo);
4550
if (isPreApproved[paymentInfoHash]) revert PaymentAlreadyPreApproved(paymentInfoHash);
@@ -63,8 +68,8 @@ contract PreApprovalPaymentCollector is TokenCollector {
6368
) internal override {
6469
// Check payment pre-approved
6570
bytes32 paymentInfoHash = paymentEscrow.getHash(paymentInfo);
71+
// Skip resetting pre-approval to save gas as the `PaymentEscrow` enforces unique, single-lifecycle payments
6672
if (!isPreApproved[paymentInfoHash]) revert PaymentNotPreApproved(paymentInfoHash);
67-
6873
// Transfer tokens from payer directly to token store
6974
SafeERC20.safeTransferFrom(IERC20(paymentInfo.token), paymentInfo.payer, tokenStore, amount);
7075
}

src/collectors/TokenCollector.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ abstract contract TokenCollector {
5757
) internal virtual;
5858

5959
/// @notice Get hash for PaymentInfo with null payer address
60-
/// @dev Proactively setting payer back to original value covers accidental bugs of memory location being used elsewhere
6160
/// @param paymentInfo PaymentInfo struct with non-null payer address
6261
/// @return hash Hash of PaymentInfo with payer replaced with zero address
6362
function _getHashPayerAgnostic(PaymentEscrow.PaymentInfo memory paymentInfo) internal view returns (bytes32) {
6463
address payer = paymentInfo.payer;
6564
paymentInfo.payer = address(0);
6665
bytes32 hashPayerAgnostic = paymentEscrow.getHash(paymentInfo);
66+
// Proactively setting payer back to original value covers accidental bugs if memory location is then used elsewhere
6767
paymentInfo.payer = payer;
6868
return hashPayerAgnostic;
6969
}

test/src/collectors/PreApprovalPaymentCollector.t.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ contract PreApprovalPaymentCollectorTest is PaymentEscrowBase {
5858
PreApprovalPaymentCollector(address(preApprovalPaymentCollector)).preApprove(paymentInfo);
5959
}
6060

61+
function test_preApprove_reverts_ifAfterPreApprovalExpiry(uint120 amount) public {
62+
vm.assume(amount > 0);
63+
64+
// Create payment info with a pre-approval expiry in the past
65+
PaymentEscrow.PaymentInfo memory paymentInfo = _createPaymentInfo({payer: payerEOA, maxAmount: amount});
66+
paymentInfo.preApprovalExpiry = uint48(block.timestamp - 1);
67+
68+
vm.prank(payerEOA);
69+
vm.expectRevert(
70+
abi.encodeWithSelector(
71+
PaymentEscrow.AfterPreApprovalExpiry.selector, uint48(block.timestamp), paymentInfo.preApprovalExpiry
72+
)
73+
);
74+
PreApprovalPaymentCollector(address(preApprovalPaymentCollector)).preApprove(paymentInfo);
75+
}
76+
6177
function test_preApprove_emitsExpectedEvents(uint120 amount) public {
6278
vm.assume(amount > 0);
6379

0 commit comments

Comments
 (0)