From b8feb0bcb61a9028c253aa68f1d009f3b3d994cb Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Sun, 6 Jul 2025 21:14:53 +0530 Subject: [PATCH 1/4] feat: add createChannelWithPermit for MuPay and multisig, with tests - Add createChannelWithPermit function for MuPay supporting EIP-2612 permit - Add createChannelWithPermit function for Multisig supporting EIP-2612 permit - Wrote tests for both schemes --- src/MuPay.sol | 54 +++++ src/Multisig_2of2.sol | 41 ++++ test/CreateChannelWithPermit.t.sol | 262 ++++++++++++++++++++++ test/mocks/MockERC20.sol | 13 ++ test/multisig/CreateChannelWithPermit.sol | 164 ++++++++++++++ 5 files changed, 534 insertions(+) create mode 100644 test/CreateChannelWithPermit.t.sol create mode 100644 test/mocks/MockERC20.sol create mode 100644 test/multisig/CreateChannelWithPermit.sol diff --git a/src/MuPay.sol b/src/MuPay.sol index 58d0142..f7522af 100644 --- a/src/MuPay.sol +++ b/src/MuPay.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.28; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; /** * @title MuPay - A Simple Payment Channel @@ -46,6 +47,7 @@ contract MuPay is ReentrancyGuard { error InsufficientAllowance(uint256 required, uint256 actual); error AddressIsNotContract(address token); error AddressIsNotERC20(address token); + error DepositWithPermitNotSupportedForNative(); /** * @dev Events to log key contract actions. @@ -132,6 +134,58 @@ contract MuPay is ReentrancyGuard { emit ChannelCreated(msg.sender, merchant, token, amount, numberOfTokens, merchantWithdrawAfterBlocks); } + /** + * @dev Creates a new payment channel using EIP-2612 permit for gasless approval. + * @param payer The address of the user funding the channel. + * @param merchant The merchant receiving payments. + * @param token The ERC-20 token address used for payments. + * @param trustAnchor The final hash value of the hashchain. + * @param amount The total deposit amount for the channel. + * @param numberOfTokens The number of tokens in the hashchain. + * @param merchantWithdrawAfterBlocks The block number after which the merchant can withdraw. + * @param payerWithdrawAfterBlocks The block number after which the payer can reclaim unused funds. + * @param deadline The deadline timestamp for the permit. + * @param v, r, s The EIP-2612 signature parameters. + */ + function createChannelWithPermit( + address payer, + address merchant, + address token, + bytes32 trustAnchor, + uint256 amount, + uint16 numberOfTokens, + uint64 merchantWithdrawAfterBlocks, + uint64 payerWithdrawAfterBlocks, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external nonReentrant { + require(token != address(0), DepositWithPermitNotSupportedForNative()); + require(payer != address(0), "Invalid payer address"); + + _validateChannelParams(merchant, numberOfTokens, merchantWithdrawAfterBlocks, payerWithdrawAfterBlocks); + + IERC20Permit(token).permit(payer, address(this), amount, deadline, v, r, s); + // Permit was successful, proceed with channel creation + _createERC20Channel(token, amount); // Handles ERC-20 token validation and transfer + _initChannel( + payer, + merchant, + token, + trustAnchor, + amount, + numberOfTokens, + merchantWithdrawAfterBlocks, + payerWithdrawAfterBlocks + ); + + // Emit an event to notify the channel has been created + emit ChannelCreated(msg.sender, merchant, token, amount, numberOfTokens, merchantWithdrawAfterBlocks); + + // revert TokenDoesNotSupportPermit(token); + } + /** * @dev Validates the parameters for channel creation. * @param merchant The merchant address, must not be zero. diff --git a/src/Multisig_2of2.sol b/src/Multisig_2of2.sol index bc740b2..9ab3408 100644 --- a/src/Multisig_2of2.sol +++ b/src/Multisig_2of2.sol @@ -6,6 +6,7 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; contract Multisig is ReentrancyGuard { using ECDSA for bytes32; // for recover() @@ -44,6 +45,7 @@ contract Multisig is ReentrancyGuard { error StaleNonce(uint256 supplied, uint256 current); error InvalidChannelSignature(address recovered, address expected); error ReclaimAfterMustBeAfterExpiration(uint64 expiration, uint64 reclaimAfter); + error DepositWithPermitNotSupportedForNative(); /** * @dev Events to log key contract actions. @@ -76,6 +78,7 @@ contract Multisig is ReentrancyGuard { * @param token The ERC-20 token address used for payments, or address(0) to use the native currency. * @param amount The total deposit amount for the channel. * @param duration The channel lifetime in blocks (from current block). + * @param reclaimDelay The time after which the payer can reclaim the funds. */ function createChannel(address payee, address token, uint256 amount, uint64 duration, uint64 reclaimDelay) external @@ -99,6 +102,44 @@ contract Multisig is ReentrancyGuard { } } + /** + * @dev Creates a new payment channel between a payer and a payee. + * @param payer The address funding the channel. + * @param payee The address receiving payments. + * @param token The ERC-20 token address used for payments, or address(0) to use the native currency. + * @param duration The channel lifetime in blocks (from current block). + * @param reclaimDelay The time after which the payer can reclaim the funds. + * @param amount The total deposit amount for the channel. + * @param duration The channel lifetime in blocks (from current block). + * @param v, r, s The EIP-2612 signature parameters. + */ + function createChannelWithPermit( + address payer, + address payee, + address token, + uint256 amount, + uint64 duration, + uint64 reclaimDelay, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable nonReentrant { + require(payee != address(0), "Invalid address"); + require( + duration < reclaimDelay, + ReclaimAfterMustBeAfterExpiration( + uint64(block.timestamp) + duration, uint64(block.timestamp) + reclaimDelay + ) + ); + // Validate the permit signature + require(token != address(0), DepositWithPermitNotSupportedForNative()); + + IERC20Permit(token).permit(payer, address(this), amount, deadline, v, r, s); + + _createERC20Channel(payee, token, amount, duration, reclaimDelay); + } + /** * @dev Handles channel creation when using native currency (ETH). * @param payee The address receiving ETH payments. diff --git a/test/CreateChannelWithPermit.t.sol b/test/CreateChannelWithPermit.t.sol new file mode 100644 index 0000000..a09538d --- /dev/null +++ b/test/CreateChannelWithPermit.t.sol @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.28; + +import {Test, console} from "forge-std/Test.sol"; +import {MuPay} from "../src/MuPay.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; +import {BaseTestHelper} from "./helper/BaseTestHelper.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract NotERC20 {} + +contract CreateChannelERC20PermitTest is Test, BaseTestHelper { + MuPay public muPay; + MockERC20 public token; + bytes32 public trustAnchor; + uint16 public numberOfTokens; + uint64 public merchantWithdrawAfterBlocks; + uint64 public payerWithdrawAfterBlocks; + uint256 public deadline; + + function setUp() public { + muPay = new MuPay(); + token = new MockERC20("Test Token", "TTK"); + + // Mint tokens to the payers + token.mint(PAYER, INITIAL_BALANCE); + token.mint(PAYER2, INITIAL_BALANCE); + + trustAnchor = 0x7cacb8c6cc65163d30a6c8ce47c0d284490d228d1d1aa7e9ae3f149f77b32b5d; + numberOfTokens = 100; + merchantWithdrawAfterBlocks = uint64(block.number) + 1; + payerWithdrawAfterBlocks = uint64(block.number) + 1; + deadline = block.timestamp + 1 hours; + } + + function getPermitSignature(uint256 privateKey, address owner, address spender, uint256 value) + public + view + returns (uint8 v, bytes32 r, bytes32 s) + { + uint256 nonce = MockERC20(address(token)).nonces(owner); + bytes32 DOMAIN_SEPARATOR = MockERC20(address(token)).DOMAIN_SEPARATOR(); + + bytes32 structHash = keccak256( + abi.encode( + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), + owner, + spender, + value, + nonce, + deadline + ) + ); + + bytes32 digest = MessageHashUtils.toTypedDataHash(DOMAIN_SEPARATOR, structHash); + + // Sign the exact digest that `permit` expects using the provided private key + (v, r, s) = vm.sign(privateKey, digest); + } + + function testCreateChannelWithPermit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(muPay)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER1PK, PAYER, address(muPay), DEPOSIT_AMOUNT); + + vm.startPrank(PAYER); + + muPay.createChannelWithPermit( + PAYER, + PAYEE, + address(token), + trustAnchor, + DEPOSIT_AMOUNT, + numberOfTokens, + merchantWithdrawAfterBlocks, + payerWithdrawAfterBlocks, + deadline, + v, + r, + s + ); + + vm.stopPrank(); + + // Capture post-deposit balance + _assertDepositBalances( + preDepositBalancePayer, + token.balanceOf(PAYER), + predepositBalanceContract, + token.balanceOf(address(muPay)), + DEPOSIT_AMOUNT + ); + + // Verify channel creation + ( + address storedTokenAddress, + bytes32 storedTrustAnchor, + uint256 storedAmount, + uint16 storedNumberOfTokens, + uint64 storedMerchantWithdrawAfterBlocks, + uint64 storedPayerWithdrawAfterBlocks + ) = muPay.channelsMapping(PAYER, PAYEE, address(token)); + + assertEq(storedTokenAddress, address(token), "Incorrect token address stored"); + assertEq(storedTrustAnchor, trustAnchor, "Incorrect trust anchor stored"); + assertEq(storedAmount, DEPOSIT_AMOUNT, "Incorrect amount stored"); + assertEq(storedNumberOfTokens, numberOfTokens, "Incorrect number of tokens stored"); + assertEq( + storedMerchantWithdrawAfterBlocks, + merchantWithdrawAfterBlocks + block.number, + "Incorrect merchant withdraw after blocks" + ); + assertEq( + storedPayerWithdrawAfterBlocks, + payerWithdrawAfterBlocks + block.number, + "Incorrect payer withdraw after blocks" + ); + } + + function testCreateChannelWithMultiplePermit() public { + testCreateChannelWithPermit(); + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE - DEPOSIT_AMOUNT, "Payer's balance should decrease"); + uint256 predepositBalanceContract = token.balanceOf(address(muPay)); + assertEq(predepositBalanceContract, DEPOSIT_AMOUNT, "Contract should have the deposit amount"); + + // Get the permit signature for the payer again + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER1PK, PAYER, address(muPay), DEPOSIT_AMOUNT); + vm.startPrank(PAYER); + muPay.createChannelWithPermit( + PAYER, + PAYEE2, + address(token), + trustAnchor, + DEPOSIT_AMOUNT, + numberOfTokens, + merchantWithdrawAfterBlocks, + payerWithdrawAfterBlocks, + deadline, + v, + r, + s + ); + vm.stopPrank(); + // Capture post-deposit balance + uint256 postDepositBalancePayer = token.balanceOf(PAYER); + uint256 postDepositBalanceContract = token.balanceOf(address(muPay)); + _assertDepositBalances( + preDepositBalancePayer, + postDepositBalancePayer, + predepositBalanceContract, + postDepositBalanceContract, + DEPOSIT_AMOUNT + ); + } + + function testCreateChannelWithExpiredPermit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(muPay)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER1PK, PAYER, address(muPay), DEPOSIT_AMOUNT); + + // Set the deadline to a past time to simulate an expired permit + vm.warp(deadline + 10); + + vm.startPrank(PAYER); + vm.expectRevert(abi.encodeWithSignature("ERC2612ExpiredSignature(uint256)", deadline)); + muPay.createChannelWithPermit( + PAYER, + PAYEE, + address(token), + trustAnchor, + DEPOSIT_AMOUNT, + numberOfTokens, + merchantWithdrawAfterBlocks, + payerWithdrawAfterBlocks, + deadline, + v, + r, + s + ); + + vm.stopPrank(); + + // Capture post-deposit balance + uint256 postDepositBalancePayer = token.balanceOf(PAYER); + uint256 postDepositBalanceContract = token.balanceOf(address(muPay)); + _assertDepositBalances( + preDepositBalancePayer, postDepositBalancePayer, predepositBalanceContract, postDepositBalanceContract, 0 + ); + } + + function testCreateChannelWithInvalidPermit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(muPay)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER2PK, PAYER, address(muPay), DEPOSIT_AMOUNT); + + vm.startPrank(PAYER); + + vm.expectRevert(abi.encodeWithSignature("ERC2612InvalidSigner(address,address)", PAYER2, PAYER)); + muPay.createChannelWithPermit( + PAYER, + PAYEE, + address(token), + trustAnchor, + DEPOSIT_AMOUNT, + numberOfTokens, + merchantWithdrawAfterBlocks, + payerWithdrawAfterBlocks, + deadline, + v, + r, + s + ); + + vm.stopPrank(); + + // Capture post-deposit balance + uint256 postDepositBalancePayer = token.balanceOf(PAYER); + uint256 postDepositBalanceContract = token.balanceOf(address(muPay)); + _assertDepositBalances( + preDepositBalancePayer, postDepositBalancePayer, predepositBalanceContract, postDepositBalanceContract, 0 + ); + } + + function _assertDepositBalances( + uint256 preDepositBalancePayer, + uint256 postDepositBalancePayer, + uint256 preDepositBalanceContract, + uint256 postDepositBalanceContract, + uint256 depositAmount + ) internal pure { + assertEq( + postDepositBalancePayer, + preDepositBalancePayer - depositAmount, + "Payer's balance should decrease by the deposit amount" + ); + assertEq( + postDepositBalanceContract, + preDepositBalanceContract + depositAmount, + "Contract should have received the deposit amount" + ); + } +} diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 0000000..1585308 --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; + +contract MockERC20 is ERC20Permit { + constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} + + // Mint tokens for testing + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} diff --git a/test/multisig/CreateChannelWithPermit.sol b/test/multisig/CreateChannelWithPermit.sol new file mode 100644 index 0000000..98c1341 --- /dev/null +++ b/test/multisig/CreateChannelWithPermit.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.28; + +import {Test, console} from "forge-std/Test.sol"; +import {Multisig} from "../../src/Multisig_2of2.sol"; +import {BaseTestHelper} from "../helper/BaseTestHelper.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract CreateChannelERC20PermitTest is Test, BaseTestHelper { + Multisig public multisig; + MockERC20 public token; + uint256 deadline; + + function setUp() public { + multisig = new Multisig(); + token = new MockERC20("Test Token", "TTK"); + deadline = block.timestamp + 1 hours; + + // Mint tokens to the payers + token.mint(PAYER, INITIAL_BALANCE); + token.mint(PAYER2, INITIAL_BALANCE); + } + + function getPermitSignature(uint256 privateKey, address owner, address spender, uint256 value) + public + view + returns (uint8 v, bytes32 r, bytes32 s) + { + uint256 nonce = MockERC20(address(token)).nonces(owner); + bytes32 DOMAIN_SEPARATOR = MockERC20(address(token)).DOMAIN_SEPARATOR(); + + bytes32 structHash = keccak256( + abi.encode( + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), + owner, + spender, + value, + nonce, + deadline + ) + ); + + bytes32 digest = MessageHashUtils.toTypedDataHash(DOMAIN_SEPARATOR, structHash); + + // Sign the exact digest that `permit` expects using the provided private key + (v, r, s) = vm.sign(privateKey, digest); + } + + function testMultisigCreateChannelWithERC20Permit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(multisig)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER1PK, PAYER, address(multisig), DEPOSIT_AMOUNT); + + vm.startPrank(PAYER); + // Create the channel with permit + multisig.createChannelWithPermit( + PAYER, PAYEE, address(token), DEPOSIT_AMOUNT, DURATION, RECLAIM_DELAY, deadline, v, r, s + ); + + // Capture post-deposit balance + _assertDepositBalances( + preDepositBalancePayer, + token.balanceOf(PAYER), + predepositBalanceContract, + token.balanceOf(address(multisig)), + DEPOSIT_AMOUNT + ); + (address storedToken, uint256 storedAmount, uint64 storedDuration, uint64 storedReclaimDelay,,) = + multisig.channels(PAYER, PAYEE, address(token)); + + assertEq(storedToken, address(token), "Stored token address should match"); + assertEq(storedAmount, DEPOSIT_AMOUNT, "Stored amount should match the deposit amount"); + assertEq(storedDuration, DURATION + block.timestamp, "Stored duration should match the specified duration"); + assertEq(storedReclaimDelay, RECLAIM_DELAY + block.timestamp, "Stored reclaim delay should match the specified reclaim delay"); + } + + function testMultisigCreateChannelWithExpiredPermit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(multisig)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER1PK, PAYER, address(multisig), DEPOSIT_AMOUNT); + + // Warp to a time after the deadline + vm.warp(deadline + 10); + + vm.startPrank(PAYER); + vm.expectRevert(abi.encodeWithSignature("ERC2612ExpiredSignature(uint256)", deadline)); + multisig.createChannelWithPermit( + PAYER, PAYEE, address(token), DEPOSIT_AMOUNT, DURATION, RECLAIM_DELAY, deadline, v, r, s + ); + vm.stopPrank(); + // Capture post-deposit balance + uint256 postDepositBalancePayer = token.balanceOf(PAYER); + uint256 postDepositBalanceContract = token.balanceOf(address(multisig)); + _assertDepositBalances( + preDepositBalancePayer, + postDepositBalancePayer, + predepositBalanceContract, + postDepositBalanceContract, + 0 // No deposit should have occurred + ); + } + + function testMultisigCreateChannelWithInvalidPermit() public { + // Capture pre-deposit balance + uint256 preDepositBalancePayer = token.balanceOf(PAYER); + assertEq(preDepositBalancePayer, INITIAL_BALANCE, "Initial balance should match"); + + uint256 predepositBalanceContract = token.balanceOf(address(multisig)); + assertEq(predepositBalanceContract, 0, "Contract should have no tokens before deposit"); + + // Get the permit signature for the payer + (uint8 v, bytes32 r, bytes32 s) = getPermitSignature(PAYER2PK, PAYER, address(multisig), DEPOSIT_AMOUNT); + + vm.startPrank(PAYER); + vm.expectRevert(abi.encodeWithSignature("ERC2612InvalidSigner(address,address)", PAYER2, PAYER)); + multisig.createChannelWithPermit( + PAYER, PAYEE, address(token), DEPOSIT_AMOUNT, DURATION, RECLAIM_DELAY, deadline, v, r, s + ); + vm.stopPrank(); + + // Capture post-deposit balance + uint256 postDepositBalancePayer = token.balanceOf(PAYER); + uint256 postDepositBalanceContract = token.balanceOf(address(multisig)); + _assertDepositBalances( + preDepositBalancePayer, + postDepositBalancePayer, + predepositBalanceContract, + postDepositBalanceContract, + 0 // No deposit should have occurred + ); + } + + function _assertDepositBalances( + uint256 preDepositBalancePayer, + uint256 postDepositBalancePayer, + uint256 preDepositBalanceContract, + uint256 postDepositBalanceContract, + uint256 depositAmount + ) internal pure { + assertEq( + postDepositBalancePayer, + preDepositBalancePayer - depositAmount, + "Payer's balance should decrease by the deposit amount" + ); + assertEq( + postDepositBalanceContract, + preDepositBalanceContract + depositAmount, + "Contract should have received the deposit amount" + ); + } +} From d5a50b4151cc8b63d1f0ef649d83edc21b158f46 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Sun, 6 Jul 2025 21:26:13 +0530 Subject: [PATCH 2/4] fix: formating --- test/multisig/CreateChannelWithPermit.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/multisig/CreateChannelWithPermit.sol b/test/multisig/CreateChannelWithPermit.sol index 98c1341..6cd7162 100644 --- a/test/multisig/CreateChannelWithPermit.sol +++ b/test/multisig/CreateChannelWithPermit.sol @@ -78,7 +78,11 @@ contract CreateChannelERC20PermitTest is Test, BaseTestHelper { assertEq(storedToken, address(token), "Stored token address should match"); assertEq(storedAmount, DEPOSIT_AMOUNT, "Stored amount should match the deposit amount"); assertEq(storedDuration, DURATION + block.timestamp, "Stored duration should match the specified duration"); - assertEq(storedReclaimDelay, RECLAIM_DELAY + block.timestamp, "Stored reclaim delay should match the specified reclaim delay"); + assertEq( + storedReclaimDelay, + RECLAIM_DELAY + block.timestamp, + "Stored reclaim delay should match the specified reclaim delay" + ); } function testMultisigCreateChannelWithExpiredPermit() public { From 1782c5b27d136aa60e5a95807c333a029acb42bb Mon Sep 17 00:00:00 2001 From: Aashish Paliwal Date: Sun, 6 Jul 2025 23:09:49 +0530 Subject: [PATCH 3/4] fix: function visibility of createChannelWithPermit --- src/MuPay.sol | 2 +- src/Multisig_2of2.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MuPay.sol b/src/MuPay.sol index f7522af..cf728e1 100644 --- a/src/MuPay.sol +++ b/src/MuPay.sol @@ -160,7 +160,7 @@ contract MuPay is ReentrancyGuard { uint8 v, bytes32 r, bytes32 s - ) external nonReentrant { + ) public nonReentrant { require(token != address(0), DepositWithPermitNotSupportedForNative()); require(payer != address(0), "Invalid payer address"); diff --git a/src/Multisig_2of2.sol b/src/Multisig_2of2.sol index 9ab3408..f4d7531 100644 --- a/src/Multisig_2of2.sol +++ b/src/Multisig_2of2.sol @@ -124,7 +124,7 @@ contract Multisig is ReentrancyGuard { uint8 v, bytes32 r, bytes32 s - ) external payable nonReentrant { + ) public nonReentrant { require(payee != address(0), "Invalid address"); require( duration < reclaimDelay, From 6f11b09fa1f049729df022a98363e211e4417456 Mon Sep 17 00:00:00 2001 From: Aashish Paliwal <32817912+pali101@users.noreply.github.com> Date: Mon, 7 Jul 2025 00:59:57 +0530 Subject: [PATCH 4/4] chore: remove commented out code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/MuPay.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MuPay.sol b/src/MuPay.sol index cf728e1..41ae5b2 100644 --- a/src/MuPay.sol +++ b/src/MuPay.sol @@ -182,8 +182,6 @@ contract MuPay is ReentrancyGuard { // Emit an event to notify the channel has been created emit ChannelCreated(msg.sender, merchant, token, amount, numberOfTokens, merchantWithdrawAfterBlocks); - - // revert TokenDoesNotSupportPermit(token); } /**