diff --git a/src/base/BaseBundlerV2.sol b/src/base/BaseBundlerV2.sol index a24069d4..d3ca5312 100644 --- a/src/base/BaseBundlerV2.sol +++ b/src/base/BaseBundlerV2.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import {CoreBundler} from "../CoreBundler.sol"; import {TransferBundler} from "../TransferBundler.sol"; import {Permit2Bundler} from "../Permit2Bundler.sol"; +import {PermitBundler} from "../PermitBundler.sol"; import {ERC4626Bundler} from "../ERC4626Bundler.sol"; import {WNativeBundler} from "../WNativeBundler.sol"; import {UrdBundler} from "../UrdBundler.sol"; @@ -17,6 +18,7 @@ import {ERC20WrapperBundler} from "../ERC20WrapperBundler.sol"; contract BaseBundlerV2 is TransferBundler, Permit2Bundler, + PermitBundler, ERC4626Bundler, WNativeBundler, UrdBundler, diff --git a/test/forge/fork/PermitBundlerForkTest.sol b/test/forge/fork/PermitBundlerForkTest.sol index 6086a9be..5495fcd5 100644 --- a/test/forge/fork/PermitBundlerForkTest.sol +++ b/test/forge/fork/PermitBundlerForkTest.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.0; import {ErrorsLib} from "../../../src/libraries/ErrorsLib.sol"; -import {DaiPermit} from "../helpers/SigUtils.sol"; +import {DaiPermit, Permit} from "../helpers/SigUtils.sol"; import "../../../src/ethereum/EthereumPermitBundler.sol"; +import {IERC20Permit} from "../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import {ERC20PermitMock} from "../../../src/mocks/ERC20PermitMock.sol"; import "./helpers/ForkTest.sol"; @@ -13,6 +15,14 @@ import "./helpers/ForkTest.sol"; bytes32 constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; contract PermitBundlerForkTest is ForkTest { + ERC20PermitMock internal permitToken; + + function setUp() public override { + super.setUp(); + + permitToken = new ERC20PermitMock("Permit Token", "PT"); + } + function testPermitDai(uint256 privateKey, uint256 expiry) public onlyEthereum { expiry = bound(expiry, block.timestamp, type(uint48).max); privateKey = bound(privateKey, 1, type(uint160).max); @@ -62,4 +72,76 @@ contract PermitBundlerForkTest is ForkTest { return abi.encodeCall(EthereumPermitBundler.permitDai, (nonce, expiry, allowed, v, r, s, skipRevert)); } + + function testPermit(uint256 amount, uint256 privateKey, uint256 deadline) public { + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + deadline = bound(deadline, block.timestamp, type(uint48).max); + privateKey = bound(privateKey, 1, type(uint160).max); + + address user = vm.addr(privateKey); + + bundle.push(_permit(permitToken, privateKey, amount, deadline, false)); + bundle.push(_permit(permitToken, privateKey, amount, deadline, true)); + + vm.prank(user); + bundler.multicall(bundle); + + assertEq(permitToken.allowance(user, address(bundler)), amount, "allowance(user, bundler)"); + } + + function testPermitUninitiated(uint256 amount) public { + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + + vm.expectRevert(bytes(ErrorsLib.UNINITIATED)); + PermitBundler(address(bundler)).permit(address(USDC), amount, SIGNATURE_DEADLINE, 0, 0, 0, true); + } + + function testPermitRevert(uint256 amount, uint256 privateKey, uint256 deadline) public { + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + deadline = bound(deadline, block.timestamp, type(uint48).max); + privateKey = bound(privateKey, 1, type(uint160).max); + + address user = vm.addr(privateKey); + + bundle.push(_permit(permitToken, privateKey, amount, deadline, false)); + bundle.push(_permit(permitToken, privateKey, amount, deadline, false)); + + vm.prank(user); + vm.expectRevert("ERC20Permit: invalid signature"); + bundler.multicall(bundle); + } + + function testTransferFrom(uint256 amount, uint256 privateKey, uint256 deadline) public { + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + deadline = bound(deadline, block.timestamp, type(uint48).max); + privateKey = bound(privateKey, 1, type(uint160).max); + + address user = vm.addr(privateKey); + + bundle.push(_permit(permitToken, privateKey, amount, deadline, false)); + bundle.push(_erc20TransferFrom(address(permitToken), amount)); + + permitToken.setBalance(user, amount); + + vm.prank(user); + bundler.multicall(bundle); + + assertEq(permitToken.balanceOf(address(bundler)), amount, "balanceOf(bundler)"); + assertEq(permitToken.balanceOf(user), 0, "balanceOf(user)"); + } + + function _permit(IERC20Permit token, uint256 privateKey, uint256 amount, uint256 deadline, bool skipRevert) + internal + view + returns (bytes memory) + { + address user = vm.addr(privateKey); + + Permit memory permit = Permit(user, address(bundler), amount, token.nonces(user), deadline); + + bytes32 digest = SigUtils.toTypedDataHash(token.DOMAIN_SEPARATOR(), permit); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodeCall(PermitBundler.permit, (address(token), amount, deadline, v, r, s, skipRevert)); + } }