@bananapus/721-hook-v6 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/721-hook-v6",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -711,9 +711,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, ERC721, IJB721TiersHook {
711
711
  // If the payer is the beneficiary, combine their NFT credits with the amount paid.
712
712
  uint256 unusedPayCredits;
713
713
  if (context.payer == context.beneficiary) {
714
- unchecked {
715
- leftoverAmount += payCredits;
716
- }
714
+ leftoverAmount += payCredits;
717
715
  } else {
718
716
  // Otherwise, the payer's NFT credits won't be used, and we keep track of the unused credits.
719
717
  unusedPayCredits = payCredits;
@@ -755,28 +753,26 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, ERC721, IJB721TiersHook {
755
753
  if (leftoverAmount != 0 && !allowOverspending) revert JB721TiersHook_Overspending(leftoverAmount);
756
754
 
757
755
  // Update NFT credits if they changed.
758
- unchecked {
759
- uint256 newPayCredits = leftoverAmount + unusedPayCredits;
760
-
761
- if (newPayCredits != payCredits) {
762
- if (newPayCredits > payCredits) {
763
- emit AddPayCredits({
764
- amount: newPayCredits - payCredits,
765
- newTotalCredits: newPayCredits,
766
- account: context.beneficiary,
767
- caller: _msgSender()
768
- });
769
- } else {
770
- emit UsePayCredits({
771
- amount: payCredits - newPayCredits,
772
- newTotalCredits: newPayCredits,
773
- account: context.beneficiary,
774
- caller: _msgSender()
775
- });
776
- }
777
-
778
- payCreditsOf[context.beneficiary] = newPayCredits;
756
+ uint256 newPayCredits = leftoverAmount + unusedPayCredits;
757
+
758
+ if (newPayCredits != payCredits) {
759
+ if (newPayCredits > payCredits) {
760
+ emit AddPayCredits({
761
+ amount: newPayCredits - payCredits,
762
+ newTotalCredits: newPayCredits,
763
+ account: context.beneficiary,
764
+ caller: _msgSender()
765
+ });
766
+ } else {
767
+ emit UsePayCredits({
768
+ amount: payCredits - newPayCredits,
769
+ newTotalCredits: newPayCredits,
770
+ account: context.beneficiary,
771
+ caller: _msgSender()
772
+ });
779
773
  }
774
+
775
+ payCreditsOf[context.beneficiary] = newPayCredits;
780
776
  }
781
777
 
782
778
  // Distribute any forwarded funds to tier split groups.
@@ -1530,4 +1530,88 @@ contract Test_afterPayRecorded_Unit is UnitTestSetup {
1530
1530
  // Check: has the holder's balance returned to 0?
1531
1531
  assertEq(hook.balanceOf(holder), 0);
1532
1532
  }
1533
+
1534
+ function test_afterPayRecorded_revertOnCreditOverflow_samePayerBeneficiary() public {
1535
+ // Mock the directory call.
1536
+ mockAndExpect(
1537
+ address(mockJBDirectory),
1538
+ abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
1539
+ abi.encode(true)
1540
+ );
1541
+
1542
+ // Set the beneficiary's pay credits to max uint256.
1543
+ stdstore.target(address(hook)).sig("payCreditsOf(address)").with_key(beneficiary)
1544
+ .checked_write(type(uint256).max);
1545
+
1546
+ // Pay 1 wei where payer == beneficiary. No metadata → no NFT mints.
1547
+ // `leftoverAmount += payCredits` overflows: 1 + type(uint256).max.
1548
+ vm.expectRevert(stdError.arithmeticError);
1549
+ vm.prank(mockTerminalAddress);
1550
+ hook.afterPayRecordedWith(
1551
+ JBAfterPayRecordedContext({
1552
+ payer: beneficiary,
1553
+ projectId: projectId,
1554
+ rulesetId: 0,
1555
+ amount: JBTokenAmount({
1556
+ token: JBConstants.NATIVE_TOKEN,
1557
+ value: 1,
1558
+ decimals: 18,
1559
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1560
+ }),
1561
+ forwardedAmount: JBTokenAmount({
1562
+ token: JBConstants.NATIVE_TOKEN,
1563
+ value: 0,
1564
+ decimals: 18,
1565
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1566
+ }),
1567
+ weight: 10 ** 18,
1568
+ newlyIssuedTokenCount: 0,
1569
+ beneficiary: beneficiary,
1570
+ hookMetadata: new bytes(0),
1571
+ payerMetadata: new bytes(0)
1572
+ })
1573
+ );
1574
+ }
1575
+
1576
+ function test_afterPayRecorded_revertOnCreditOverflow_differentPayerBeneficiary() public {
1577
+ // Mock the directory call.
1578
+ mockAndExpect(
1579
+ address(mockJBDirectory),
1580
+ abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
1581
+ abi.encode(true)
1582
+ );
1583
+
1584
+ // Set the beneficiary's pay credits to max uint256.
1585
+ stdstore.target(address(hook)).sig("payCreditsOf(address)").with_key(beneficiary)
1586
+ .checked_write(type(uint256).max);
1587
+
1588
+ // Pay 1 wei where payer != beneficiary. No metadata → no NFT mints, overspending allowed.
1589
+ // leftoverAmount=1, unusedPayCredits=type(uint256).max → overflow in `leftoverAmount + unusedPayCredits`.
1590
+ vm.expectRevert(stdError.arithmeticError);
1591
+ vm.prank(mockTerminalAddress);
1592
+ hook.afterPayRecordedWith(
1593
+ JBAfterPayRecordedContext({
1594
+ payer: address(0xdead),
1595
+ projectId: projectId,
1596
+ rulesetId: 0,
1597
+ amount: JBTokenAmount({
1598
+ token: JBConstants.NATIVE_TOKEN,
1599
+ value: 1,
1600
+ decimals: 18,
1601
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1602
+ }),
1603
+ forwardedAmount: JBTokenAmount({
1604
+ token: JBConstants.NATIVE_TOKEN,
1605
+ value: 0,
1606
+ decimals: 18,
1607
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1608
+ }),
1609
+ weight: 10 ** 18,
1610
+ newlyIssuedTokenCount: 0,
1611
+ beneficiary: beneficiary,
1612
+ hookMetadata: new bytes(0),
1613
+ payerMetadata: new bytes(0)
1614
+ })
1615
+ );
1616
+ }
1533
1617
  }