diff --git a/snapshots/TheCompactTest.json b/snapshots/TheCompactTest.json index 89d5521..3dc1fa5 100644 --- a/snapshots/TheCompactTest.json +++ b/snapshots/TheCompactTest.json @@ -1,17 +1,17 @@ { - "basicTransfer": "56861", - "basicWithdrawal": "59819", - "batchClaim": "111831", - "batchClaimRegisteredWithDeposit": "111831", - "batchClaimRegisteredWithDepositWithWitness": "112593", - "batchClaimWithWitness": "112587", + "basicTransfer": "56858", + "basicWithdrawal": "59816", + "batchClaim": "111828", + "batchClaimRegisteredWithDeposit": "111828", + "batchClaimRegisteredWithDepositWithWitness": "112590", + "batchClaimWithWitness": "112584", "batchDepositAndRegisterViaPermit2": "221900", "batchDepositAndRegisterWithWitnessViaPermit2": "221878", - "batchTransfer": "81539", - "batchWithdrawal": "99954", - "claim": "56978", - "claimAndWithdraw": "73269", - "claimWithWitness": "59443", + "batchTransfer": "81536", + "batchWithdrawal": "99951", + "claim": "56975", + "claimAndWithdraw": "73266", + "claimWithWitness": "59440", "depositAndRegisterViaPermit2": "124270", "depositBatchSingleERC20": "67868", "depositBatchSingleNative": "28171", @@ -22,21 +22,21 @@ "depositERC20ViaPermit2AndURI": "98312", "depositETHAndURI": "26777", "depositETHBasic": "28384", - "qualifiedBatchClaim": "113249", - "qualifiedBatchClaimWithWitness": "112713", - "qualifiedClaim": "60237", - "qualifiedClaimWithWitness": "58777", - "qualifiedSplitBatchClaim": "140937", - "qualifiedSplitBatchClaimWithWitness": "140929", - "qualifiedSplitClaim": "86524", - "qualifiedSplitClaimWithWitness": "86890", + "qualifiedBatchClaim": "113246", + "qualifiedBatchClaimWithWitness": "112710", + "qualifiedClaim": "60234", + "qualifiedClaimWithWitness": "58774", + "qualifiedSplitBatchClaim": "140934", + "qualifiedSplitBatchClaimWithWitness": "140926", + "qualifiedSplitClaim": "86521", + "qualifiedSplitClaimWithWitness": "86887", "register": "25379", - "splitBatchClaim": "140399", - "splitBatchClaimWithWitness": "140363", - "splitBatchTransfer": "110614", - "splitBatchWithdrawal": "139822", - "splitClaim": "86447", - "splitClaimWithWitness": "85928", - "splitTransfer": "82751", - "splitWithdrawal": "93705" + "splitBatchClaim": "140396", + "splitBatchClaimWithWitness": "140360", + "splitBatchTransfer": "110611", + "splitBatchWithdrawal": "139819", + "splitClaim": "86444", + "splitClaimWithWitness": "85925", + "splitTransfer": "82748", + "splitWithdrawal": "93702" } \ No newline at end of file diff --git a/src/TheCompact.sol b/src/TheCompact.sol index dcdd2e6..e4ee8c1 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -11,6 +11,7 @@ import { Scope } from "./types/Scope.sol"; import { ResetPeriod } from "./types/ResetPeriod.sol"; import { ForcedWithdrawalStatus } from "./types/ForcedWithdrawalStatus.sol"; +import { AllocatorLogic } from "./lib/AllocatorLogic.sol"; import { ClaimProcessor } from "./lib/ClaimProcessor.sol"; import { Extsload } from "./lib/Extsload.sol"; @@ -25,7 +26,7 @@ import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.so * formation and mediation of reusable "resource locks." * This contract has not yet been properly tested, audited, or reviewed. */ -contract TheCompact is ITheCompact, ClaimProcessor, ERC6909, Extsload { +contract TheCompact is ITheCompact, AllocatorLogic, ClaimProcessor, ERC6909, Extsload { function deposit(address allocator) external payable returns (uint256) { return _performBasicNativeTokenDeposit(allocator); } diff --git a/src/lib/AllocatorLogic.sol b/src/lib/AllocatorLogic.sol new file mode 100644 index 0000000..bb6140d --- /dev/null +++ b/src/lib/AllocatorLogic.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { BatchTransfer, SplitBatchTransfer } from "../types/BatchClaims.sol"; +import { BasicTransfer, SplitTransfer } from "../types/Claims.sol"; +import { CompactCategory } from "../types/CompactCategory.sol"; +import { SplitComponent, TransferComponent, SplitByIdComponent } from "../types/Components.sol"; +import { ForcedWithdrawalStatus } from "../types/ForcedWithdrawalStatus.sol"; +import { Lock } from "../types/Lock.sol"; +import { ResetPeriod } from "../types/ResetPeriod.sol"; +import { Scope } from "../types/Scope.sol"; + +import { ConsumerLib } from "./ConsumerLib.sol"; +import { EfficiencyLib } from "./EfficiencyLib.sol"; +import { FunctionCastLib } from "./FunctionCastLib.sol"; +import { HashLib } from "./HashLib.sol"; +import { IdLib } from "./IdLib.sol"; +import { MetadataRenderer } from "./MetadataRenderer.sol"; +import { ValidityLib } from "./ValidityLib.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { Tstorish } from "tstorish/Tstorish.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; + +contract AllocatorLogic { + using HashLib for address; + using HashLib for bytes32; + using HashLib for uint256; + using HashLib for BasicTransfer; + using HashLib for SplitTransfer; + using HashLib for BatchTransfer; + using HashLib for SplitBatchTransfer; + using IdLib for uint96; + using IdLib for uint256; + using IdLib for address; + using IdLib for Lock; + using IdLib for ResetPeriod; + using SafeTransferLib for address; + using ConsumerLib for uint256; + using EfficiencyLib for bool; + using EfficiencyLib for bytes32; + using EfficiencyLib for uint256; + using ValidityLib for address; + using ValidityLib for uint96; + using ValidityLib for uint256; + using ValidityLib for bytes32; + using FunctionCastLib for function(bytes32, address, BasicTransfer calldata) internal; + + function _consume(uint256[] calldata nonces) internal returns (bool) { + // NOTE: this may not be necessary, consider removing + msg.sender.usingAllocatorId().mustHaveARegisteredAllocator(); + + unchecked { + uint256 i; + + assembly ("memory-safe") { + i := nonces.offset + } + + uint256 end = i + (nonces.length << 5); + uint256 nonce; + for (; i < end; i += 0x20) { + assembly ("memory-safe") { + nonce := calldataload(i) + } + nonce.consumeNonceAsAllocator(msg.sender); + } + } + + return true; + } + + function _registerAllocator(address allocator, bytes calldata proof) internal returns (uint96 allocatorId) { + allocator = uint256(uint160(allocator)).asSanitizedAddress(); + if (!allocator.canBeRegistered(proof)) { + assembly ("memory-safe") { + // revert InvalidRegistrationProof(allocator) + mstore(0, 0x4e7f492b) + mstore(0x20, allocator) + revert(0x1c, 0x24) + } + } + + allocatorId = allocator.register(); + } + + function _hasConsumedAllocatorNonce(uint256 nonce, address allocator) internal view returns (bool) { + return allocator.hasConsumedAllocatorNonce(nonce); + } + + function _getLockDetails(uint256 id) internal view returns (address token, address allocator, ResetPeriod resetPeriod, Scope scope) { + token = id.toToken(); + allocator = id.toAllocatorId().toRegisteredAllocator(); + resetPeriod = id.toResetPeriod(); + scope = id.toScope(); + } +} diff --git a/src/lib/InternalLogic.sol b/src/lib/InternalLogic.sol index 5807136..dbce072 100644 --- a/src/lib/InternalLogic.sol +++ b/src/lib/InternalLogic.sol @@ -84,21 +84,6 @@ contract InternalLogic is Tstorish { _PERMIT2_INITIALLY_DEPLOYED = _checkPermit2Deployment(); } - function _emitClaim(address sponsor, bytes32 messageHash, address allocator) internal { - assembly ("memory-safe") { - mstore(0, messageHash) - log4(0, 0x20, _CLAIM_EVENT_SIGNATURE, shr(0x60, shl(0x60, sponsor)), shr(0x60, shl(0x60, allocator)), caller()) - } - } - - function _notExpiredAndSignedByAllocator(bytes32 messageHash, address allocator, BasicTransfer calldata transferPayload) internal { - transferPayload.expires.later(); - - messageHash.signedBy(allocator, transferPayload.allocatorSignature, _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID)); - - _emitClaim(msg.sender, messageHash, allocator); - } - function _setReentrancyGuard() internal { _setTstorish(_REENTRANCY_GUARD_SLOT, 1); } @@ -107,105 +92,6 @@ contract InternalLogic is Tstorish { _clearTstorish(_REENTRANCY_GUARD_SLOT); } - function _register(address sponsor, bytes32 claimHash, bytes32 typehash, uint256 duration) internal { - assembly ("memory-safe") { - let m := mload(0x40) - mstore(add(m, 0x14), sponsor) - mstore(m, _ACTIVE_REGISTRATIONS_SCOPE) - mstore(add(m, 0x34), claimHash) - mstore(add(m, 0x54), typehash) - let cutoffSlot := keccak256(add(m, 0x1c), 0x58) - - let expires := add(timestamp(), duration) - if or(lt(expires, sload(cutoffSlot)), gt(duration, 0x278d00)) { - // revert InvalidRegistrationDuration(uint256 duration) - mstore(0, 0x1f9a96f4) - mstore(0x20, duration) - revert(0x1c, 0x24) - } - - sstore(cutoffSlot, expires) - mstore(add(m, 0x74), expires) - log2(add(m, 0x34), 0x60, _COMPACT_REGISTERED_SIGNATURE, shr(0x60, shl(0x60, sponsor))) - } - } - - function _registerWithDefaults(bytes32 claimHash, bytes32 typehash) internal { - _register(msg.sender, claimHash, typehash, uint256(0x258).asStubborn()); - } - - function _registerBatch(bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) internal returns (bool) { - unchecked { - uint256 totalClaimHashes = claimHashesAndTypehashes.length; - for (uint256 i = 0; i < totalClaimHashes; ++i) { - bytes32[2] calldata claimHashAndTypehash = claimHashesAndTypehashes[i]; - _register(msg.sender, claimHashAndTypehash[0], claimHashAndTypehash[1], duration); - } - } - - return true; - } - - function _consume(uint256[] calldata nonces) internal returns (bool) { - // NOTE: this may not be necessary, consider removing - msg.sender.usingAllocatorId().mustHaveARegisteredAllocator(); - - unchecked { - uint256 i; - - assembly ("memory-safe") { - i := nonces.offset - } - - uint256 end = i + (nonces.length << 5); - uint256 nonce; - for (; i < end; i += 0x20) { - assembly ("memory-safe") { - nonce := calldataload(i) - } - nonce.consumeNonceAsAllocator(msg.sender); - } - } - - return true; - } - - function _registerAllocator(address allocator, bytes calldata proof) internal returns (uint96 allocatorId) { - allocator = uint256(uint160(allocator)).asSanitizedAddress(); - if (!allocator.canBeRegistered(proof)) { - assembly ("memory-safe") { - // revert InvalidRegistrationProof(allocator) - mstore(0, 0x4e7f492b) - mstore(0x20, allocator) - revert(0x1c, 0x24) - } - } - - allocatorId = allocator.register(); - } - - function _getLockDetails(uint256 id) internal view returns (address token, address allocator, ResetPeriod resetPeriod, Scope scope) { - token = id.toToken(); - allocator = id.toAllocatorId().toRegisteredAllocator(); - resetPeriod = id.toResetPeriod(); - scope = id.toScope(); - } - - function _hasConsumedAllocatorNonce(uint256 nonce, address allocator) internal view returns (bool) { - return allocator.hasConsumedAllocatorNonce(nonce); - } - - function _getRegistrationStatus(address sponsor, bytes32 claimHash, bytes32 typehash) internal view returns (uint256 expires) { - assembly ("memory-safe") { - let m := mload(0x40) - mstore(add(m, 0x14), sponsor) - mstore(m, _ACTIVE_REGISTRATIONS_SCOPE) - mstore(add(m, 0x34), claimHash) - mstore(add(m, 0x54), typehash) - expires := sload(keccak256(add(m, 0x1c), 0x58)) - } - } - function _isPermit2Deployed() internal view returns (bool) { if (_PERMIT2_INITIALLY_DEPLOYED) { return true; diff --git a/src/lib/RegistrationLogic.sol b/src/lib/RegistrationLogic.sol new file mode 100644 index 0000000..dc5b67e --- /dev/null +++ b/src/lib/RegistrationLogic.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { BatchTransfer, SplitBatchTransfer } from "../types/BatchClaims.sol"; +import { BasicTransfer, SplitTransfer } from "../types/Claims.sol"; +import { CompactCategory } from "../types/CompactCategory.sol"; +import { SplitComponent, TransferComponent, SplitByIdComponent } from "../types/Components.sol"; +import { ForcedWithdrawalStatus } from "../types/ForcedWithdrawalStatus.sol"; +import { Lock } from "../types/Lock.sol"; +import { ResetPeriod } from "../types/ResetPeriod.sol"; +import { Scope } from "../types/Scope.sol"; + +import { ConsumerLib } from "./ConsumerLib.sol"; +import { EfficiencyLib } from "./EfficiencyLib.sol"; +import { FunctionCastLib } from "./FunctionCastLib.sol"; +import { HashLib } from "./HashLib.sol"; +import { IdLib } from "./IdLib.sol"; +import { MetadataRenderer } from "./MetadataRenderer.sol"; +import { ValidityLib } from "./ValidityLib.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { Tstorish } from "tstorish/Tstorish.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; + +import { InternalLogic } from "./InternalLogic.sol"; + +contract RegistrationLogic is InternalLogic { + using HashLib for address; + using HashLib for bytes32; + using HashLib for uint256; + using HashLib for BasicTransfer; + using HashLib for SplitTransfer; + using HashLib for BatchTransfer; + using HashLib for SplitBatchTransfer; + using IdLib for uint96; + using IdLib for uint256; + using IdLib for address; + using IdLib for Lock; + using IdLib for ResetPeriod; + using SafeTransferLib for address; + using ConsumerLib for uint256; + using EfficiencyLib for bool; + using EfficiencyLib for bytes32; + using EfficiencyLib for uint256; + using ValidityLib for address; + using ValidityLib for uint96; + using ValidityLib for uint256; + using ValidityLib for bytes32; + using FunctionCastLib for function(bytes32, address, BasicTransfer calldata) internal; + + address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + + uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; + + uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; + + /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; + + /// @dev `keccak256(bytes("Claim(address,address,address,bytes32)"))`. + uint256 private constant _CLAIM_EVENT_SIGNATURE = 0x770c32a2314b700d6239ee35ba23a9690f2fceb93a55d8c753e953059b3b18d4; + + /// @dev `keccak256(bytes("CompactRegistered(address,bytes32,bytes32,uint256)"))`. + uint256 private constant _COMPACT_REGISTERED_SIGNATURE = 0xf78a2f33ff80ef4391f7449c748dc2d577a62cd645108f4f4069f4a7e0635b6a; + + /// @dev `keccak256(bytes("ForcedWithdrawalStatusUpdated(address,uint256,bool,uint256)"))`. + uint256 private constant _FORCED_WITHDRAWAL_STATUS_UPDATED_SIGNATURE = 0xe27f5e0382cf5347965fc81d5c81cd141897fe9ce402d22c496b7c2ddc84e5fd; + + uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; + + // slot: keccak256(_FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE ++ account ++ id) => activates + uint256 private constant _FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE = 0x41d0e04b; + + // slot: keccak256(_ACTIVE_REGISTRATIONS_SCOPE ++ sponsor ++ claimHash ++ typehash) => expires + uint256 private constant _ACTIVE_REGISTRATIONS_SCOPE = 0x68a30dd0; + + function _register(address sponsor, bytes32 claimHash, bytes32 typehash, uint256 duration) internal { + assembly ("memory-safe") { + let m := mload(0x40) + mstore(add(m, 0x14), sponsor) + mstore(m, _ACTIVE_REGISTRATIONS_SCOPE) + mstore(add(m, 0x34), claimHash) + mstore(add(m, 0x54), typehash) + let cutoffSlot := keccak256(add(m, 0x1c), 0x58) + + let expires := add(timestamp(), duration) + if or(lt(expires, sload(cutoffSlot)), gt(duration, 0x278d00)) { + // revert InvalidRegistrationDuration(uint256 duration) + mstore(0, 0x1f9a96f4) + mstore(0x20, duration) + revert(0x1c, 0x24) + } + + sstore(cutoffSlot, expires) + mstore(add(m, 0x74), expires) + log2(add(m, 0x34), 0x60, _COMPACT_REGISTERED_SIGNATURE, shr(0x60, shl(0x60, sponsor))) + } + } + + function _registerWithDefaults(bytes32 claimHash, bytes32 typehash) internal { + _register(msg.sender, claimHash, typehash, uint256(0x258).asStubborn()); + } + + function _registerBatch(bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) internal returns (bool) { + unchecked { + uint256 totalClaimHashes = claimHashesAndTypehashes.length; + for (uint256 i = 0; i < totalClaimHashes; ++i) { + bytes32[2] calldata claimHashAndTypehash = claimHashesAndTypehashes[i]; + _register(msg.sender, claimHashAndTypehash[0], claimHashAndTypehash[1], duration); + } + } + + return true; + } + + function _getRegistrationStatus(address sponsor, bytes32 claimHash, bytes32 typehash) internal view returns (uint256 expires) { + assembly ("memory-safe") { + let m := mload(0x40) + mstore(add(m, 0x14), sponsor) + mstore(m, _ACTIVE_REGISTRATIONS_SCOPE) + mstore(add(m, 0x34), claimHash) + mstore(add(m, 0x54), typehash) + expires := sload(keccak256(add(m, 0x1c), 0x58)) + } + } +} diff --git a/src/lib/TransferLogic.sol b/src/lib/TransferLogic.sol index 135876f..6c08565 100644 --- a/src/lib/TransferLogic.sol +++ b/src/lib/TransferLogic.sol @@ -22,9 +22,9 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { Tstorish } from "tstorish/Tstorish.sol"; import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; -import { InternalLogic } from "./InternalLogic.sol"; +import { RegistrationLogic } from "./RegistrationLogic.sol"; -contract TransferLogic is InternalLogic { +contract TransferLogic is RegistrationLogic { using HashLib for address; using HashLib for bytes32; using HashLib for uint256; @@ -53,8 +53,26 @@ contract TransferLogic is InternalLogic { /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; + /// @dev `keccak256(bytes("Claim(address,address,address,bytes32)"))`. + uint256 private constant _CLAIM_EVENT_SIGNATURE = 0x770c32a2314b700d6239ee35ba23a9690f2fceb93a55d8c753e953059b3b18d4; + uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; + function _emitClaim(address sponsor, bytes32 messageHash, address allocator) internal { + assembly ("memory-safe") { + mstore(0, messageHash) + log4(0, 0x20, _CLAIM_EVENT_SIGNATURE, shr(0x60, shl(0x60, sponsor)), shr(0x60, shl(0x60, allocator)), caller()) + } + } + + function _notExpiredAndSignedByAllocator(bytes32 messageHash, address allocator, BasicTransfer calldata transferPayload) internal { + transferPayload.expires.later(); + + messageHash.signedBy(allocator, transferPayload.allocatorSignature, _domainSeparator()); + + _emitClaim(msg.sender, messageHash, allocator); + } + function _processBasicTransfer(BasicTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { _notExpiredAndSignedByAllocator(transfer.toMessageHash(), transfer.id.toRegisteredAllocatorWithConsumed(transfer.nonce), transfer);