From b6bf591b585a6d4cfb715df2990374ea0f7d511b Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:51:47 +0100 Subject: [PATCH] chore: restore permit2.t.sol --- scripts/solidity/Permit2.t.sol | 435 +++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 scripts/solidity/Permit2.t.sol diff --git a/scripts/solidity/Permit2.t.sol b/scripts/solidity/Permit2.t.sol new file mode 100644 index 00000000..f4415ce9 --- /dev/null +++ b/scripts/solidity/Permit2.t.sol @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "permit2/src/Permit2.sol"; +import "permit2/src/interfaces/ISignatureTransfer.sol"; + +contract Permit2Test is Test { + bytes32 constant TOKEN_PERMISSIONS_TYPEHASH = + keccak256("TokenPermissions(address token,uint256 amount)"); + bytes32 constant PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( + "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" + ); + + string testMnemonic = "whale pepper wink eight disease negative renew volume dream forest clean rent"; + + // DAI address + // mainnet: 0x6b175474e89094c44da98b954eedeac495271d0f + // goerli: 0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844 + IERC20 daiContract = IERC20(0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844); + + // the same address on mainnet and goerli + Permit2 permit2Contract = Permit2(0x000000000022D473030F116dDEE9F6B43aC78BA3); + + address botAddress = 0xa701216C86b1fFC1F0E4D592DA4186eD519eaDf9; + address userAddress = 0x398cb4c0a4821667373DDEB713dd3371c968460b; + address userAddress2 = 0xe3E77B89CCa2A37d98359A3463822e8e888EB363; + + uint botPrivateKey = vm.deriveKey(testMnemonic, 0); + + function setUp() public { + // use goerli fork + vm.selectFork( + vm.createFork("https://goerli.infura.io/v3/42c7a210df614077867503863d375617") + ); + // bot allows permit2 to spend 1k DAI (this operation should run only once) + vm.prank(botAddress); + daiContract.approve(address(permit2Contract), 1000e18); + } + + function testPermitTransferFrom() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter's balance before + uint userBalanceBefore = daiContract.balanceOf(userAddress); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + vm.prank(userAddress); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + + // bounty hunter's balance after + uint userBalanceAfter = daiContract.balanceOf(userAddress); + assertEq(userBalanceAfter, userBalanceBefore + 1 ether); + } + + function testPermitTransferFrom_ShouldRevert_IfNonceHasAlreadyBeenUsed() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + vm.prank(userAddress); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + + // bounty hunter tries to execute the same tx again + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfNonceIsNotValid() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + permitTransferFromData.nonce = 1; + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfUserModifiedPermitAmount() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + permitTransferFromData.permitted.amount = 2e18; + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfUserTriesToExecuteNotHisTx() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + vm.prank(userAddress2); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + function testPermitTransferFrom_ShouldSendRewardToAnotherAddress() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + uint user2BalanceBefore = daiContract.balanceOf(userAddress2); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress2, + requestedAmount: 1e18 + }); + vm.prank(userAddress); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + + uint user2BalanceAfter = daiContract.balanceOf(userAddress2); + assertEq(user2BalanceAfter, user2BalanceBefore + 1 ether); + } + + function testPermitTransferFrom_ShouldSendHalfOfTheReward() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + uint userBalanceBefore = daiContract.balanceOf(userAddress); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 0.5 ether + }); + vm.prank(userAddress); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + + uint userBalanceAfter = daiContract.balanceOf(userAddress); + assertEq(userBalanceAfter, userBalanceBefore + 0.5 ether); + } + + function testPermitTransferFrom_ShouldRevert_IfRequestedAmountIsGreaterThanPermitted() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 2 ether + }); + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfOwnerAddressIsInvalid() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1 ether + }); + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, userAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfUserTriesToUseSignatureFromAlreadyPaidBounty() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1 ether + }); + vm.prank(userAddress); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + + // bot (or admin) creates a 2nd signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData2 = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 1, + deadline: block.timestamp + }); + + // bounty hunter calls 2nd permitTransferFrom with the old signature + ISignatureTransfer.SignatureTransferDetails memory transferDetails2 = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1 ether + }); + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData2, transferDetails2, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfUserTriesToUseSignatureFromNotYetPaidBounty() public { + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 0, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // bot (or admin) creates a 2nd signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData2 = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: 1, + deadline: block.timestamp + }); + + // bounty hunter calls 2nd permitTransferFrom with the signature from tx with nonce 0 + ISignatureTransfer.SignatureTransferDetails memory transferDetails2 = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1 ether + }); + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData2, transferDetails2, botAddress, sig); + } + + function testPermitTransferFrom_ShouldRevert_IfNonceHasBeenInvalidated() public { + // nonce we are invalidating + uint nonce = 999; + + // bot (or admin) creates a signature for bounty hunter + ISignatureTransfer.PermitTransferFrom memory permitTransferFromData = ISignatureTransfer.PermitTransferFrom({ + permitted: ISignatureTransfer.TokenPermissions({ + token: address(daiContract), + amount: 1e18 + }), + nonce: nonce, + deadline: block.timestamp + }); + bytes memory sig = _signPermit(permitTransferFromData, userAddress, botPrivateKey); + + // check that nonce is not used + assertFalse(_isNonceUsed(botAddress, nonce)); + + // invalidate nonce + (uint wordPos, uint mask) = _getParamsForNonceInvalidation(botAddress, nonce); + vm.prank(botAddress); + permit2Contract.invalidateUnorderedNonces(wordPos, mask); + + // check that nonce is marked as used + assertTrue(_isNonceUsed(botAddress, nonce)); + + // bounty hunter calls permitTransferFrom and transfers reward + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ + to: userAddress, + requestedAmount: 1e18 + }); + vm.prank(userAddress); + vm.expectRevert(); + permit2Contract.permitTransferFrom(permitTransferFromData, transferDetails, botAddress, sig); + } + + /** + * Helper functions + */ + + // Generate a signature for a permit message. + function _signPermit( + ISignatureTransfer.PermitTransferFrom memory permit, + address spender, + uint256 signerKey + ) + internal + view + returns (bytes memory sig) + { + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(signerKey, _getEIP712Hash(permit, spender)); + return abi.encodePacked(r, s, v); + } + + // Compute the EIP712 hash of the permit object. + // Normally this would be implemented off-chain. + function _getEIP712Hash(ISignatureTransfer.PermitTransferFrom memory permit, address spender) + internal + view + returns (bytes32 h) + { + return keccak256(abi.encodePacked( + "\x19\x01", + permit2Contract.DOMAIN_SEPARATOR(), + keccak256(abi.encode( + PERMIT_TRANSFER_FROM_TYPEHASH, + keccak256(abi.encode( + TOKEN_PERMISSIONS_TYPEHASH, + permit.permitted.token, + permit.permitted.amount + )), + spender, + permit.nonce, + permit.deadline + )) + )); + } + + // Checks whether a permit nonce is used + function _isNonceUsed(address from, uint nonce) internal returns (bool) { + // find word position (first 248 bits of nonce) + uint wordPos = uint248(nonce >> 8); + // find bit position in bitmap + uint bitPos = uint8(nonce); + // prepare a mask for target bit + uint256 bit = 1 << bitPos; + // get bitmap with a flipped bit + uint sourceBitmap = permit2Contract.nonceBitmap(from, wordPos); + uint256 flipped = sourceBitmap ^= bit; + // check if any bit has been updated + return flipped & bit == 0; + } + + // Returns params to be used in "SignatureTransfer.invalidateUnorderedNonces()" + function _getParamsForNonceInvalidation(address from, uint nonce) internal returns(uint256 wordPos, uint256 mask) { + wordPos = uint248(nonce >> 8); + uint bitPos = uint8(nonce); + uint256 bit = 1 << bitPos; + uint sourceBitmap = permit2Contract.nonceBitmap(from, wordPos); + mask = sourceBitmap | bit; + } +} \ No newline at end of file