diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 846ebfd..9c0b90f 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -91,8 +91,8 @@ import { PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH, PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE, PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO, - TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE, - TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO, + TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE, + TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO, COMPACT_ACTIVATION_TYPEHASH, BATCH_COMPACT_ACTIVATION_TYPEHASH, MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH, @@ -426,8 +426,8 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { if iszero(witnessLength) { let indexWords := shl(5, c) - mstore(add(categorySpecificEnd, 0x0e), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO) - mstore(sub(categorySpecificEnd, 1), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE) + mstore(add(categorySpecificEnd, 0x0e), TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO) + mstore(sub(categorySpecificEnd, 1), TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE) mstore(memoryLocation, sub(add(categorySpecificEnd, 0x2e), memoryOffset)) let m := mload(0x40) @@ -451,8 +451,8 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { // 4. insert tokenPermissions let tokenPermissionsFragmentStart := add(categorySpecificEnd, witnessLength) - mstore(add(tokenPermissionsFragmentStart, 0x0f), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO) - mstore(tokenPermissionsFragmentStart, TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE) + mstore(add(tokenPermissionsFragmentStart, 0x0f), TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO) + mstore(tokenPermissionsFragmentStart, TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE) mstore(memoryLocation, sub(add(tokenPermissionsFragmentStart, 0x2f), memoryOffset)) categorySpecificEnd := add(tokenPermissionsFragmentStart, 1) @@ -479,9 +479,11 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { let permit2WitnessOffset := add(m, 0x160) let activationTypehash activationTypehash, compactTypehash := writeWitnessAndGetTypehashes(permit2WitnessOffset, compactCategory, witness.offset, witness.length) - let signatureOffset := and(add(mload(permit2WitnessOffset), 0x5f), not(0x1f)) - mstore(add(m, 0x140), signatureOffset) - signatureOffset := add(m, add(signatureOffset, 0x20)) + let signatureOffsetValue := and(add(mload(permit2WitnessOffset), 0x17f), not(0x1f)) + mstore(add(m, 0x140), signatureOffsetValue) + let signatureOffset := add(m, add(signatureOffsetValue, 0x20)) + mstore(signatureOffset, signatureLength) + calldatacopy(add(signatureOffset, 0x20), signature.offset, signatureLength) mstore(0, activationTypehash) mstore(0x20, id) @@ -489,10 +491,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { mstore(add(m, 0x100), keccak256(0, 0x60)) mstore(0x40, m) - mstore(signatureOffset, signatureLength) - calldatacopy(add(signatureOffset, 0x20), signature.offset, signatureLength) - - if iszero(and(isPermit2Deployed, call(gas(), permit2, 0, add(m, 0x1c), add(0x24, add(signatureOffset, signatureLength)), 0, 0))) { + if iszero(and(isPermit2Deployed, call(gas(), permit2, 0, add(m, 0x1c), add(0x24, add(signatureOffsetValue, signatureLength)), 0, 0))) { // bubble up if the call failed and there's data // NOTE: consider evaluating remaining gas to protect against revert bombing if returndatasize() { diff --git a/src/lib/HashLib.sol b/src/lib/HashLib.sol index 73cfa7b..e704835 100644 --- a/src/lib/HashLib.sol +++ b/src/lib/HashLib.sol @@ -42,8 +42,8 @@ import { COMPACT_BATCH_ACTIVATION_TYPEHASH, BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH, MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH, - TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE, - TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO, + TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE, + TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO, PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH } from "../types/EIP712Types.sol"; diff --git a/src/types/EIP712Types.sol b/src/types/EIP712Types.sol index dc40908..2285534 100644 --- a/src/types/EIP712Types.sol +++ b/src/types/EIP712Types.sol @@ -193,8 +193,8 @@ bytes32 constant PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE // uint128(abi.decode(bytes("] idsAndAmounts,"), (bytes16))) uint128 constant PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX = 0x5d20696473416e64416d6f756e74732c; -// abi.decode(bytes("TokenPermissions(address token,u"), (bytes32)) -bytes32 constant TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE = 0x546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c75; +// abi.decode(bytes(")TokenPermissions(address token,"), (bytes32)) +bytes32 constant TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE = 0x29546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c; -// uint112(abi.decode(bytes("int256 amount)"), (bytes14))) -uint112 constant TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO = 0x696e7432353620616d6f756e7429; +// uint120(abi.decode(bytes("uint256 amount)"), (bytes15))) +uint120 constant TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO = 0x75696e7432353620616d6f756e7429; diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 1d7852e..4e9a31b 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -7,6 +7,7 @@ import { MockERC20 } from "../lib/solady/test/utils/mocks/MockERC20.sol"; import { Compact, BatchCompact, Segment } from "../src/types/EIP712Types.sol"; import { ResetPeriod } from "../src/types/ResetPeriod.sol"; import { Scope } from "../src/types/Scope.sol"; +import { CompactCategory } from "../src/types/CompactCategory.sol"; import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; import { HashLib } from "../src/lib/HashLib.sol"; @@ -341,6 +342,89 @@ contract TheCompactTest is Test { assert(bytes(theCompact.tokenURI(id)).length > 0); } + function test_depositAndRegisterViaPermit2ThenClaim() public { + address recipient = 0x1111111111111111111111111111111111111111; + ResetPeriod resetPeriod = ResetPeriod.TenMinutes; + Scope scope = Scope.Multichain; + uint256 amount = 1e18; + uint256 nonce = 0; + uint256 deadline = block.timestamp + 1000; + uint256 expires = block.timestamp + 1000; + address claimant = 0x1111111111111111111111111111111111111111; + address arbiter = 0x2222222222222222222222222222222222222222; + + vm.prank(allocator); + uint96 allocatorId = theCompact.__registerAllocator(allocator, ""); + + bytes32 domainSeparator = keccak256(abi.encode(permit2EIP712DomainHash, keccak256(bytes("Permit2")), block.chainid, address(permit2))); + + assertEq(domainSeparator, EIP712(permit2).DOMAIN_SEPARATOR()); + + bytes32 typehash = keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"); + + uint256 id = (uint256(scope) << 255) | (uint256(resetPeriod) << 252) | (uint256(allocatorId) << 160) | uint256(uint160(address(token))); + + bytes32 claimHash = keccak256(abi.encode(typehash, arbiter, swapper, nonce, expires, id, amount)); + + bytes32 digest = keccak256( + abi.encodePacked( + bytes2(0x1901), + domainSeparator, + keccak256( + abi.encode( + keccak256( + "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)TokenPermissions(address token,uint256 amount)" + ), + keccak256(abi.encode(keccak256("TokenPermissions(address token,uint256 amount)"), address(token), amount)), + address(theCompact), // spender + nonce, + deadline, + keccak256( + abi.encode( + keccak256("Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"), id, claimHash + ) + ) + ) + ) + ) + ); + + (bytes32 r, bytes32 vs) = vm.signCompact(swapperPrivateKey, digest); + bytes memory signature = abi.encodePacked(r, vs); + + uint256 returnedId = theCompact.depositAndRegister(address(token), amount, nonce, deadline, swapper, allocator, resetPeriod, scope, claimHash, CompactCategory.Compact, "", signature); + vm.snapshotGasLastCall("depositAndRegisterViaPermit2"); + assertEq(returnedId, id); + + (address derivedToken, address derivedAllocator, ResetPeriod derivedResetPeriod, Scope derivedScope) = theCompact.getLockDetails(id); + assertEq(derivedToken, address(token)); + assertEq(derivedAllocator, allocator); + assertEq(uint256(derivedResetPeriod), uint256(resetPeriod)); + assertEq(uint256(derivedScope), uint256(scope)); + + assertEq(token.balanceOf(address(theCompact)), amount); + assertEq(theCompact.balanceOf(recipient, id), amount); + + digest = keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash)); + + bytes memory sponsorSignature = ""; + + (r, vs) = vm.signCompact(allocatorPrivateKey, digest); + bytes memory allocatorSignature = abi.encodePacked(r, vs); + + BasicClaim memory claim = BasicClaim(allocatorSignature, sponsorSignature, swapper, nonce, expires, id, amount, claimant, amount); + + vm.prank(arbiter); + (bool status) = theCompact.claim(claim); + vm.snapshotGasLastCall("claim"); + assert(status); + + assertEq(address(theCompact).balance, amount); + assertEq(claimant.balance, 0); + assertEq(theCompact.balanceOf(swapper, id), 0); + assertEq(theCompact.balanceOf(claimant, id), amount); + } + /* TODO: add this test back once there's room for batch permit2 deposits again function test_depositBatchViaPermit2SingleERC20() public { address recipient = 0x1111111111111111111111111111111111111111;