The Created State Alone Does Not Indicate Whether a Payment is Unlocked
Payments are created in the Created state with an unlockTime, but the Created state alone does not indicate whether a payment is unlocked, complicating UI status queries and filtering of ready-to-settle payments.
Description
Payments are created in the Created state with an unlockTime determining when they can be settled. However, the Created state alone does not indicate whether a payment is unlocked (i.e., unlockTime <= block.timestamp).
This ambiguity complicates querying payment status, as users must check unlockTime separately. The settlePayment function enforces this via two modifiers:
paymentCreatedStatus(ensures Created state)paymentUnlocked(verifies unlockTime).
Impact
- More complex user interfaces: UI needs to display both the payment status and unlock timing information.
- Filtering and sorting challenges: difficult to implement efficient filtering of payments that are "ready to settle" versus those still locked.
Proof of Concept
function testUnlockedButNotSettledState() public {uint256 timelockPeriod = 1 days;vm.startPrank(user1);token.approve(address(payments), paymentAmount);payments.createERC20PaymentWithTimeLock(user2, address(token), paymentAmount, timelockPeriod);vm.stopPrank();PaymaticPayments.Payment memory payment = payments.getPaymentDetails(1);recordStateChange(1, payment.status);assertEq(uint256(payment.status), uint256(PaymaticPayments.PaymentStatus.Created));vm.startPrank(user2);vm.expectRevert(abi.encodeWithSelector(PaymaticPayments.PaymentTimelocked.selector));payments.settlePayment(1);vm.stopPrank();vm.warp(block.timestamp + timelockPeriod + 1);payment = payments.getPaymentDetails(1);assertEq(uint256(payment.status), uint256(PaymaticPayments.PaymentStatus.Created));assertTrue(payment.unlockTime < block.timestamp);vm.startPrank(user2);payments.settlePayment(1);vm.stopPrank();payment = payments.getPaymentDetails(1);recordStateChange(1, payment.status);// Verify state changed to SettledassertEq(uint256(payment.status), uint256(PaymaticPayments.PaymentStatus.Settled));// Verify state historyassertTrue(validateStateHistory(1));// Verify transition was directly from Created to Settled,// without an intermediate state indicating it was unlockedassertEq(paymentStateHistory[1].length, 2);assertEq(uint256(paymentStateHistory[1][0]), uint256(PaymaticPayments.PaymentStatus.Created));assertEq(uint256(paymentStateHistory[1][1]), uint256(PaymaticPayments.PaymentStatus.Settled));}
Recommendation
Add a view function to check if a payment is unlocked, improving usability without altering the contract's state structure.
function getPaymentLockStatus(uint256 id) external view returns (bool) {if (payments[id].status == PaymentStatus.Created && payments[id].unlockTime > block.timestamp) {return true;}return false;}

