From 4b5062a222d83867cd21a3f5bfb67e64b345af24 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:22:11 -0700 Subject: [PATCH 1/6] implement reference deposit and register functions (3,267 over) --- src/TheCompact.sol | 199 ++++++++++++++++++++++++++++++++-- src/lib/HashLib.sol | 69 ++++++++++-- src/lib/IdLib.sol | 8 ++ src/types/CompactCategory.sol | 8 ++ src/types/EIP712Types.sol | 28 ++++- 5 files changed, 292 insertions(+), 20 deletions(-) create mode 100644 src/types/CompactCategory.sol diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 344f358..000d274 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.27; import { ITheCompact } from "./interfaces/ITheCompact.sol"; +import { CompactCategory } from "./types/CompactCategory.sol"; +import { ActivatedCompactCategory } from "./types/ActivatedCompactCategory.sol"; import { Lock } from "./types/Lock.sol"; import { Scope } from "./types/Scope.sol"; import { ResetPeriod } from "./types/ResetPeriod.sol"; @@ -154,11 +156,13 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { using HashLib for ExogenousSplitBatchMultichainClaimWithWitness; using HashLib for ExogenousQualifiedSplitBatchMultichainClaim; using HashLib for ExogenousQualifiedSplitBatchMultichainClaimWithWitness; + using HashLib for ActivatedCompactCategory; using IdLib for uint96; using IdLib for uint256; using IdLib for address; using IdLib for Lock; using IdLib for ResetPeriod; + using IdLib for CompactCategory; using SafeTransferLib for address; using FixedPointMathLib for uint256; using ConsumerLib for uint256; @@ -379,6 +383,179 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { _clearTstorish(_REENTRANCY_GUARD_SLOT); } + function depositAndRegister( + uint256 id, + uint256 amount, + uint256 nonce, + uint256 deadline, + address depositor, + bytes32 claimHash, + CompactCategory compactCategory, + string calldata witness, + bytes calldata signature + ) external returns (bool) { + _setTstorish(_REENTRANCY_GUARD_SLOT, 1); + id.toAllocatorId().mustHaveARegisteredAllocator(); + address token = id.toToken().excludingNative(); + + uint256 initialBalance = token.balanceOf(address(this)); + + string memory activationTypestring; + string memory compactTypestring; + if (compactCategory == CompactCategory.Compact) { + compactTypestring = string.concat("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount", bytes(witness).length != 0 ? "," : "", witness, ")"); + activationTypestring = string.concat("Activation(uint256 id,Compact compact)", compactTypestring); + } else if (compactCategory == CompactCategory.BatchCompact) { + compactTypestring = + string.concat("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts", bytes(witness).length != 0 ? "," : "", witness, ")"); + activationTypestring = string.concat("Activation(uint256 id,BatchCompact compact)", compactTypestring); + } else { + compactTypestring = string.concat( + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts", + bytes(witness).length != 0 ? "," : "", + witness, + ")" + ); + activationTypestring = string.concat("Activation(uint256 id,MultichainCompact compact)", compactTypestring); + } + + string memory witnessTypestring = string.concat("Activation witness)", activationTypestring, "TokenPermissions(address token,uint256 amount)"); + + bytes32 witnessHash = keccak256(abi.encodePacked(keccak256(bytes(activationTypestring)), id, claimHash)); + + ISignatureTransfer.SignatureTransferDetails memory details = ISignatureTransfer.SignatureTransferDetails({ to: address(this), requestedAmount: amount }); + + ISignatureTransfer.PermitTransferFrom memory permitTransferFrom = + ISignatureTransfer.PermitTransferFrom({ permitted: ISignatureTransfer.TokenPermissions({ token: token, amount: amount }), nonce: nonce, deadline: deadline }); + + _PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witnessHash, witnessTypestring, signature); + + uint256 tokenBalance = token.balanceOf(address(this)); + + assembly ("memory-safe") { + if iszero(lt(initialBalance, tokenBalance)) { + // revert InvalidDepositBalanceChange() + mstore(0, 0x426d8dcf) + revert(0x1c, 0x04) + } + } + + unchecked { + _deposit(depositor, id, tokenBalance - initialBalance); + } + + _registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring)); + + _clearTstorish(_REENTRANCY_GUARD_SLOT); + + return true; + } + + function depositAndRegister( + address depositor, + ISignatureTransfer.TokenPermissions[] calldata permitted, + address allocator, + ResetPeriod resetPeriod, + Scope scope, + address recipient, + uint256 nonce, + uint256 deadline, + bytes32 claimHash, + CompactCategory compactCategory, + string calldata witness, + bytes calldata signature + ) external payable returns (uint256[] memory ids) { + _setTstorish(_REENTRANCY_GUARD_SLOT, 1); + + uint256 totalTokens = permitted.length; + bool firstUnderlyingTokenIsNative; + assembly ("memory-safe") { + let permittedOffset := permitted.offset + firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, add(permittedOffset, 0x20)))) + + // Revert if: + // * the array is empty + // * the callvalue is zero but the first token is native + // * the callvalue is nonzero but the first token is non-native + // * the first token is non-native and the callvalue doesn't equal the first amount + if or(iszero(totalTokens), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(permittedOffset, 0x40))))))) + { + // revert InvalidBatchDepositStructure() + mstore(0, 0xca0fc08e) + revert(0x1c, 0x04) + } + } + + uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator); + + string memory activationTypestring; + string memory compactTypestring; + if (compactCategory == CompactCategory.Compact) { + compactTypestring = string.concat("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount", bytes(witness).length != 0 ? "," : "", witness, ")"); + activationTypestring = string.concat("BatchActivation(uint256[] ids,Compact compact)", compactTypestring); + } else if (compactCategory == CompactCategory.BatchCompact) { + compactTypestring = + string.concat("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts", bytes(witness).length != 0 ? "," : "", witness, ")"); + activationTypestring = string.concat("BatchActivation(uint256[] ids,BatchCompact compact)", compactTypestring); + } else { + compactTypestring = string.concat( + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts", + bytes(witness).length != 0 ? "," : "", + witness, + ")" + ); + activationTypestring = string.concat("BatchActivation(uint256[] ids,MultichainCompact compact)", compactTypestring); + } + + ids = new uint256[](totalTokens); + + uint256 totalTokensLessInitialNative; + unchecked { + totalTokensLessInitialNative = totalTokens - firstUnderlyingTokenIsNative.asUint256(); + } + + if (firstUnderlyingTokenIsNative) { + _deposit(recipient, initialId, msg.value); + ids[0] = initialId; + } + + (ISignatureTransfer.SignatureTransferDetails[] memory details, ISignatureTransfer.TokenPermissions[] memory permittedTokens, uint256[] memory initialTokenBalances) = + _preparePermit2ArraysAndGetBalances(ids, totalTokensLessInitialNative, firstUnderlyingTokenIsNative, permitted, initialId); + + ISignatureTransfer.PermitBatchTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitBatchTransferFrom({ permitted: permittedTokens, nonce: nonce, deadline: deadline }); + + string memory witnessTypestring = string.concat("BatchActivation witness)", activationTypestring, "TokenPermissions(address token,uint256 amount)"); + + bytes32 witnessHash = keccak256(abi.encodePacked(keccak256(bytes(activationTypestring)), keccak256(abi.encodePacked(ids)), claimHash)); + + _PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witnessHash, witnessTypestring, signature); + + uint256 tokenBalance; + uint256 initialBalance; + uint256 errorBuffer; + unchecked { + for (uint256 i = 0; i < totalTokensLessInitialNative; ++i) { + tokenBalance = permittedTokens[i].token.balanceOf(address(this)); + initialBalance = initialTokenBalances[i]; + errorBuffer |= (initialBalance >= tokenBalance).asUint256(); + + _deposit(recipient, ids[i + firstUnderlyingTokenIsNative.asUint256()], tokenBalance - initialBalance); + } + } + + assembly ("memory-safe") { + if errorBuffer { + // revert InvalidDepositBalanceChange() + mstore(0, 0x426d8dcf) + revert(0x1c, 0x04) + } + } + + _registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring)); + + _clearTstorish(_REENTRANCY_GUARD_SLOT); + } + function deposit( address depositor, ISignatureTransfer.TokenPermissions[] calldata permitted, @@ -412,7 +589,17 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator); return _processBatchPermit2Deposits( - firstUnderlyingTokenIsNative, recipient, initialId, totalTokens, permitted, depositor, nonce, deadline, allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient), signature + firstUnderlyingTokenIsNative, + recipient, + initialId, + totalTokens, + permitted, + depositor, + nonce, + deadline, + allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient), + "CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)", + signature ); } @@ -1768,6 +1955,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { uint256 nonce, uint256 deadline, bytes32 witness, + string memory witnessTypestring, bytes calldata signature ) internal returns (uint256[] memory ids) { _setTstorish(_REENTRANCY_GUARD_SLOT, 1); @@ -1789,14 +1977,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { ISignatureTransfer.PermitBatchTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitBatchTransferFrom({ permitted: permittedTokens, nonce: nonce, deadline: deadline }); - _PERMIT2.permitWitnessTransferFrom( - permitTransferFrom, - details, - depositor, - witness, - "CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)", - signature - ); + _PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witness, witnessTypestring, signature); uint256 tokenBalance; uint256 initialBalance; diff --git a/src/lib/HashLib.sol b/src/lib/HashLib.sol index e093f96..dde3746 100644 --- a/src/lib/HashLib.sol +++ b/src/lib/HashLib.sol @@ -53,7 +53,15 @@ import { PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE, PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR, PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE, - PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX, + COMPACT_ACTIVATION_TYPEHASH, + BATCH_COMPACT_ACTIVATION_TYPEHASH, + MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH, + COMPACT_BATCH_ACTIVATION_TYPEHASH, + BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH, + MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH, + TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE, + TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO } from "../types/EIP712Types.sol"; import { @@ -922,13 +930,20 @@ library HashLib { } } - function toPermit2ActivatedCompactTypehash(ActivatedCompactCategory category, string calldata witness) internal pure returns (bytes32 typehash) { + function toPermit2DepositAndRegisterTypehashes(ActivatedCompactCategory category, string calldata compactWitnessTypestringFragment) + internal + pure + returns (bytes32 permit2Typehash, bytes32 activationTypehash, bytes32 compactTypehash) + { assembly ("memory-safe") { - function toTypehash(c, witnessOffset, witnessLength) -> t { + function toTypehash(c, witnessOffset, witnessLength) -> p, a, t { let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied let isBatch := gt(c, 2) c := sub(c, mul(isBatch, 3)) + let indexWords := shl(5, c) + + let activationStart // 1. handle no-witness cases or prepare first witness fragment based on deposit vs batch deposit let fragmentTwoStart @@ -937,7 +952,18 @@ library HashLib { mstore(0, PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH) mstore(0x20, PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH) mstore(0x40, PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) - t := mload(shl(5, c)) + p := mload(indexWords) + + mstore(0, COMPACT_ACTIVATION_TYPEHASH) + mstore(0x20, BATCH_COMPACT_ACTIVATION_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) + a := mload(indexWords) + + mstore(0, COMPACT_TYPEHASH) + mstore(0x20, BATCH_COMPACT_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) + t := mload(indexWords) + mstore(0x40, m) leave } @@ -948,6 +974,7 @@ library HashLib { mstore(add(m, 0x6d), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE) mstore(add(m, 0x60), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR) fragmentTwoStart := add(m, 0x8d) + activationStart := add(m, 0x77) } if iszero(fragmentTwoStart) { @@ -955,7 +982,18 @@ library HashLib { mstore(0, PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH) mstore(0x20, PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH) mstore(0x40, PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) - t := mload(shl(5, c)) + p := mload(indexWords) + + mstore(0, COMPACT_BATCH_ACTIVATION_TYPEHASH) + mstore(0x20, BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH) + a := mload(indexWords) + + mstore(0, COMPACT_TYPEHASH) + mstore(0x20, BATCH_COMPACT_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) + t := mload(indexWords) + mstore(0x40, m) leave } @@ -967,6 +1005,7 @@ library HashLib { mstore(add(m, 0x80), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE) mstore8(add(m, 0xa0), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX) fragmentTwoStart := add(m, 0xa1) + activationStart := add(m, 0x83) } // 2. prepare second witness fragment based on compact category @@ -997,14 +1036,26 @@ library HashLib { fragmentThreeStart := add(fragmentTwoStart, 0xb0) } - // 3. insert the supplied witness (must also include TokenPermissions) + // 3. insert the supplied compact witness calldatacopy(fragmentThreeStart, witnessOffset, witnessLength) - // 4. derive the typehash - t := keccak256(m, add(sub(fragmentThreeStart, m), witnessLength)) + // 4. insert tokenPermissions + let tokenPermissionsFragmentStart := add(fragmentThreeStart, witnessLength) + mstore(add(tokenPermissionsFragmentStart, 0x0e), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO) + mstore(tokenPermissionsFragmentStart, TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE) + + // 5. derive the permit2 typehash + let totalPayloadSizeWithoutTokenPermissions := sub(tokenPermissionsFragmentStart, m) + p := keccak256(m, add(totalPayloadSizeWithoutTokenPermissions, 0x2e)) + + // 6. derive the activation typehash + a := keccak256(activationStart, sub(totalPayloadSizeWithoutTokenPermissions, activationStart)) + + // 7. derive the compact typehash + t := keccak256(fragmentTwoStart, sub(totalPayloadSizeWithoutTokenPermissions, fragmentTwoStart)) } - typehash := toTypehash(category, witness.offset, witness.length) + permit2Typehash, activationTypehash, compactTypehash := toTypehash(category, compactWitnessTypestringFragment.offset, compactWitnessTypestringFragment.length) } } } diff --git a/src/lib/IdLib.sol b/src/lib/IdLib.sol index a4dff4f..016f4e4 100644 --- a/src/lib/IdLib.sol +++ b/src/lib/IdLib.sol @@ -7,6 +7,8 @@ import { Lock } from "../types/Lock.sol"; import { MetadataLib } from "./MetadataLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; +import { CompactCategory } from "../types/CompactCategory.sol"; +import { ActivatedCompactCategory } from "../types/ActivatedCompactCategory.sol"; library IdLib { using IdLib for uint96; @@ -192,6 +194,12 @@ library IdLib { return (msg.sender == allocator).or(allocator.code.length > 0).or(proof.length == 86 && (proof[0] == 0xff).and(allocator == address(uint160(uint256(keccak256(proof)))))); } + function toActivated(CompactCategory category, bool batch) internal pure returns (ActivatedCompactCategory activatedCategory) { + assembly ("memory-safe") { + activatedCategory := add(category, mul(batch, 3)) + } + } + function register(address allocator) internal returns (uint96 allocatorId) { allocatorId = allocator.usingAllocatorId(); diff --git a/src/types/CompactCategory.sol b/src/types/CompactCategory.sol new file mode 100644 index 0000000..f345f5e --- /dev/null +++ b/src/types/CompactCategory.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +enum CompactCategory { + Compact, + BatchCompact, + MultichainCompact +} diff --git a/src/types/EIP712Types.sol b/src/types/EIP712Types.sol index 346ca34..df3974b 100644 --- a/src/types/EIP712Types.sol +++ b/src/types/EIP712Types.sol @@ -124,21 +124,39 @@ bytes32 constant PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH = 0xe055493563385cc588fff /// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)TokenPermissions(address token,uint256 amount)"))`. bytes32 constant PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH = 0xf653f659d3a9d0c2d3b3e901b5f71b67ad949a927c0c27b41dcdf4ecddd9b489; +/// @dev `keccak256(bytes("Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"))`. +bytes32 constant COMPACT_ACTIVATION_TYPEHASH = 0x2bf981c42c7f423b06fa49ba996d2930887e2f1f53d9a26b8c7423ac1cf83e61; + /// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. bytes32 constant PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH = 0x17513dd8a454440fed0be792cc9b0b440ce3fc8ee96c1b1b1a836d2846eb6756; +/// @dev `keccak256(bytes("Activation(uint256 id,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)"))`. +bytes32 constant BATCH_COMPACT_ACTIVATION_TYPEHASH = 0xd14445d78213a5acddfa89171b0199de521c3b36738b835264cae18f5a53dbf3; + /// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. bytes32 constant PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x6a707dd548c9a14542ee33ebe7c7bedffc0f1a3bc827c73459e910b3b7f7ebe1; +/// @dev `keccak256(bytes("Activation(uint256 id,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. +bytes32 constant MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x329b3c527a3c74b8cabc51c304669d1866b87352cafdf440ef2becd6dc261d1e; + /// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)TokenPermissions(address token,uint256 amount)"))`. bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH = 0xc57615aa5f4f1313fa11445825d867465f56dfccd036e04265bfe0dd050822fd; +/// @dev `keccak256(bytes("BatchActivation(uint256[] ids,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"))`. +bytes32 constant COMPACT_BATCH_ACTIVATION_TYPEHASH = 0x45012d42fad8c9e937cff5a2d750ee18713dd45aadcd718660d5523056618d99; + /// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH = 0x95e426a66b0811209294f67c0bf2ae0aab045490466d04cd6bf18cb70295c0ad; -/// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. +/// @dev `keccak256(bytes("BatchActivation(uint256[] ids,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)"))`. +bytes32 constant BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH = 0xc2e16a823b8cdddfdf889991d7a461f0a19faf1f8e608f1c164495a52151cc3e; + +/// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x142f91d42a44f5b5264d0eac9eddd2080a6c1649a31d76457dcc0ff612ff69d6; +/// @dev `keccak256(bytes("BatchActivation(uint256[] ids,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. +bytes32 constant MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH = 0xd2f6ad391328936f118250f231e63c7e639f9756a9ebf972d81763870a772d87; + // abi.decode(bytes("PermitWitnessTransferFrom(TokenP"), (bytes32)) bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE = 0x5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e50; @@ -169,7 +187,7 @@ bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR // abi.decode(bytes("ss)BatchActivation(uint256[] ids"), (bytes32)) bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE = 0x737329426174636841637469766174696f6e2875696e743235365b5d20696473; -// uint8(abi.decode(bytes("n(uint256 id,"), (bytes1))) +// uint8(abi.decode(bytes(","), (bytes1))) uint8 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX = 0x2c; // abi.decode(bytes("Compact compact)Compact(address "), (bytes32)) @@ -213,3 +231,9 @@ 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; + +// uint112(abi.decode(bytes("int256 amount)"), (bytes14))) +uint112 constant TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO = 0x696e7432353620616d6f756e7429; From 47c4802b19e9f0f4a47534502c7e3d2a95db5c05 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:24:58 -0700 Subject: [PATCH 2/6] optimize deposit + register & get under codesize (removed batch permit2 deposit for now) --- foundry.toml | 2 +- snapshots/TheCompactTest.json | 63 ++-- src/TheCompact.sol | 416 ++++++++++++++++--------- src/interfaces/ITheCompact.sol | 16 +- src/lib/HashLib.sol | 151 +-------- src/lib/IdLib.sol | 7 - src/types/ActivatedCompactCategory.sol | 11 - src/types/EIP712Types.sol | 55 +--- test/TheCompact.t.sol | 2 + 9 files changed, 328 insertions(+), 395 deletions(-) delete mode 100644 src/types/ActivatedCompactCategory.sol diff --git a/foundry.toml b/foundry.toml index 6c27aae..d9e7d95 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ solc = '0.8.28' evm_version='cancun' via_ir = true # optimizer_runs = 4_294_967_295 -optimizer_runs = 9 +optimizer_runs = 200 bytecode_hash = 'none' src = "src" out = "out" diff --git a/snapshots/TheCompactTest.json b/snapshots/TheCompactTest.json index 86584af..bfa3da6 100644 --- a/snapshots/TheCompactTest.json +++ b/snapshots/TheCompactTest.json @@ -1,34 +1,33 @@ { - "basicTransfer": "57220", - "basicWithdrawal": "60350", - "batchClaim": "112331", - "batchClaimWithWitness": "112988", - "batchTransfer": "82936", - "batchWithdrawal": "101303", - "claim": "60821", - "claimAndWithdraw": "73704", - "claimWithWitness": "60004", - "depositBatchSingleERC20": "67765", - "depositBatchViaPermit2SingleERC20": "107820", - "depositERC20AndURI": "67076", - "depositERC20Basic": "67081", - "depositERC20ViaPermit2AndURI": "98237", - "depositETHAndURI": "26774", - "depositETHBasic": "28237", - "qualifiedBatchClaim": "113658", - "qualifiedBatchClaimWithWitness": "113117", - "qualifiedClaim": "60719", - "qualifiedClaimWithWitness": "59407", - "qualifiedSplitBatchClaim": "141263", - "qualifiedSplitBatchClaimWithWitness": "141228", - "qualifiedSplitClaim": "86947", - "qualifiedSplitClaimWithWitness": "87358", - "splitBatchClaim": "140743", - "splitBatchClaimWithWitness": "140674", - "splitBatchTransfer": "113646", - "splitBatchWithdrawal": "142938", - "splitClaim": "86873", - "splitClaimWithWitness": "86452", - "splitTransfer": "83176", - "splitWithdrawal": "94106" + "basicTransfer": "57231", + "basicWithdrawal": "60321", + "batchClaim": "112342", + "batchClaimWithWitness": "112999", + "batchTransfer": "82862", + "batchWithdrawal": "101233", + "claim": "60835", + "claimAndWithdraw": "73718", + "claimWithWitness": "59909", + "depositBatchSingleERC20": "67728", + "depositERC20AndURI": "67021", + "depositERC20Basic": "67026", + "depositERC20ViaPermit2AndURI": "98015", + "depositETHAndURI": "26694", + "depositETHBasic": "28186", + "qualifiedBatchClaim": "113669", + "qualifiedBatchClaimWithWitness": "113128", + "qualifiedClaim": "60733", + "qualifiedClaimWithWitness": "59312", + "qualifiedSplitBatchClaim": "141215", + "qualifiedSplitBatchClaimWithWitness": "141180", + "qualifiedSplitClaim": "86961", + "qualifiedSplitClaimWithWitness": "87256", + "splitBatchClaim": "140724", + "splitBatchClaimWithWitness": "140655", + "splitBatchTransfer": "113519", + "splitBatchWithdrawal": "142771", + "splitClaim": "86887", + "splitClaimWithWitness": "86379", + "splitTransfer": "83187", + "splitWithdrawal": "94053" } \ No newline at end of file diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 000d274..59155e3 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.27; import { ITheCompact } from "./interfaces/ITheCompact.sol"; import { CompactCategory } from "./types/CompactCategory.sol"; -import { ActivatedCompactCategory } from "./types/ActivatedCompactCategory.sol"; import { Lock } from "./types/Lock.sol"; import { Scope } from "./types/Scope.sol"; import { ResetPeriod } from "./types/ResetPeriod.sol"; @@ -85,7 +84,33 @@ import { ExogenousQualifiedSplitBatchMultichainClaimWithWitness } from "./types/BatchMultichainClaims.sol"; -import { COMPACT_TYPEHASH, BATCH_COMPACT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH } from "./types/EIP712Types.sol"; +import { + COMPACT_TYPEHASH, + BATCH_COMPACT_TYPEHASH, + MULTICHAIN_COMPACT_TYPEHASH, + 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, + COMPACT_ACTIVATION_TYPEHASH, + BATCH_COMPACT_ACTIVATION_TYPEHASH, + MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH, + PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_ONE, + PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_TWO, + PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_THREE, + PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_FOUR, + PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE, + PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO, + PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE, + PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_ONE, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_TWO, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE, + PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX +} from "./types/EIP712Types.sol"; import { SplitComponent, TransferComponent, SplitByIdComponent, BatchClaimComponent, SplitBatchClaimComponent } from "./types/Components.sol"; @@ -156,7 +181,6 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { using HashLib for ExogenousSplitBatchMultichainClaimWithWitness; using HashLib for ExogenousQualifiedSplitBatchMultichainClaim; using HashLib for ExogenousQualifiedSplitBatchMultichainClaimWithWitness; - using HashLib for ActivatedCompactCategory; using IdLib for uint96; using IdLib for uint256; using IdLib for address; @@ -208,11 +232,13 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { uint256 private immutable _INITIAL_CHAIN_ID; bytes32 private immutable _INITIAL_DOMAIN_SEPARATOR; MetadataRenderer private immutable _METADATA_RENDERER; + bool private immutable _PERMIT2_INITIALLY_DEPLOYED; constructor() { _INITIAL_CHAIN_ID = block.chainid; _INITIAL_DOMAIN_SEPARATOR = block.chainid.toNotarizedDomainSeparator(); _METADATA_RENDERER = new MetadataRenderer(); + _PERMIT2_INITIALLY_DEPLOYED = _checkPermit2Deployment(); } function deposit(address allocator) external payable returns (uint256 id) { @@ -253,56 +279,6 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { return _processBatchDeposit(idsAndAmounts, msg.sender); } - function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal returns (bool) { - _setTstorish(_REENTRANCY_GUARD_SLOT, 1); - uint256 totalIds = idsAndAmounts.length; - bool firstUnderlyingTokenIsNative; - uint256 id; - - assembly ("memory-safe") { - let idsAndAmountsOffset := idsAndAmounts.offset - id := calldataload(idsAndAmountsOffset) - firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, id))) - // Revert if: - // * the array is empty - // * the callvalue is zero but the first token is native - // * the callvalue is nonzero but the first token is non-native - // * the first token is non-native and the callvalue doesn't equal the first amount - if or(iszero(totalIds), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(idsAndAmountsOffset, 0x20))))))) - { - // revert InvalidBatchDepositStructure() - mstore(0, 0xca0fc08e) - revert(0x1c, 0x04) - } - } - - uint96 currentAllocatorId = id.toRegisteredAllocatorId(); - - if (firstUnderlyingTokenIsNative) { - _deposit(recipient, id, msg.value); - } - - unchecked { - for (uint256 i = firstUnderlyingTokenIsNative.asUint256(); i < totalIds; ++i) { - uint256[2] calldata idAndAmount = idsAndAmounts[i]; - id = idAndAmount[0]; - uint256 amount = idAndAmount[1]; - - uint96 newAllocatorId = id.toAllocatorId(); - if (newAllocatorId != currentAllocatorId) { - newAllocatorId.mustHaveARegisteredAllocator(); - currentAllocatorId = newAllocatorId; - } - - _transferAndDeposit(id.toToken(), recipient, id, amount); - } - } - - _clearTstorish(_REENTRANCY_GUARD_SLOT); - - return true; - } - function deposit( address token, uint256, // amount @@ -384,51 +360,150 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { } function depositAndRegister( - uint256 id, - uint256 amount, - uint256 nonce, - uint256 deadline, - address depositor, + address token, + uint256, // amount + uint256, // nonce + uint256, // deadline + address depositor, // also recipient + address allocator, + ResetPeriod resetPeriod, + Scope scope, bytes32 claimHash, CompactCategory compactCategory, string calldata witness, bytes calldata signature - ) external returns (bool) { + ) external returns (uint256 id) { _setTstorish(_REENTRANCY_GUARD_SLOT, 1); - id.toAllocatorId().mustHaveARegisteredAllocator(); - address token = id.toToken().excludingNative(); + id = token.excludingNative().toIdIfRegistered(scope, resetPeriod, allocator); + + address permit2 = address(_PERMIT2); uint256 initialBalance = token.balanceOf(address(this)); - string memory activationTypestring; - string memory compactTypestring; - if (compactCategory == CompactCategory.Compact) { - compactTypestring = string.concat("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount", bytes(witness).length != 0 ? "," : "", witness, ")"); - activationTypestring = string.concat("Activation(uint256 id,Compact compact)", compactTypestring); - } else if (compactCategory == CompactCategory.BatchCompact) { - compactTypestring = - string.concat("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts", bytes(witness).length != 0 ? "," : "", witness, ")"); - activationTypestring = string.concat("Activation(uint256 id,BatchCompact compact)", compactTypestring); - } else { - compactTypestring = string.concat( - "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts", - bytes(witness).length != 0 ? "," : "", - witness, - ")" - ); - activationTypestring = string.concat("Activation(uint256 id,MultichainCompact compact)", compactTypestring); - } + bool isPermit2Deployed = _isPermit2Deployed(); - string memory witnessTypestring = string.concat("Activation witness)", activationTypestring, "TokenPermissions(address token,uint256 amount)"); + bytes32 compactTypehash; - bytes32 witnessHash = keccak256(abi.encodePacked(keccak256(bytes(activationTypestring)), id, claimHash)); + assembly ("memory-safe") { + function writeWitnessAndGetTypehashes(memoryLocation, c, witnessOffset, witnessLength) -> derivedActivationTypehash, derivedCompactTypehash { + let memoryOffset := add(memoryLocation, 0x20) + // 1. prepare initial witness string at offset + mstore(add(memoryOffset, 0x09), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO) + mstore(memoryOffset, PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE) + let activationStart := add(memoryOffset, 0x13) + let categorySpecificStart := add(memoryOffset, 0x29) + let categorySpecificEnd + if iszero(c) { + mstore(categorySpecificStart, PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_ONE) + mstore(add(categorySpecificStart, 0x20), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_TWO) + mstore(add(categorySpecificStart, 0x50), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_FOUR) + mstore(add(categorySpecificStart, 0x40), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_THREE) + categorySpecificEnd := add(categorySpecificStart, 0x70) + categorySpecificStart := add(categorySpecificStart, 0x10) + } - ISignatureTransfer.SignatureTransferDetails memory details = ISignatureTransfer.SignatureTransferDetails({ to: address(this), requestedAmount: amount }); + if iszero(sub(c, 1)) { + mstore(categorySpecificStart, PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE) + mstore(add(categorySpecificStart, 0x20), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO) + mstore(add(categorySpecificStart, 0x5b), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR) + mstore(add(categorySpecificStart, 0x40), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE) + categorySpecificEnd := add(categorySpecificStart, 0x7b) + categorySpecificStart := add(categorySpecificStart, 0x15) + } - ISignatureTransfer.PermitTransferFrom memory permitTransferFrom = - ISignatureTransfer.PermitTransferFrom({ permitted: ISignatureTransfer.TokenPermissions({ token: token, amount: amount }), nonce: nonce, deadline: deadline }); + if iszero(categorySpecificEnd) { + mstore(categorySpecificStart, PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_ONE) + mstore(add(categorySpecificStart, 0x20), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_TWO) + mstore(add(categorySpecificStart, 0x40), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE) + mstore(add(categorySpecificStart, 0x60), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR) + mstore(add(categorySpecificStart, 0x70), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX) + mstore(add(categorySpecificStart, 0x60), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE) + categorySpecificEnd := add(categorySpecificStart, 0x90) + categorySpecificStart := add(categorySpecificStart, 0x1a) + } - _PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witnessHash, witnessTypestring, signature); + // 2. handle no-witness cases + 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(memoryLocation, sub(add(categorySpecificEnd, 0x2e), memoryOffset)) + + let m := mload(0x40) + + mstore(0, COMPACT_ACTIVATION_TYPEHASH) + mstore(0x20, BATCH_COMPACT_ACTIVATION_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) + derivedActivationTypehash := mload(indexWords) + + mstore(0, COMPACT_TYPEHASH) + mstore(0x20, BATCH_COMPACT_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) + derivedCompactTypehash := mload(indexWords) + + mstore(0x40, m) + leave + } + + // 3. insert the supplied compact witness + calldatacopy(categorySpecificEnd, witnessOffset, witnessLength) + + // 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(memoryLocation, sub(add(tokenPermissionsFragmentStart, 0x2f), memoryOffset)) + + categorySpecificEnd := add(tokenPermissionsFragmentStart, 1) + + // 5. derive the activation typehash + derivedActivationTypehash := keccak256(activationStart, sub(categorySpecificEnd, activationStart)) + + // 6. derive the compact typehash + derivedCompactTypehash := keccak256(categorySpecificStart, sub(categorySpecificEnd, categorySpecificStart)) + } + + let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied. + + let signatureLength := signature.length + let dataStart := add(m, 0x1c) + + mstore(m, _PERMIT_WITNESS_TRANSFER_FROM_SELECTOR) + calldatacopy(add(m, 0x20), 0x04, 0x80) // token, amount, nonce, deadline + mstore(add(m, 0xa0), address()) + mstore(add(m, 0xc0), calldataload(0x24)) // amount + mstore(add(m, 0xe0), calldataload(0x84)) // depositor + mstore(add(m, 0x120), 0x140) + + 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)) + + mstore(0, activationTypehash) + mstore(0x20, id) + mstore(0x40, claimHash) + 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))) { + // bubble up if the call failed and there's data + // NOTE: consider evaluating remaining gas to protect against revert bombing + if returndatasize() { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + + // TODO: add proper revert on no data + revert(0, 0) + } + } uint256 tokenBalance = token.balanceOf(address(this)); @@ -444,11 +519,57 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { _deposit(depositor, id, tokenBalance - initialBalance); } - _registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring)); + _registeredClaimHashes[depositor][claimHash] = compactTypehash; _clearTstorish(_REENTRANCY_GUARD_SLOT); + } - return true; + /* TODO: put these two batch deposit methods back in after finding some room for them + function deposit( + address depositor, + ISignatureTransfer.TokenPermissions[] calldata permitted, + address allocator, + ResetPeriod resetPeriod, + Scope scope, + address recipient, + uint256 nonce, + uint256 deadline, + bytes calldata signature + ) external payable returns (uint256[] memory) { + uint256 totalTokens = permitted.length; + bool firstUnderlyingTokenIsNative; + assembly ("memory-safe") { + let permittedOffset := permitted.offset + firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, add(permittedOffset, 0x20)))) + + // Revert if: + // * the array is empty + // * the callvalue is zero but the first token is native + // * the callvalue is nonzero but the first token is non-native + // * the first token is non-native and the callvalue doesn't equal the first amount + if or(iszero(totalTokens), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(permittedOffset, 0x40))))))) + { + // revert InvalidBatchDepositStructure() + mstore(0, 0xca0fc08e) + revert(0x1c, 0x04) + } + } + + uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator); + + return _processBatchPermit2Deposits( + firstUnderlyingTokenIsNative, + recipient, + initialId, + totalTokens, + permitted, + depositor, + nonce, + deadline, + allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient), + "CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)", + signature + ); } function depositAndRegister( @@ -555,53 +676,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { _clearTstorish(_REENTRANCY_GUARD_SLOT); } - - function deposit( - address depositor, - ISignatureTransfer.TokenPermissions[] calldata permitted, - address allocator, - ResetPeriod resetPeriod, - Scope scope, - address recipient, - uint256 nonce, - uint256 deadline, - bytes calldata signature - ) external payable returns (uint256[] memory) { - uint256 totalTokens = permitted.length; - bool firstUnderlyingTokenIsNative; - assembly ("memory-safe") { - let permittedOffset := permitted.offset - firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, add(permittedOffset, 0x20)))) - - // Revert if: - // * the array is empty - // * the callvalue is zero but the first token is native - // * the callvalue is nonzero but the first token is non-native - // * the first token is non-native and the callvalue doesn't equal the first amount - if or(iszero(totalTokens), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(permittedOffset, 0x40))))))) - { - // revert InvalidBatchDepositStructure() - mstore(0, 0xca0fc08e) - revert(0x1c, 0x04) - } - } - - uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator); - - return _processBatchPermit2Deposits( - firstUnderlyingTokenIsNative, - recipient, - initialId, - totalTokens, - permitted, - depositor, - nonce, - deadline, - allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient), - "CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)", - signature - ); - } + */ function allocatedTransfer(BasicTransfer calldata transfer) external returns (bool) { return _processBasicTransfer(transfer, _release); @@ -1160,6 +1235,56 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { } } + function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal returns (bool) { + _setTstorish(_REENTRANCY_GUARD_SLOT, 1); + uint256 totalIds = idsAndAmounts.length; + bool firstUnderlyingTokenIsNative; + uint256 id; + + assembly ("memory-safe") { + let idsAndAmountsOffset := idsAndAmounts.offset + id := calldataload(idsAndAmountsOffset) + firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, id))) + // Revert if: + // * the array is empty + // * the callvalue is zero but the first token is native + // * the callvalue is nonzero but the first token is non-native + // * the first token is non-native and the callvalue doesn't equal the first amount + if or(iszero(totalIds), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(idsAndAmountsOffset, 0x20))))))) + { + // revert InvalidBatchDepositStructure() + mstore(0, 0xca0fc08e) + revert(0x1c, 0x04) + } + } + + uint96 currentAllocatorId = id.toRegisteredAllocatorId(); + + if (firstUnderlyingTokenIsNative) { + _deposit(recipient, id, msg.value); + } + + unchecked { + for (uint256 i = firstUnderlyingTokenIsNative.asUint256(); i < totalIds; ++i) { + uint256[2] calldata idAndAmount = idsAndAmounts[i]; + id = idAndAmount[0]; + uint256 amount = idAndAmount[1]; + + uint96 newAllocatorId = id.toAllocatorId(); + if (newAllocatorId != currentAllocatorId) { + newAllocatorId.mustHaveARegisteredAllocator(); + currentAllocatorId = newAllocatorId; + } + + _transferAndDeposit(id.toToken(), recipient, id, amount); + } + } + + _clearTstorish(_REENTRANCY_GUARD_SLOT); + + return true; + } + function _notExpiredAndSignedByAllocator(bytes32 messageHash, address allocator, BasicTransfer calldata transferPayload) internal { transferPayload.expires.later(); @@ -2003,14 +2128,6 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { _clearTstorish(_REENTRANCY_GUARD_SLOT); } - function _verifyAndProcessBatchComponents( - uint96 allocatorId, - address sponsor, - address claimant, - BatchClaimComponent[] calldata claims, - function(address, address, uint256, uint256) internal returns (bool) operation - ) internal returns (bool) { } - function _verifyAndProcessSplitComponents( address sponsor, uint256 id, @@ -2294,4 +2411,19 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { } } } + + function _isPermit2Deployed() internal view returns (bool) { + if (_PERMIT2_INITIALLY_DEPLOYED) { + return true; + } + + return _checkPermit2Deployment(); + } + + function _checkPermit2Deployment() internal view returns (bool permit2Deployed) { + address permit2 = address(_PERMIT2); + assembly ("memory-safe") { + permit2Deployed := iszero(iszero(extcodesize(permit2))) + } + } } diff --git a/src/interfaces/ITheCompact.sol b/src/interfaces/ITheCompact.sol index 20d0401..f7f99ed 100644 --- a/src/interfaces/ITheCompact.sol +++ b/src/interfaces/ITheCompact.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.27; import { ForcedWithdrawalStatus } from "../types/ForcedWithdrawalStatus.sol"; import { ResetPeriod } from "../types/ResetPeriod.sol"; import { Scope } from "../types/Scope.sol"; +import { CompactCategory } from "../types/CompactCategory.sol"; import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; import { BasicTransfer, @@ -120,17 +121,20 @@ interface ITheCompact { bytes calldata signature ) external returns (uint256 id); - function deposit( + function depositAndRegister( + address token, + uint256 amount, + uint256 nonce, + uint256 deadline, address depositor, - ISignatureTransfer.TokenPermissions[] calldata permitted, address allocator, ResetPeriod resetPeriod, Scope scope, - address recipient, - uint256 nonce, - uint256 deadline, + bytes32 claimHash, + CompactCategory compactCategory, + string calldata witness, bytes calldata signature - ) external payable returns (uint256[] memory ids); + ) external returns (uint256 id); function allocatedTransfer(BasicTransfer calldata transfer) external returns (bool); diff --git a/src/lib/HashLib.sol b/src/lib/HashLib.sol index dde3746..73cfa7b 100644 --- a/src/lib/HashLib.sol +++ b/src/lib/HashLib.sol @@ -22,24 +22,6 @@ import { MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE, MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR, MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE, - PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH, - PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH, - PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE, - PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO, - PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE, - PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR, - PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE, - PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX, PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_ONE, PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_TWO, PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_THREE, @@ -61,7 +43,8 @@ import { BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH, MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH, TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE, - TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO + TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO, + PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH } from "../types/EIP712Types.sol"; import { @@ -130,7 +113,6 @@ import { import { TransferComponent, SplitComponent, SplitByIdComponent, BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol"; -import { ActivatedCompactCategory } from "../types/ActivatedCompactCategory.sol"; import { ResetPeriod } from "../types/ResetPeriod.sol"; import { Scope } from "../types/Scope.sol"; @@ -929,133 +911,4 @@ library HashLib { messageHash := keccak256(m, 0xa0) } } - - function toPermit2DepositAndRegisterTypehashes(ActivatedCompactCategory category, string calldata compactWitnessTypestringFragment) - internal - pure - returns (bytes32 permit2Typehash, bytes32 activationTypehash, bytes32 compactTypehash) - { - assembly ("memory-safe") { - function toTypehash(c, witnessOffset, witnessLength) -> p, a, t { - let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied - - let isBatch := gt(c, 2) - c := sub(c, mul(isBatch, 3)) - let indexWords := shl(5, c) - - let activationStart - - // 1. handle no-witness cases or prepare first witness fragment based on deposit vs batch deposit - let fragmentTwoStart - if iszero(isBatch) { - if iszero(witnessLength) { - mstore(0, PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH) - mstore(0x20, PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH) - mstore(0x40, PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) - p := mload(indexWords) - - mstore(0, COMPACT_ACTIVATION_TYPEHASH) - mstore(0x20, BATCH_COMPACT_ACTIVATION_TYPEHASH) - mstore(0x40, MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) - a := mload(indexWords) - - mstore(0, COMPACT_TYPEHASH) - mstore(0x20, BATCH_COMPACT_TYPEHASH) - mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) - t := mload(indexWords) - - mstore(0x40, m) - leave - } - - mstore(m, PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE) - mstore(add(m, 0x20), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO) - mstore(add(m, 0x40), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE) - mstore(add(m, 0x6d), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE) - mstore(add(m, 0x60), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR) - fragmentTwoStart := add(m, 0x8d) - activationStart := add(m, 0x77) - } - - if iszero(fragmentTwoStart) { - if iszero(witnessLength) { - mstore(0, PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH) - mstore(0x20, PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH) - mstore(0x40, PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH) - p := mload(indexWords) - - mstore(0, COMPACT_BATCH_ACTIVATION_TYPEHASH) - mstore(0x20, BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH) - mstore(0x40, MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH) - a := mload(indexWords) - - mstore(0, COMPACT_TYPEHASH) - mstore(0x20, BATCH_COMPACT_TYPEHASH) - mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) - t := mload(indexWords) - - mstore(0x40, m) - leave - } - - mstore(m, PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE) - mstore(add(m, 0x20), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO) - mstore(add(m, 0x40), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE) - mstore(add(m, 0x60), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR) - mstore(add(m, 0x80), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE) - mstore8(add(m, 0xa0), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX) - fragmentTwoStart := add(m, 0xa1) - activationStart := add(m, 0x83) - } - - // 2. prepare second witness fragment based on compact category - let fragmentThreeStart - if iszero(c) { - mstore(fragmentTwoStart, PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_ONE) - mstore(add(fragmentTwoStart, 0x20), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_TWO) - mstore(add(fragmentTwoStart, 0x50), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_FOUR) - mstore(add(fragmentTwoStart, 0x40), PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_THREE) - fragmentThreeStart := add(fragmentTwoStart, 0x70) - } - - if iszero(sub(c, 1)) { - mstore(fragmentTwoStart, PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE) - mstore(add(fragmentTwoStart, 0x20), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO) - mstore(add(fragmentTwoStart, 0x5b), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR) - mstore(add(fragmentTwoStart, 0x40), PERMIT2_ACTIVATION_BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE) - fragmentThreeStart := add(fragmentTwoStart, 0x7b) - } - - if iszero(fragmentThreeStart) { - mstore(fragmentTwoStart, PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_ONE) - mstore(add(fragmentTwoStart, 0x20), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_TWO) - mstore(add(fragmentTwoStart, 0x40), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE) - mstore(add(fragmentTwoStart, 0x60), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR) - mstore(add(fragmentTwoStart, 0x90), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX) - mstore(add(fragmentTwoStart, 0x80), PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE) - fragmentThreeStart := add(fragmentTwoStart, 0xb0) - } - - // 3. insert the supplied compact witness - calldatacopy(fragmentThreeStart, witnessOffset, witnessLength) - - // 4. insert tokenPermissions - let tokenPermissionsFragmentStart := add(fragmentThreeStart, witnessLength) - mstore(add(tokenPermissionsFragmentStart, 0x0e), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO) - mstore(tokenPermissionsFragmentStart, TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE) - - // 5. derive the permit2 typehash - let totalPayloadSizeWithoutTokenPermissions := sub(tokenPermissionsFragmentStart, m) - p := keccak256(m, add(totalPayloadSizeWithoutTokenPermissions, 0x2e)) - - // 6. derive the activation typehash - a := keccak256(activationStart, sub(totalPayloadSizeWithoutTokenPermissions, activationStart)) - - // 7. derive the compact typehash - t := keccak256(fragmentTwoStart, sub(totalPayloadSizeWithoutTokenPermissions, fragmentTwoStart)) - } - - permit2Typehash, activationTypehash, compactTypehash := toTypehash(category, compactWitnessTypestringFragment.offset, compactWitnessTypestringFragment.length) - } - } } diff --git a/src/lib/IdLib.sol b/src/lib/IdLib.sol index 016f4e4..ddc3ef0 100644 --- a/src/lib/IdLib.sol +++ b/src/lib/IdLib.sol @@ -8,7 +8,6 @@ import { MetadataLib } from "./MetadataLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; import { CompactCategory } from "../types/CompactCategory.sol"; -import { ActivatedCompactCategory } from "../types/ActivatedCompactCategory.sol"; library IdLib { using IdLib for uint96; @@ -194,12 +193,6 @@ library IdLib { return (msg.sender == allocator).or(allocator.code.length > 0).or(proof.length == 86 && (proof[0] == 0xff).and(allocator == address(uint160(uint256(keccak256(proof)))))); } - function toActivated(CompactCategory category, bool batch) internal pure returns (ActivatedCompactCategory activatedCategory) { - assembly ("memory-safe") { - activatedCategory := add(category, mul(batch, 3)) - } - } - function register(address allocator) internal returns (uint96 allocatorId) { allocatorId = allocator.usingAllocatorId(); diff --git a/src/types/ActivatedCompactCategory.sol b/src/types/ActivatedCompactCategory.sol deleted file mode 100644 index 54ecefa..0000000 --- a/src/types/ActivatedCompactCategory.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; - -enum ActivatedCompactCategory { - Compact, - BatchCompact, - MultichainCompact, - BatchDepositCompact, - BatchDepositBatchCompact, - BatchDepositMultichainCompact -} diff --git a/src/types/EIP712Types.sol b/src/types/EIP712Types.sol index df3974b..dc40908 100644 --- a/src/types/EIP712Types.sol +++ b/src/types/EIP712Types.sol @@ -121,74 +121,35 @@ bytes32 constant EMISSARY_ASSIGNMENT_TYPEHASH = 0x5ca9a66b8bbf0d2316e90dfa3df465 /// @dev `keccak256(bytes("CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)"))`. bytes32 constant PERMIT2_DEPOSIT_WITNESS_FRAGMENT_HASH = 0xe055493563385cc588fffacbffe2dab023fef807baa449530431169b0eeb5b69; -/// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)TokenPermissions(address token,uint256 amount)"))`. -bytes32 constant PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH = 0xf653f659d3a9d0c2d3b3e901b5f71b67ad949a927c0c27b41dcdf4ecddd9b489; - /// @dev `keccak256(bytes("Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"))`. bytes32 constant COMPACT_ACTIVATION_TYPEHASH = 0x2bf981c42c7f423b06fa49ba996d2930887e2f1f53d9a26b8c7423ac1cf83e61; -/// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. -bytes32 constant PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH = 0x17513dd8a454440fed0be792cc9b0b440ce3fc8ee96c1b1b1a836d2846eb6756; - /// @dev `keccak256(bytes("Activation(uint256 id,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)"))`. bytes32 constant BATCH_COMPACT_ACTIVATION_TYPEHASH = 0xd14445d78213a5acddfa89171b0199de521c3b36738b835264cae18f5a53dbf3; -/// @dev `keccak256(bytes("PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. -bytes32 constant PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x6a707dd548c9a14542ee33ebe7c7bedffc0f1a3bc827c73459e910b3b7f7ebe1; - /// @dev `keccak256(bytes("Activation(uint256 id,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. bytes32 constant MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x329b3c527a3c74b8cabc51c304669d1866b87352cafdf440ef2becd6dc261d1e; -/// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)TokenPermissions(address token,uint256 amount)"))`. -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH = 0xc57615aa5f4f1313fa11445825d867465f56dfccd036e04265bfe0dd050822fd; - /// @dev `keccak256(bytes("BatchActivation(uint256[] ids,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"))`. bytes32 constant COMPACT_BATCH_ACTIVATION_TYPEHASH = 0x45012d42fad8c9e937cff5a2d750ee18713dd45aadcd718660d5523056618d99; -/// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)TokenPermissions(address token,uint256 amount)"))`. -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH = 0x95e426a66b0811209294f67c0bf2ae0aab045490466d04cd6bf18cb70295c0ad; - /// @dev `keccak256(bytes("BatchActivation(uint256[] ids,BatchCompact compact)BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)"))`. bytes32 constant BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH = 0xc2e16a823b8cdddfdf889991d7a461f0a19faf1f8e608f1c164495a52151cc3e; -/// @dev `keccak256(bytes("PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,BatchActivation witness)BatchActivation(uint256[] ids,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH = 0x142f91d42a44f5b5264d0eac9eddd2080a6c1649a31d76457dcc0ff612ff69d6; - /// @dev `keccak256(bytes("BatchActivation(uint256[] ids,MultichainCompact compact)MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts)"))`. bytes32 constant MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH = 0xd2f6ad391328936f118250f231e63c7e639f9756a9ebf972d81763870a772d87; -// abi.decode(bytes("PermitWitnessTransferFrom(TokenP"), (bytes32)) -bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE = 0x5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e50; - -// abi.decode(bytes("ermissions permitted,address spe"), (bytes32)) -bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO = 0x65726d697373696f6e73207065726d69747465642c6164647265737320737065; - -// abi.decode(bytes("nder,uint256 nonce,uint256 deadl"), (bytes32)) -bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE = 0x6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c; - -// abi.decode(bytes("ine,Activation witness)Activatio"), (bytes32)) -bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR = 0x696e652c41637469766174696f6e207769746e6573732941637469766174696f; - -// uint104(abi.decode(bytes("n(uint256 id,"), (bytes13))) -uint104 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE = 0x6e2875696e743235362069642c; - -// abi.decode(bytes("PermitBatchWitnessTransferFrom(T"), (bytes32)) -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE = 0x5065726d697442617463685769746e6573735472616e7366657246726f6d2854; - -// abi.decode(bytes("okenPermissions[] permitted,addr"), (bytes32)) -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO = 0x6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472; - -// abi.decode(bytes("ess spender,uint256 nonce,uint25"), (bytes32)) -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_THREE = 0x657373207370656e6465722c75696e74323536206e6f6e63652c75696e743235; +// abi.decode(bytes("Activation witness)Activation(ui"), (bytes32)) +bytes32 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE = 0x41637469766174696f6e207769746e6573732941637469766174696f6e287569; -// abi.decode(bytes("6 deadline,BatchActivation witne"), (bytes32)) -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR = 0x3620646561646c696e652c426174636841637469766174696f6e207769746e65; +// uint72(abi.decode(bytes("nt256 id,"), (bytes9))) +uint72 constant PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO = 0x6e743235362069642c; -// abi.decode(bytes("ss)BatchActivation(uint256[] ids"), (bytes32)) -bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE = 0x737329426174636841637469766174696f6e2875696e743235365b5d20696473; +// abi.decode(bytes("BatchActivation witness)BatchAct"), (bytes32)) +bytes32 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_ONE = 0x426174636841637469766174696f6e207769746e657373294261746368416374; -// uint8(abi.decode(bytes(","), (bytes1))) -uint8 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX = 0x2c; +// uint176(abi.decode(bytes("ivation(uint256[] ids,"), (bytes22))) +uint176 constant PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_TWO = 0x69766174696f6e2875696e743235365b5d206964732c; // abi.decode(bytes("Compact compact)Compact(address "), (bytes32)) bytes32 constant PERMIT2_ACTIVATION_COMPACT_TYPESTRING_FRAGMENT_ONE = 0x436f6d7061637420636f6d7061637429436f6d70616374286164647265737320; diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 4d266dd..8dc9186 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -341,6 +341,7 @@ contract TheCompactTest is Test { assert(bytes(theCompact.tokenURI(id)).length > 0); } + /* TODO: add this test back once there's room for batch permit2 deposits again function test_depositBatchViaPermit2SingleERC20() public { address recipient = 0x1111111111111111111111111111111111111111; ResetPeriod resetPeriod = ResetPeriod.TenMinutes; @@ -397,6 +398,7 @@ contract TheCompactTest is Test { assertEq(theCompact.balanceOf(recipient, ids[0]), amount); assert(bytes(theCompact.tokenURI(ids[0])).length > 0); } + */ function test_basicTransfer() public { ResetPeriod resetPeriod = ResetPeriod.TenMinutes; From 888e55fc43ccb296dd4f2493fe203eeddc445873 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:51:26 -0700 Subject: [PATCH 3/6] add test for register + claim --- snapshots/TheCompactTest.json | 19 +++++++------- src/TheCompact.sol | 21 +++++++++++----- src/interfaces/ITheCompact.sol | 1 + test/TheCompact.t.sol | 45 ++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/snapshots/TheCompactTest.json b/snapshots/TheCompactTest.json index bfa3da6..34c1a34 100644 --- a/snapshots/TheCompactTest.json +++ b/snapshots/TheCompactTest.json @@ -1,12 +1,12 @@ { "basicTransfer": "57231", "basicWithdrawal": "60321", - "batchClaim": "112342", + "batchClaim": "112336", "batchClaimWithWitness": "112999", "batchTransfer": "82862", "batchWithdrawal": "101233", - "claim": "60835", - "claimAndWithdraw": "73718", + "claim": "57450", + "claimAndWithdraw": "73712", "claimWithWitness": "59909", "depositBatchSingleERC20": "67728", "depositERC20AndURI": "67021", @@ -14,19 +14,20 @@ "depositERC20ViaPermit2AndURI": "98015", "depositETHAndURI": "26694", "depositETHBasic": "28186", - "qualifiedBatchClaim": "113669", + "qualifiedBatchClaim": "113663", "qualifiedBatchClaimWithWitness": "113128", - "qualifiedClaim": "60733", + "qualifiedClaim": "60727", "qualifiedClaimWithWitness": "59312", - "qualifiedSplitBatchClaim": "141215", + "qualifiedSplitBatchClaim": "141209", "qualifiedSplitBatchClaimWithWitness": "141180", - "qualifiedSplitClaim": "86961", + "qualifiedSplitClaim": "86955", "qualifiedSplitClaimWithWitness": "87256", - "splitBatchClaim": "140724", + "register": "24890", + "splitBatchClaim": "140718", "splitBatchClaimWithWitness": "140655", "splitBatchTransfer": "113519", "splitBatchWithdrawal": "142771", - "splitClaim": "86887", + "splitClaim": "86881", "splitClaimWithWitness": "86379", "splitTransfer": "83187", "splitWithdrawal": "94053" diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 59155e3..846ebfd 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -519,7 +519,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { _deposit(depositor, id, tokenBalance - initialBalance); } - _registeredClaimHashes[depositor][claimHash] = compactTypehash; + _register(depositor, claimHash, compactTypehash); _clearTstorish(_REENTRANCY_GUARD_SLOT); } @@ -672,7 +672,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { } } - _registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring)); + _register(depositor, claimHash], keccak256(bytes(compactTypestring)); _clearTstorish(_REENTRANCY_GUARD_SLOT); } @@ -1139,10 +1139,15 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { } function register(bytes32 claimHash, bytes32 typehash) external returns (bool) { - _registeredClaimHashes[msg.sender][claimHash] = typehash; + _register(msg.sender, claimHash, typehash); return true; } + function _register(address sponsor, bytes32 claimHash, bytes32 typehash) internal { + _registeredClaimHashes[sponsor][claimHash] = typehash; + emit CompactRegistered(sponsor, claimHash, typehash); + } + function register(bytes32[2][] calldata claimHashesAndTypehashes) external returns (bool) { return _registerFor(msg.sender, claimHashesAndTypehashes); } @@ -1152,7 +1157,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { uint256 totalClaimHashes = claimHashesAndTypehashes.length; for (uint256 i = 0; i < totalClaimHashes; ++i) { bytes32[2] calldata claimHashAndTypehash = claimHashesAndTypehashes[i]; - _registeredClaimHashes[sponsor][claimHashAndTypehash[0]] = claimHashAndTypehash[1]; + _register(sponsor, claimHashAndTypehash[0], claimHashAndTypehash[1]); } } @@ -1867,8 +1872,12 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { function _typehashes(uint256 i) internal pure returns (bytes32 typehash) { assembly ("memory-safe") { - let j := sub(i, 1) - typehash := add(mul(iszero(i), COMPACT_TYPEHASH), add(mul(iszero(j), BATCH_COMPACT_TYPEHASH), mul(iszero(iszero(j)), MULTICHAIN_COMPACT_TYPEHASH))) + let m := mload(0x40) + mstore(0, COMPACT_TYPEHASH) + mstore(0x20, BATCH_COMPACT_TYPEHASH) + mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH) + typehash := mload(shl(5, i)) + mstore(0x40, m) } } diff --git a/src/interfaces/ITheCompact.sol b/src/interfaces/ITheCompact.sol index f7f99ed..e6f1954 100644 --- a/src/interfaces/ITheCompact.sol +++ b/src/interfaces/ITheCompact.sol @@ -83,6 +83,7 @@ interface ITheCompact { event ForcedWithdrawalEnabled(address indexed account, uint256 indexed id, uint256 withdrawableAt); event ForcedWithdrawalDisabled(address indexed account, uint256 indexed id); event AllocatorRegistered(uint96 allocatorId, address allocator); + event CompactRegistered(address indexed sponsor, bytes32 claimHash, bytes32 typehash); error InvalidToken(address token); error Expired(uint256 expiration); diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 8dc9186..1d7852e 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -898,6 +898,51 @@ contract TheCompactTest is Test { assertEq(theCompact.balanceOf(claimant, id), amount); } + function test_registerAndClaim() public { + ResetPeriod resetPeriod = ResetPeriod.TenMinutes; + Scope scope = Scope.Multichain; + uint256 amount = 1e18; + uint256 nonce = 0; + uint256 expires = block.timestamp + 1000; + address claimant = 0x1111111111111111111111111111111111111111; + address arbiter = 0x2222222222222222222222222222222222222222; + + vm.prank(allocator); + theCompact.__registerAllocator(allocator, ""); + + vm.prank(swapper); + uint256 id = theCompact.deposit{ value: amount }(allocator, resetPeriod, scope, swapper); + assertEq(theCompact.balanceOf(swapper, id), amount); + + bytes32 typehash = keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"); + + bytes32 claimHash = keccak256(abi.encode(typehash, arbiter, swapper, nonce, expires, id, amount)); + + bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash)); + + vm.prank(swapper); + (bool status) = theCompact.register(claimHash, typehash); + vm.snapshotGasLastCall("register"); + assert(status); + + bytes memory sponsorSignature = ""; + + (bytes32 r, bytes32 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); + (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); + } + function test_claimAndWithdraw() public { ResetPeriod resetPeriod = ResetPeriod.TenMinutes; Scope scope = Scope.Multichain; From 76a37dff2d146fb8d07d58670130fe4377e98c13 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Sun, 27 Oct 2024 22:52:29 -0700 Subject: [PATCH 4/6] =?UTF-8?q?progress=20on=20deposit+register+claim=20?= =?UTF-8?q?=E2=80=94=20still=20hitting=20a=20signature=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/TheCompact.sol | 25 ++++++------ src/lib/HashLib.sol | 4 +- src/types/EIP712Types.sol | 8 ++-- test/TheCompact.t.sol | 84 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 19 deletions(-) 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; From f7e5fbe5813aaed50068880abc6512b4cee87bfd Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:17:55 -0700 Subject: [PATCH 5/6] get depositAndRegister + claim test working! --- test/TheCompact.t.sol | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 4e9a31b..843646b 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -343,7 +343,6 @@ contract TheCompactTest is Test { } function test_depositAndRegisterViaPermit2ThenClaim() public { - address recipient = 0x1111111111111111111111111111111111111111; ResetPeriod resetPeriod = ResetPeriod.TenMinutes; Scope scope = Scope.Multichain; uint256 amount = 1e18; @@ -360,12 +359,16 @@ contract TheCompactTest is Test { assertEq(domainSeparator, EIP712(permit2).DOMAIN_SEPARATOR()); - bytes32 typehash = keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"); + string memory compactTypestring = "Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"; + + bytes32 typehash = keccak256(bytes(compactTypestring)); 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 activationTypehash = keccak256(bytes(string.concat("Activation(uint256 id,Compact compact)", compactTypestring))); + bytes32 digest = keccak256( abi.encodePacked( bytes2(0x1901), @@ -373,17 +376,13 @@ contract TheCompactTest is Test { 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)" + "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)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 - ) - ) + keccak256(abi.encode(activationTypehash, id, claimHash)) ) ) ) @@ -403,7 +402,7 @@ contract TheCompactTest is Test { assertEq(uint256(derivedScope), uint256(scope)); assertEq(token.balanceOf(address(theCompact)), amount); - assertEq(theCompact.balanceOf(recipient, id), amount); + assertEq(theCompact.balanceOf(swapper, id), amount); digest = keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash)); @@ -419,8 +418,7 @@ contract TheCompactTest is Test { vm.snapshotGasLastCall("claim"); assert(status); - assertEq(address(theCompact).balance, amount); - assertEq(claimant.balance, 0); + assertEq(token.balanceOf(address(theCompact)), amount); assertEq(theCompact.balanceOf(swapper, id), 0); assertEq(theCompact.balanceOf(claimant, id), amount); } From 03047a726340ef94d49ff1e02d7a48d0ba9f8f5d Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:26:44 -0700 Subject: [PATCH 6/6] make a small fix to witness case and add test --- src/TheCompact.sol | 12 +++--- test/TheCompact.t.sol | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 9c0b90f..ada8a2a 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -451,17 +451,15 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish { // 4. insert tokenPermissions let tokenPermissionsFragmentStart := add(categorySpecificEnd, witnessLength) - 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) + mstore(add(tokenPermissionsFragmentStart, 0x0e), TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_TWO) + mstore(sub(tokenPermissionsFragmentStart, 1), TOKEN_PERMISSIONS_TYPESTRING_FRAGMENT_ONE) + mstore(memoryLocation, sub(add(tokenPermissionsFragmentStart, 0x2e), memoryOffset)) // 5. derive the activation typehash - derivedActivationTypehash := keccak256(activationStart, sub(categorySpecificEnd, activationStart)) + derivedActivationTypehash := keccak256(activationStart, sub(tokenPermissionsFragmentStart, activationStart)) // 6. derive the compact typehash - derivedCompactTypehash := keccak256(categorySpecificStart, sub(categorySpecificEnd, categorySpecificStart)) + derivedCompactTypehash := keccak256(categorySpecificStart, sub(tokenPermissionsFragmentStart, categorySpecificStart)) } let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied. diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 843646b..3ab3759 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -1168,6 +1168,93 @@ contract TheCompactTest is Test { assertEq(theCompact.balanceOf(claimant, id), amount); } + function test_depositAndRegisterWithWitnessViaPermit2ThenClaim() public { + 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()); + + string memory witnessTypestring = "CompactWitness witness)CompactWitness(uint256 witnessArgument)"; + uint256 witnessArgument = 234; + bytes32 witness = keccak256(abi.encode(keccak256(bytes("CompactWitness(uint256 witnessArgument)")), witnessArgument)); + + string memory compactTypestring = + "Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount,CompactWitness witness)CompactWitness(uint256 witnessArgument)"; + + bytes32 typehash = keccak256(bytes(compactTypestring)); + + 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, witness)); + + bytes32 activationTypehash = keccak256(bytes(string.concat("Activation(uint256 id,Compact compact)", compactTypestring))); + + bytes32 digest = keccak256( + abi.encodePacked( + bytes2(0x1901), + domainSeparator, + keccak256( + abi.encode( + keccak256( + "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Activation witness)Activation(uint256 id,Compact compact)Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount,CompactWitness witness)CompactWitness(uint256 witnessArgument)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(activationTypehash, 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, witnessTypestring, 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(swapper, 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); + + ClaimWithWitness memory claim = ClaimWithWitness(allocatorSignature, sponsorSignature, swapper, nonce, expires, witness, witnessTypestring, id, amount, claimant, amount); + + vm.prank(arbiter); + (bool status) = theCompact.claim(claim); + vm.snapshotGasLastCall("claim"); + assert(status); + + assertEq(token.balanceOf(address(theCompact)), amount); + assertEq(theCompact.balanceOf(swapper, id), 0); + assertEq(theCompact.balanceOf(claimant, id), amount); + } + function test_qualifiedClaimWithWitness() public { ResetPeriod resetPeriod = ResetPeriod.TenMinutes; Scope scope = Scope.Multichain;