diff --git a/snapshots/TheCompactTest.json b/snapshots/TheCompactTest.json index c95050d..fcd9c89 100644 --- a/snapshots/TheCompactTest.json +++ b/snapshots/TheCompactTest.json @@ -1,17 +1,17 @@ { - "basicTransfer": "56850", - "basicWithdrawal": "59816", - "batchClaim": "111810", - "batchClaimRegisteredWithDeposit": "111810", - "batchClaimRegisteredWithDepositWithWitness": "112675", - "batchClaimWithWitness": "112669", + "basicTransfer": "56867", + "basicWithdrawal": "59810", + "batchClaim": "112477", + "batchClaimRegisteredWithDeposit": "112477", + "batchClaimRegisteredWithDepositWithWitness": "113236", + "batchClaimWithWitness": "113230", "batchDepositAndRegisterViaPermit2": "221877", "batchDepositAndRegisterWithWitnessViaPermit2": "221855", - "batchTransfer": "81520", - "batchWithdrawal": "99951", - "claim": "56817", - "claimAndWithdraw": "73116", - "claimWithWitness": "59462", + "batchTransfer": "81344", + "batchWithdrawal": "99729", + "claim": "56982", + "claimAndWithdraw": "73258", + "claimWithWitness": "59479", "depositAndRegisterViaPermit2": "124247", "depositBatchSingleERC20": "67868", "depositBatchSingleNative": "28171", @@ -22,21 +22,21 @@ "depositERC20ViaPermit2AndURI": "98289", "depositETHAndURI": "26754", "depositETHBasic": "28391", - "qualifiedBatchClaim": "113348", - "qualifiedBatchClaimWithWitness": "112885", - "qualifiedClaim": "60238", - "qualifiedClaimWithWitness": "58927", - "qualifiedSplitBatchClaim": "140922", - "qualifiedSplitBatchClaimWithWitness": "140987", - "qualifiedSplitClaim": "86554", - "qualifiedSplitClaimWithWitness": "87031", + "qualifiedBatchClaim": "114099", + "qualifiedBatchClaimWithWitness": "113637", + "qualifiedClaim": "60414", + "qualifiedClaimWithWitness": "59106", + "qualifiedSplitBatchClaim": "141456", + "qualifiedSplitBatchClaimWithWitness": "141522", + "qualifiedSplitClaim": "86977", + "qualifiedSplitClaimWithWitness": "87452", "register": "25379", - "splitBatchClaim": "140264", - "splitBatchClaimWithWitness": "140331", - "splitBatchTransfer": "110587", - "splitBatchWithdrawal": "139819", - "splitClaim": "86275", - "splitClaimWithWitness": "85936", - "splitTransfer": "82732", - "splitWithdrawal": "93702" + "splitBatchClaim": "140758", + "splitBatchClaimWithWitness": "140719", + "splitBatchTransfer": "110582", + "splitBatchWithdrawal": "139745", + "splitClaim": "86751", + "splitClaimWithWitness": "86232", + "splitTransfer": "82438", + "splitWithdrawal": "93362" } \ No newline at end of file diff --git a/src/lib/ClaimHashLib.sol b/src/lib/ClaimHashLib.sol index 6131247..27af860 100644 --- a/src/lib/ClaimHashLib.sol +++ b/src/lib/ClaimHashLib.sol @@ -65,7 +65,7 @@ import { ExogenousQualifiedSplitBatchMultichainClaimWithWitness } from "../types/BatchMultichainClaims.sol"; -import { TransferComponent, SplitComponent, SplitByIdComponent, BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol"; +import { BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol"; import { ResetPeriod } from "../types/ResetPeriod.sol"; import { Scope } from "../types/Scope.sol"; diff --git a/src/lib/ClaimProcessorLib.sol b/src/lib/ClaimProcessorLib.sol index e891ffc..fa36490 100644 --- a/src/lib/ClaimProcessorLib.sol +++ b/src/lib/ClaimProcessorLib.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; -import { COMPACT_TYPEHASH, BATCH_COMPACT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH } from "../types/EIP712Types.sol"; -import { SplitComponent, BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol"; - +import { ComponentLib } from "./ComponentLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { EventLib } from "./EventLib.sol"; +import { HashLib } from "./HashLib.sol"; import { IdLib } from "./IdLib.sol"; import { RegistrationLib } from "./RegistrationLib.sol"; import { ValidityLib } from "./ValidityLib.sol"; @@ -20,13 +19,14 @@ import { ValidityLib } from "./ValidityLib.sol"; * any changes. */ library ClaimProcessorLib { + using ComponentLib for bytes32; using ClaimProcessorLib for uint256; using ClaimProcessorLib for bytes32; - using ClaimProcessorLib for SplitComponent[]; using EfficiencyLib for bool; using EfficiencyLib for uint256; using EfficiencyLib for bytes32; using EventLib for address; + using HashLib for uint256; using IdLib for uint256; using ValidityLib for uint256; using ValidityLib for uint96; @@ -177,33 +177,7 @@ library ClaimProcessorLib { bytes32 domainSeparator, function(address, address, uint256, uint256) internal returns (bool) operation ) internal returns (bool) { - // Declare variables for parameters that will be extracted from calldata. - uint256 id; - uint256 allocatedAmount; - SplitComponent[] calldata components; - - assembly ("memory-safe") { - // Calculate pointer to claim parameters using provided offset. - let calldataPointerWithOffset := add(calldataPointer, offsetToId) - - // Extract resource lock id and allocated amount. - id := calldataload(calldataPointerWithOffset) - allocatedAmount := calldataload(add(calldataPointerWithOffset, 0x20)) - - // Extract array of split components containing claimant addresses and amounts. - let componentsPtr := add(calldataPointer, calldataload(add(calldataPointerWithOffset, 0x40))) - components.offset := add(0x20, componentsPtr) - components.length := calldataload(componentsPtr) - } - - // Validate the claim and extract the sponsor address. - address sponsor = messageHash.validate(id.toAllocatorId(), qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); - - // Verify the resource lock scope is compatible with the provided domain separator. - sponsorDomainSeparator.ensureValidScope(id); - - // Process each split component, verifying total amount and executing operations. - return components.verifyAndProcessSplitComponents(sponsor, id, allocatedAmount, operation); + return messageHash.processClaimWithSplitComponents(qualificationMessageHash, calldataPointer, offsetToId, sponsorDomainSeparator, typehash, domainSeparator, operation, validate); } /** @@ -232,76 +206,7 @@ library ClaimProcessorLib { bytes32 domainSeparator, function(address, address, uint256, uint256) internal returns (bool) operation ) internal returns (bool) { - // Declare variables for parameters that will be extracted from calldata. - BatchClaimComponent[] calldata claims; - address claimant; - - assembly ("memory-safe") { - // Calculate pointer to claim parameters using provided offset. - let calldataPointerWithOffset := add(calldataPointer, offsetToId) - - // Extract array of batch claim components and claimant address. - let claimsPtr := add(calldataPointer, calldataload(calldataPointerWithOffset)) - claims.offset := add(0x20, claimsPtr) - claims.length := calldataload(claimsPtr) - claimant := calldataload(add(calldataPointerWithOffset, 0x20)) - } - - // Extract allocator id from first claim for validation. - uint96 firstAllocatorId = claims[0].id.toAllocatorId(); - - // Validate the claim and extract the sponsor address. - address sponsor = messageHash.validate(firstAllocatorId, qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); - - // Revert if the batch is empty. - uint256 totalClaims = claims.length; - assembly ("memory-safe") { - if iszero(totalClaims) { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } - - // Process first claim and initialize error tracking. - // NOTE: many of the bounds checks on these array accesses can be skipped as an optimization - BatchClaimComponent calldata component = claims[0]; - uint256 id = component.id; - uint256 amount = component.amount; - uint256 errorBuffer = component.allocatedAmount.allocationExceededOrScopeNotMultichain(amount, id, sponsorDomainSeparator).asUint256(); - - // Execute transfer or withdrawal for first claim. - operation(sponsor, claimant, id, amount); - - unchecked { - // Process remaining claims while accumulating potential errors. - for (uint256 i = 1; i < totalClaims; ++i) { - component = claims[i]; - id = component.id; - amount = component.amount; - errorBuffer |= (id.toAllocatorId() != firstAllocatorId).or(component.allocatedAmount.allocationExceededOrScopeNotMultichain(amount, id, sponsorDomainSeparator)).asUint256(); - - operation(sponsor, claimant, id, amount); - } - - // If any errors occurred, identify specific failures and revert. - if (errorBuffer.asBool()) { - for (uint256 i = 0; i < totalClaims; ++i) { - component = claims[i]; - component.amount.withinAllocated(component.allocatedAmount); - id = component.id; - sponsorDomainSeparator.ensureValidScope(component.id); - } - - assembly ("memory-safe") { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } - } - - return true; + return messageHash.processClaimWithBatchComponents(qualificationMessageHash, calldataPointer, offsetToId, sponsorDomainSeparator, typehash, domainSeparator, operation, validate); } /** @@ -330,108 +235,7 @@ library ClaimProcessorLib { bytes32 domainSeparator, function(address, address, uint256, uint256) internal returns (bool) operation ) internal returns (bool) { - // Declare variable for SplitBatchClaimComponent array that will be extracted from calldata. - SplitBatchClaimComponent[] calldata claims; - - assembly ("memory-safe") { - // Extract array of split batch claim components. - let claimsPtr := add(calldataPointer, calldataload(add(calldataPointer, offsetToId))) - claims.offset := add(0x20, claimsPtr) - claims.length := calldataload(claimsPtr) - } - - // Extract allocator id from first claim for validation. - uint96 firstAllocatorId = claims[0].id.toAllocatorId(); - - // Validate the claim and extract the sponsor address. - address sponsor = messageHash.validate(firstAllocatorId, qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); - - // Initialize tracking variables. - uint256 totalClaims = claims.length; - uint256 errorBuffer = (totalClaims == 0).asUint256(); - uint256 id; - - unchecked { - // Process each claim component while accumulating potential errors. - for (uint256 i = 0; i < totalClaims; ++i) { - SplitBatchClaimComponent calldata claimComponent = claims[i]; - id = claimComponent.id; - errorBuffer |= (id.toAllocatorId() != firstAllocatorId).or(id.scopeNotMultichain(sponsorDomainSeparator)).asUint256(); - - // Process each split component, verifying total amount and executing operations. - claimComponent.portions.verifyAndProcessSplitComponents(sponsor, id, claimComponent.allocatedAmount, operation); - } - - // If any errors occurred, identify specific scope failures and revert. - if (errorBuffer.asBool()) { - for (uint256 i = 0; i < totalClaims; ++i) { - sponsorDomainSeparator.ensureValidScope(claims[i].id); - } - - assembly ("memory-safe") { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } - } - - return true; - } - - /** - * @notice Internal function for verifying and processing split components. Ensures that the - * sum of split amounts doesn't exceed the allocated amount, checks for arithmetic overflow, - * and executes the specified operation for each split recipient. Reverts if the total - * claimed amount exceeds the allocation or if arithmetic overflow occurs during summation. - * @param claimants Array of split components specifying recipients and their amounts. - * @param sponsor The address of the claim sponsor. - * @param id The ERC6909 token identifier of the resource lock. - * @param allocatedAmount The total amount allocated for this claim. - * @param operation Function pointer to either _release or _withdraw for executing the claim. - * @return Whether all split components were successfully processed. - */ - function verifyAndProcessSplitComponents( - SplitComponent[] calldata claimants, - address sponsor, - uint256 id, - uint256 allocatedAmount, - function(address, address, uint256, uint256) internal returns (bool) operation - ) internal returns (bool) { - // Initialize tracking variables. - uint256 totalClaims = claimants.length; - uint256 spentAmount = 0; - uint256 errorBuffer = (totalClaims == 0).asUint256(); - - unchecked { - // Process each split component while tracking total amount and checking for overflow. - for (uint256 i = 0; i < totalClaims; ++i) { - SplitComponent calldata component = claimants[i]; - uint256 amount = component.amount; - - // Track total amount claimed, checking for overflow. - uint256 updatedSpentAmount = amount + spentAmount; - errorBuffer |= (updatedSpentAmount < spentAmount).asUint256(); - spentAmount = updatedSpentAmount; - - // Execute transfer or withdrawal for the split component. - operation(sponsor, component.claimant, id, amount); - } - } - - // Revert if an overflow occurred or if total claimed amount exceeds allocation. - errorBuffer |= (allocatedAmount < spentAmount).asUint256(); - assembly ("memory-safe") { - if errorBuffer { - // revert AllocatedAmountExceeded(allocatedAmount, amount); - mstore(0, 0x3078b2f6) - mstore(0x20, allocatedAmount) - mstore(0x40, spentAmount) - revert(0x1c, 0x44) - } - } - - return true; + return messageHash.processClaimWithSplitBatchComponents(qualificationMessageHash, calldataPointer, offsetToId, sponsorDomainSeparator, typehash, domainSeparator, operation, validate); } /** @@ -739,25 +543,4 @@ library ClaimProcessorLib { ) internal returns (bool) { return messageHash.processSplitBatchClaimWithQualificationAndSponsorDomain(messageHash, calldataPointer, offsetToId, sponsorDomain, typehash, domainSeparator, operation); } - - /** - * @notice Internal pure function for retrieving EIP-712 typehashes where no witness data is - * provided, returning the corresponding typehash based on the index provided. The available - * typehashes are: - * - 0: COMPACT_TYPEHASH - * - 1: BATCH_COMPACT_TYPEHASH - * - 2: MULTICHAIN_COMPACT_TYPEHASH - * @param i The index of the EIP-712 typehash to retrieve. - * @return typehash The corresponding EIP-712 typehash. - */ - function typehashes(uint256 i) internal pure returns (bytes32 typehash) { - assembly ("memory-safe") { - 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/lib/ClaimProcessorLogic.sol b/src/lib/ClaimProcessorLogic.sol index 6dc41b5..76a2c07 100644 --- a/src/lib/ClaimProcessorLogic.sol +++ b/src/lib/ClaimProcessorLogic.sol @@ -51,9 +51,10 @@ import { import { ClaimHashLib } from "./ClaimHashLib.sol"; import { ClaimProcessorLib } from "./ClaimProcessorLib.sol"; +import { DomainLib } from "./DomainLib.sol"; +import { HashLib } from "./HashLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { FunctionCastLib } from "./FunctionCastLib.sol"; -import { HashLib } from "./HashLib.sol"; import { SharedLogic } from "./SharedLogic.sol"; import { ValidityLib } from "./ValidityLib.sol"; @@ -117,12 +118,13 @@ contract ClaimProcessorLogic is SharedLogic { using ClaimHashLib for ExogenousQualifiedSplitBatchMultichainClaim; using ClaimHashLib for ExogenousQualifiedSplitBatchMultichainClaimWithWitness; using ClaimProcessorLib for uint256; + using DomainLib for uint256; + using HashLib for uint256; using EfficiencyLib for uint256; using FunctionCastLib for function(bytes32, uint256, uint256, bytes32, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); using FunctionCastLib for function(bytes32, uint256, uint256, bytes32, bytes32, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); using FunctionCastLib for function(bytes32, bytes32, uint256, uint256, bytes32, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); using FunctionCastLib for function(bytes32, bytes32, uint256, uint256, bytes32, bytes32, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); - using HashLib for uint256; using ValidityLib for uint96; using ValidityLib for uint256; using ValidityLib for bytes32; diff --git a/src/lib/ComponentLib.sol b/src/lib/ComponentLib.sol new file mode 100644 index 0000000..d31f842 --- /dev/null +++ b/src/lib/ComponentLib.sol @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { SplitTransfer } from "../types/Claims.sol"; +import { BatchTransfer, SplitBatchTransfer } from "../types/BatchClaims.sol"; + +import { TransferComponent, SplitComponent, SplitByIdComponent, BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol"; + +import { EfficiencyLib } from "./EfficiencyLib.sol"; +import { EventLib } from "./EventLib.sol"; +import { HashLib } from "./HashLib.sol"; +import { IdLib } from "./IdLib.sol"; +import { RegistrationLib } from "./RegistrationLib.sol"; +import { ValidityLib } from "./ValidityLib.sol"; + +/** + * @title ComponentLib + * @notice Library contract implementing internal functions with helper logic for + * processing claims that incorporate split and/or batch components. + * @dev IMPORTANT NOTE: logic for processing claims assumes that the utilized structs are + * formatted in a very specific manner — if parameters are rearranged or new parameters + * are inserted, much of this functionality will break. Proceed with caution when making + * any changes. + */ +library ComponentLib { + using ComponentLib for SplitComponent[]; + using EfficiencyLib for bool; + using EfficiencyLib for uint256; + using EfficiencyLib for bytes32; + using EventLib for address; + using HashLib for uint256; + using IdLib for uint256; + using ValidityLib for uint256; + using ValidityLib for uint96; + using ValidityLib for bytes32; + using RegistrationLib for address; + + /** + * @notice Internal function for performing a set of split transfers or withdrawals. + * Executes the transfer or withdrawal operation targeting multiple recipients from + * a single resource lock. + * @param transfer A SplitTransfer struct containing split transfer details. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @return Whether the transfer was successfully processed. + */ + function processSplitTransfer(SplitTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { + // Navigate to the split components in calldata. + SplitComponent[] calldata recipients = transfer.recipients; + + // Retrieve the resource lock ID and the total number of components. + uint256 id = transfer.id; + uint256 totalSplits = recipients.length; + + unchecked { + // Iterate over each additional component in calldata. + for (uint256 i = 0; i < totalSplits; ++i) { + // Navigate to location of the component in calldata. + SplitComponent calldata component = recipients[i]; + + // Perform the transfer or withdrawal for the portion. + operation(msg.sender, component.claimant, id, component.amount); + } + } + + return true; + } + + /** + * @notice Internal function for performing a set of batch transfer or withdrawal operations. + * Executes the transfer or withdrawal operation for a single recipient from multiple + * resource locks. + * @param transfer A BatchTransfer struct containing batch transfer details. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @return Whether the transfer was successfully processed. + */ + function performBatchTransfer(BatchTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { + // Navigate to the transfer components in calldata. + TransferComponent[] calldata transfers = transfer.transfers; + + // Retrieve the recipient and the total number of components. + address recipient = transfer.recipient; + uint256 totalTransfers = transfers.length; + + unchecked { + // Iterate over each component in calldata. + for (uint256 i = 0; i < totalTransfers; ++i) { + // Navigate to location of the component in calldata. + TransferComponent calldata component = transfers[i]; + + // Perform the transfer or withdrawal for the component. + operation(msg.sender, recipient, component.id, component.amount); + } + } + + return true; + } + + /** + * @notice Internal function for performing a set of split batch transfers or withdrawals. + * Executes the transfer or withdrawal operation for multiple recipients from multiple + * resource locks. + * @param transfer A SplitBatchTransfer struct containing split batch transfer details. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @return Whether the transfer was successfully processed. + */ + function performSplitBatchTransfer(SplitBatchTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { + // Declare a variable for tracking the id of each component. + uint256 id; + + // Navigate to the split batch components array in calldata. + SplitByIdComponent[] calldata transfers = transfer.transfers; + + // Retrieve the total number of components. + uint256 totalIds = transfers.length; + + unchecked { + // Iterate over each component in calldata. + for (uint256 i = 0; i < totalIds; ++i) { + // Navigate to location of the component in calldata. + SplitByIdComponent calldata component = transfers[i]; + + // Retrieve the id for the component. + id = component.id; + + // Navigate to location of component portions in calldata. + SplitComponent[] calldata portions = component.portions; + + // Retrieve the total number of portions on the component. + uint256 totalPortions = portions.length; + + // Iterate over each portion in calldata. + for (uint256 j = 0; j < totalPortions; ++j) { + // Navigate to the location of the portion in calldata. + SplitComponent calldata portion = portions[j]; + + // Perform the transfer or withdrawal for the portion. + operation(msg.sender, portion.claimant, id, portion.amount); + } + } + } + + return true; + } + + /** + * @notice Internal function for processing qualified split claims with potentially exogenous + * sponsor signatures. Extracts claim parameters from calldata, validates the claim, + * validates the scope, and executes either releases of ERC6909 tokens or withdrawals of + * underlying tokens to multiple recipients. + * @param messageHash The EIP-712 hash of the claim message. + * @param qualificationMessageHash The EIP-712 hash of the allocator's qualification message. + * @param calldataPointer Pointer to the location of the associated struct in calldata. + * @param offsetToId Offset to segment of calldata where relevant claim parameters begin. + * @param sponsorDomainSeparator The domain separator for the sponsor's signature, or zero for non-exogenous claims. + * @param typehash The EIP-712 typehash used for the claim message. + * @param domainSeparator The local domain separator. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @param validation Function pointer to the _validate function. + * @return Whether the split claim was successfully processed. + */ + function processClaimWithSplitComponents( + bytes32 messageHash, + bytes32 qualificationMessageHash, + uint256 calldataPointer, + uint256 offsetToId, + bytes32 sponsorDomainSeparator, + bytes32 typehash, + bytes32 domainSeparator, + function(address, address, uint256, uint256) internal returns (bool) operation, + function(bytes32, uint96, bytes32, uint256, bytes32, bytes32, bytes32) internal returns (address) validation + ) internal returns (bool) { + // Declare variables for parameters that will be extracted from calldata. + uint256 id; + uint256 allocatedAmount; + SplitComponent[] calldata components; + + assembly ("memory-safe") { + // Calculate pointer to claim parameters using provided offset. + let calldataPointerWithOffset := add(calldataPointer, offsetToId) + + // Extract resource lock id and allocated amount. + id := calldataload(calldataPointerWithOffset) + allocatedAmount := calldataload(add(calldataPointerWithOffset, 0x20)) + + // Extract array of split components containing claimant addresses and amounts. + let componentsPtr := add(calldataPointer, calldataload(add(calldataPointerWithOffset, 0x40))) + components.offset := add(0x20, componentsPtr) + components.length := calldataload(componentsPtr) + } + + // Validate the claim and extract the sponsor address. + address sponsor = validation(messageHash, id.toAllocatorId(), qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); + + // Verify the resource lock scope is compatible with the provided domain separator. + sponsorDomainSeparator.ensureValidScope(id); + + // Process each split component, verifying total amount and executing operations. + return components.verifyAndProcessSplitComponents(sponsor, id, allocatedAmount, operation); + } + + /** + * @notice Internal function for processing qualified batch claims with potentially exogenous + * sponsor signatures. Extracts batch claim parameters from calldata, validates the claim, + * executes operations, and performs optimized validation of allocator consistency, amounts, + * and scopes. If any validation fails, all operations are reverted after explicitly + * identifying the specific validation failures. + * @param messageHash The EIP-712 hash of the claim message. + * @param qualificationMessageHash The EIP-712 hash of the allocator's qualification message. + * @param calldataPointer Pointer to the location of the associated struct in calldata. + * @param offsetToId Offset to segment of calldata where relevant claim parameters begin. + * @param sponsorDomainSeparator The domain separator for the sponsor's signature, or zero for non-exogenous claims. + * @param typehash The EIP-712 typehash used for the claim message. + * @param domainSeparator The local domain separator. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @param validation Function pointer to the _validate function. + * @return Whether the batch claim was successfully processed. + */ + function processClaimWithBatchComponents( + bytes32 messageHash, + bytes32 qualificationMessageHash, + uint256 calldataPointer, + uint256 offsetToId, + bytes32 sponsorDomainSeparator, + bytes32 typehash, + bytes32 domainSeparator, + function(address, address, uint256, uint256) internal returns (bool) operation, + function(bytes32, uint96, bytes32, uint256, bytes32, bytes32, bytes32) internal returns (address) validation + ) internal returns (bool) { + // Declare variables for parameters that will be extracted from calldata. + BatchClaimComponent[] calldata claims; + address claimant; + + assembly ("memory-safe") { + // Calculate pointer to claim parameters using provided offset. + let calldataPointerWithOffset := add(calldataPointer, offsetToId) + + // Extract array of batch claim components and claimant address. + let claimsPtr := add(calldataPointer, calldataload(calldataPointerWithOffset)) + claims.offset := add(0x20, claimsPtr) + claims.length := calldataload(claimsPtr) + claimant := calldataload(add(calldataPointerWithOffset, 0x20)) + } + + // Extract allocator id from first claim for validation. + uint96 firstAllocatorId = claims[0].id.toAllocatorId(); + + // Validate the claim and extract the sponsor address. + address sponsor = validation(messageHash, firstAllocatorId, qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); + + // Revert if the batch is empty. + uint256 totalClaims = claims.length; + assembly ("memory-safe") { + if iszero(totalClaims) { + // revert InvalidBatchAllocation() + mstore(0, 0x3a03d3bb) + revert(0x1c, 0x04) + } + } + + // Process first claim and initialize error tracking. + // NOTE: many of the bounds checks on these array accesses can be skipped as an optimization + BatchClaimComponent calldata component = claims[0]; + uint256 id = component.id; + uint256 amount = component.amount; + uint256 errorBuffer = component.allocatedAmount.allocationExceededOrScopeNotMultichain(amount, id, sponsorDomainSeparator).asUint256(); + + // Execute transfer or withdrawal for first claim. + operation(sponsor, claimant, id, amount); + + unchecked { + // Process remaining claims while accumulating potential errors. + for (uint256 i = 1; i < totalClaims; ++i) { + component = claims[i]; + id = component.id; + amount = component.amount; + errorBuffer |= (id.toAllocatorId() != firstAllocatorId).or(component.allocatedAmount.allocationExceededOrScopeNotMultichain(amount, id, sponsorDomainSeparator)).asUint256(); + + operation(sponsor, claimant, id, amount); + } + + // If any errors occurred, identify specific failures and revert. + if (errorBuffer.asBool()) { + for (uint256 i = 0; i < totalClaims; ++i) { + component = claims[i]; + component.amount.withinAllocated(component.allocatedAmount); + id = component.id; + sponsorDomainSeparator.ensureValidScope(component.id); + } + + assembly ("memory-safe") { + // revert InvalidBatchAllocation() + mstore(0, 0x3a03d3bb) + revert(0x1c, 0x04) + } + } + } + + return true; + } + + /** + * @notice Internal function for processing qualified split batch claims with potentially + * exogenous sponsor signatures. Extracts split batch claim parameters from calldata, + * validates the claim, and executes split operations for each resource lock. Uses optimized + * validation of allocator consistency and scopes, with explicit validation on failure to + * identify specific issues. Each resource lock can be split among multiple recipients. + * @param messageHash The EIP-712 hash of the claim message. + * @param qualificationMessageHash The EIP-712 hash of the allocator's qualification message. + * @param calldataPointer Pointer to the location of the associated struct in calldata. + * @param offsetToId Offset to segment of calldata where relevant claim parameters begin. + * @param sponsorDomainSeparator The domain separator for the sponsor's signature, or zero for non-exogenous claims. + * @param typehash The EIP-712 typehash used for the claim message. + * @param domainSeparator The local domain separator. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @param validation Function pointer to the _validate function. + * @return Whether the split batch claim was successfully processed. + */ + function processClaimWithSplitBatchComponents( + bytes32 messageHash, + bytes32 qualificationMessageHash, + uint256 calldataPointer, + uint256 offsetToId, + bytes32 sponsorDomainSeparator, + bytes32 typehash, + bytes32 domainSeparator, + function(address, address, uint256, uint256) internal returns (bool) operation, + function(bytes32, uint96, bytes32, uint256, bytes32, bytes32, bytes32) internal returns (address) validation + ) internal returns (bool) { + // Declare variable for SplitBatchClaimComponent array that will be extracted from calldata. + SplitBatchClaimComponent[] calldata claims; + + assembly ("memory-safe") { + // Extract array of split batch claim components. + let claimsPtr := add(calldataPointer, calldataload(add(calldataPointer, offsetToId))) + claims.offset := add(0x20, claimsPtr) + claims.length := calldataload(claimsPtr) + } + + // Extract allocator id from first claim for validation. + uint96 firstAllocatorId = claims[0].id.toAllocatorId(); + + // Validate the claim and extract the sponsor address. + address sponsor = validation(messageHash, firstAllocatorId, qualificationMessageHash, calldataPointer, domainSeparator, sponsorDomainSeparator, typehash); + + // Initialize tracking variables. + uint256 totalClaims = claims.length; + uint256 errorBuffer = (totalClaims == 0).asUint256(); + uint256 id; + + unchecked { + // Process each claim component while accumulating potential errors. + for (uint256 i = 0; i < totalClaims; ++i) { + SplitBatchClaimComponent calldata claimComponent = claims[i]; + id = claimComponent.id; + errorBuffer |= (id.toAllocatorId() != firstAllocatorId).or(id.scopeNotMultichain(sponsorDomainSeparator)).asUint256(); + + // Process each split component, verifying total amount and executing operations. + claimComponent.portions.verifyAndProcessSplitComponents(sponsor, id, claimComponent.allocatedAmount, operation); + } + + // If any errors occurred, identify specific scope failures and revert. + if (errorBuffer.asBool()) { + for (uint256 i = 0; i < totalClaims; ++i) { + sponsorDomainSeparator.ensureValidScope(claims[i].id); + } + + assembly ("memory-safe") { + // revert InvalidBatchAllocation() + mstore(0, 0x3a03d3bb) + revert(0x1c, 0x04) + } + } + } + + return true; + } + + /** + * @notice Internal function for verifying and processing split components. Ensures that the + * sum of split amounts doesn't exceed the allocated amount, checks for arithmetic overflow, + * and executes the specified operation for each split recipient. Reverts if the total + * claimed amount exceeds the allocation or if arithmetic overflow occurs during summation. + * @param claimants Array of split components specifying recipients and their amounts. + * @param sponsor The address of the claim sponsor. + * @param id The ERC6909 token identifier of the resource lock. + * @param allocatedAmount The total amount allocated for this claim. + * @param operation Function pointer to either _release or _withdraw for executing the claim. + * @return Whether all split components were successfully processed. + */ + function verifyAndProcessSplitComponents( + SplitComponent[] calldata claimants, + address sponsor, + uint256 id, + uint256 allocatedAmount, + function(address, address, uint256, uint256) internal returns (bool) operation + ) internal returns (bool) { + // Initialize tracking variables. + uint256 totalClaims = claimants.length; + uint256 spentAmount = 0; + uint256 errorBuffer = (totalClaims == 0).asUint256(); + + unchecked { + // Process each split component while tracking total amount and checking for overflow. + for (uint256 i = 0; i < totalClaims; ++i) { + SplitComponent calldata component = claimants[i]; + uint256 amount = component.amount; + + // Track total amount claimed, checking for overflow. + uint256 updatedSpentAmount = amount + spentAmount; + errorBuffer |= (updatedSpentAmount < spentAmount).asUint256(); + spentAmount = updatedSpentAmount; + + // Execute transfer or withdrawal for the split component. + operation(sponsor, component.claimant, id, amount); + } + } + + // Revert if an overflow occurred or if total claimed amount exceeds allocation. + errorBuffer |= (allocatedAmount < spentAmount).asUint256(); + assembly ("memory-safe") { + if errorBuffer { + // revert AllocatedAmountExceeded(allocatedAmount, amount); + mstore(0, 0x3078b2f6) + mstore(0x20, allocatedAmount) + mstore(0x40, spentAmount) + revert(0x1c, 0x44) + } + } + + return true; + } +} diff --git a/src/lib/ConstructorLogic.sol b/src/lib/ConstructorLogic.sol index 0f98468..04ae01b 100644 --- a/src/lib/ConstructorLogic.sol +++ b/src/lib/ConstructorLogic.sol @@ -5,7 +5,7 @@ import { Lock } from "../types/Lock.sol"; import { ResetPeriod } from "../types/ResetPeriod.sol"; import { Scope } from "../types/Scope.sol"; -import { HashLib } from "./HashLib.sol"; +import { DomainLib } from "./DomainLib.sol"; import { IdLib } from "./IdLib.sol"; import { MetadataRenderer } from "./MetadataRenderer.sol"; @@ -21,8 +21,8 @@ import { Tstorish } from "tstorish/Tstorish.sol"; * for activating TSTORE support if the chain eventually adds support for it. */ contract ConstructorLogic is Tstorish { - using HashLib for bytes32; - using HashLib for uint256; + using DomainLib for bytes32; + using DomainLib for uint256; using IdLib for uint256; // Address of the Permit2 contract, optionally used for depositing ERC20 tokens. diff --git a/src/lib/DomainLib.sol b/src/lib/DomainLib.sol new file mode 100644 index 0000000..509f0ab --- /dev/null +++ b/src/lib/DomainLib.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title DomainLib + * @notice Libray contract implementing logic for deriving domain hashes. + */ +library DomainLib { + /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + /// @dev `keccak256(bytes("The Compact"))`. + bytes32 internal constant _NAME_HASH = 0x5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b292; + + /// @dev `keccak256("0")`. + bytes32 internal constant _VERSION_HASH = 0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d; + + function toLatest(bytes32 initialDomainSeparator, uint256 initialChainId) internal view returns (bytes32 domainSeparator) { + // Set the initial domain separator as the default domain separator. + domainSeparator = initialDomainSeparator; + + assembly ("memory-safe") { + // Rederive the domain separator if the initial chain ID differs from the current one. + if xor(chainid(), initialChainId) { + // Retrieve the free memory pointer. + let m := mload(0x40) + + // Prepare domain data: EIP-712 typehash, name hash, version hash, chain ID, and verifying contract. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), _NAME_HASH) + mstore(add(m, 0x40), _VERSION_HASH) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + + // Derive the domain separator. + domainSeparator := keccak256(m, 0xa0) + } + } + } + + function toNotarizedDomainSeparator(uint256 notarizedChainId) internal view returns (bytes32 notarizedDomainSeparator) { + assembly ("memory-safe") { + // Retrieve the free memory pointer. + let m := mload(0x40) + + // Prepare domain data: EIP-712 typehash, name hash, version hash, notarizing chain ID, and verifying contract. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), _NAME_HASH) + mstore(add(m, 0x40), _VERSION_HASH) + mstore(add(m, 0x60), notarizedChainId) + mstore(add(m, 0x80), address()) + + // Derive the domain separator. + notarizedDomainSeparator := keccak256(m, 0xa0) + } + } + + function withDomain(bytes32 messageHash, bytes32 domainSeparator) internal pure returns (bytes32 domainHash) { + assembly ("memory-safe") { + // Retrieve and cache the free memory pointer. + let m := mload(0x40) + + // Prepare the 712 prefix. + mstore(0, 0x1901) + + // Prepare the domain separator. + mstore(0x20, domainSeparator) + + // Prepare the message hash and compute the domain hash. + mstore(0x40, messageHash) + domainHash := keccak256(0x1e, 0x42) + + // Restore the free memory pointer. + mstore(0x40, m) + } + } +} diff --git a/src/lib/FunctionCastLib.sol b/src/lib/FunctionCastLib.sol index fed4abf..98f62d6 100644 --- a/src/lib/FunctionCastLib.sol +++ b/src/lib/FunctionCastLib.sol @@ -3427,4 +3427,14 @@ library FunctionCastLib { fnOut := fnIn } } + + function usingSplitByIdComponent(function(TransferComponent[] calldata, uint256, function (TransferComponent[] calldata, uint256) internal pure returns (uint96)) internal returns (address) fnIn) + internal + pure + returns (function(SplitByIdComponent[] calldata, uint256, function (SplitByIdComponent[] calldata, uint256) internal pure returns (uint96)) internal returns (address) fnOut) + { + assembly ("memory-safe") { + fnOut := fnIn + } + } } diff --git a/src/lib/HashLib.sol b/src/lib/HashLib.sol index c8686a2..d4c8bf7 100644 --- a/src/lib/HashLib.sol +++ b/src/lib/HashLib.sol @@ -42,61 +42,8 @@ library HashLib { using EfficiencyLib for bool; using EfficiencyLib for uint256; using FunctionCastLib for function (BatchTransfer calldata, uint256) internal view returns (bytes32); - - /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; - - /// @dev `keccak256(bytes("The Compact"))`. - bytes32 internal constant _NAME_HASH = 0x5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b292; - - /// @dev `keccak256("0")`. - bytes32 internal constant _VERSION_HASH = 0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d; - - function toLatest(bytes32 initialDomainSeparator, uint256 initialChainId) internal view returns (bytes32 domainSeparator) { - domainSeparator = initialDomainSeparator; - - assembly ("memory-safe") { - // Prepare the domain separator, rederiving it if necessary. - if xor(chainid(), initialChainId) { - let m := mload(0x40) // Grab the free memory pointer. - mstore(m, _DOMAIN_TYPEHASH) - mstore(add(m, 0x20), _NAME_HASH) - mstore(add(m, 0x40), _VERSION_HASH) - mstore(add(m, 0x60), chainid()) - mstore(add(m, 0x80), address()) - domainSeparator := keccak256(m, 0xa0) - } - } - } - - function toNotarizedDomainSeparator(uint256 notarizedChainId) internal view returns (bytes32 notarizedDomainSeparator) { - assembly ("memory-safe") { - let m := mload(0x40) // Grab the free memory pointer. - mstore(m, _DOMAIN_TYPEHASH) - mstore(add(m, 0x20), _NAME_HASH) - mstore(add(m, 0x40), _VERSION_HASH) - mstore(add(m, 0x60), notarizedChainId) - mstore(add(m, 0x80), address()) - notarizedDomainSeparator := keccak256(m, 0xa0) - } - } - - function withDomain(bytes32 messageHash, bytes32 domainSeparator) internal pure returns (bytes32 domainHash) { - assembly ("memory-safe") { - let m := mload(0x40) // Grab the free memory pointer. - - // Prepare the 712 prefix. - mstore(0, 0x1901) - - mstore(0x20, domainSeparator) - - // Prepare the message hash and compute the domain hash. - mstore(0x40, messageHash) - domainHash := keccak256(0x1e, 0x42) - - mstore(0x40, m) // Restore the free memory pointer. - } - } + using HashLib for uint256; + using HashLib for BatchTransfer; function toBasicTransferMessageHash(BasicTransfer calldata transfer) internal view returns (bytes32 messageHash) { assembly ("memory-safe") { @@ -156,7 +103,7 @@ library HashLib { idsAndAmountsHash := keccak256(m, totalTransferData) } - return deriveBatchCompactMessageHash(transfer, idsAndAmountsHash); + return transfer.deriveBatchCompactMessageHash(idsAndAmountsHash); } function toSplitBatchTransferMessageHash(SplitBatchTransfer calldata transfer) internal view returns (bytes32) { @@ -304,11 +251,11 @@ library HashLib { } function toSimpleMultichainClaimMessageHash(uint256 claim, uint256 idsAndAmountsHash) internal view returns (bytes32 messageHash) { - return toMultichainClaimMessageHash(claim, uint256(0).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); + return claim.toMultichainClaimMessageHash(uint256(0).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); } function toQualifiedMultichainClaimMessageHash(uint256 claim, uint256 idsAndAmountsHash) internal view returns (bytes32 messageHash) { - return toMultichainClaimMessageHash(claim, uint256(0x40).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); + return claim.toMultichainClaimMessageHash(uint256(0x40).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); } function toMultichainClaimMessageHash(uint256 claim, uint256 additionalOffset, bytes32 allocationTypehash, bytes32 multichainCompactTypehash, uint256 idsAndAmountsHash) @@ -345,11 +292,11 @@ library HashLib { } function toSimpleExogenousMultichainClaimMessageHash(uint256 claim, uint256 idsAndAmountsHash) internal view returns (bytes32 messageHash) { - return toExogenousMultichainClaimMessageHash(claim, uint256(0).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); + return claim.toExogenousMultichainClaimMessageHash(uint256(0).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); } function toExogenousQualifiedMultichainClaimMessageHash(uint256 claim, uint256 idsAndAmountsHash) internal view returns (bytes32 messageHash) { - return toExogenousMultichainClaimMessageHash(claim, uint256(0x40).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); + return claim.toExogenousMultichainClaimMessageHash(uint256(0x40).asStubborn(), SEGMENT_TYPEHASH, MULTICHAIN_COMPACT_TYPEHASH, idsAndAmountsHash); } function toExogenousMultichainClaimMessageHash(uint256 claim, uint256 additionalOffset, bytes32 allocationTypehash, bytes32 multichainCompactTypehash, uint256 idsAndAmountsHash) @@ -472,4 +419,25 @@ library HashLib { qualificationMessageHash := keccak256(m, add(0x40, qualificationPayloadLength)) } } + + /** + * @notice Internal pure function for retrieving EIP-712 typehashes where no witness data is + * provided, returning the corresponding typehash based on the index provided. The available + * typehashes are: + * - 0: COMPACT_TYPEHASH + * - 1: BATCH_COMPACT_TYPEHASH + * - 2: MULTICHAIN_COMPACT_TYPEHASH + * @param i The index of the EIP-712 typehash to retrieve. + * @return typehash The corresponding EIP-712 typehash. + */ + function typehashes(uint256 i) internal pure returns (bytes32 typehash) { + assembly ("memory-safe") { + 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/lib/TransferLogic.sol b/src/lib/TransferLogic.sol index e5b13e0..533a0bb 100644 --- a/src/lib/TransferLogic.sol +++ b/src/lib/TransferLogic.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.27; import { BatchTransfer, SplitBatchTransfer } from "../types/BatchClaims.sol"; import { BasicTransfer, SplitTransfer } from "../types/Claims.sol"; -import { SplitComponent, TransferComponent, SplitByIdComponent } from "../types/Components.sol"; +import { TransferComponent, SplitByIdComponent } from "../types/Components.sol"; import { ClaimHashLib } from "./ClaimHashLib.sol"; +import { ComponentLib } from "./ComponentLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { EventLib } from "./EventLib.sol"; import { FunctionCastLib } from "./FunctionCastLib.sol"; @@ -26,13 +27,17 @@ contract TransferLogic is SharedLogic { using ClaimHashLib for SplitTransfer; using ClaimHashLib for BatchTransfer; using ClaimHashLib for SplitBatchTransfer; + using ComponentLib for SplitTransfer; + using ComponentLib for BatchTransfer; + using ComponentLib for SplitBatchTransfer; using IdLib for uint256; using EfficiencyLib for bool; using EventLib for address; using ValidityLib for uint96; using ValidityLib for uint256; using ValidityLib for bytes32; - using FunctionCastLib for function(bytes32, address, BasicTransfer calldata) internal; + using FunctionCastLib for function (bytes32, address, BasicTransfer calldata) internal; + using FunctionCastLib for function(TransferComponent[] calldata, uint256, function (TransferComponent[] calldata, uint256) internal pure returns (uint96)) internal returns (address); // bytes4(keccak256("attest(address,address,address,uint256,uint256)")). uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; @@ -65,21 +70,8 @@ contract TransferLogic is SharedLogic { // Derive hash, validate expiry, consume nonce, and check allocator signature. _notExpiredAndSignedByAllocator.usingSplitTransfer()(transfer.toClaimHash(), transfer.id.toRegisteredAllocatorWithConsumed(transfer.nonce), transfer); - // Retrieve the total number of components. - uint256 totalSplits = transfer.recipients.length; - - unchecked { - // Iterate over each additional component in calldata. - for (uint256 i = 0; i < totalSplits; ++i) { - // Navigate to location of the component in calldata. - SplitComponent calldata component = transfer.recipients[i]; - - // Perform the transfer or withdrawal for the portion. - operation(msg.sender, component.claimant, transfer.id, component.amount); - } - } - - return true; + // Perform the split transfers or withdrawals. + return transfer.processSplitTransfer(operation); } /** @@ -93,23 +85,12 @@ contract TransferLogic is SharedLogic { */ function _processBatchTransfer(BatchTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { // Derive hash, validate expiry, consume nonce, and check allocator signature. - _notExpiredAndSignedByAllocator.usingBatchTransfer()(transfer.toClaimHash(), _deriveConsistentAllocatorAndConsumeNonce(transfer.transfers, transfer.nonce), transfer); - - // Retrieve the total number of components. - uint256 totalTransfers = transfer.transfers.length; - - unchecked { - // Iterate over each component in calldata. - for (uint256 i = 0; i < totalTransfers; ++i) { - // Navigate to location of the component in calldata. - TransferComponent calldata component = transfer.transfers[i]; - - // Perform the transfer or withdrawal for the component. - operation(msg.sender, transfer.recipient, component.id, component.amount); - } - } + _notExpiredAndSignedByAllocator.usingBatchTransfer()( + transfer.toClaimHash(), _deriveConsistentAllocatorAndConsumeNonce(transfer.transfers, transfer.nonce, _allocatorIdOfTransferComponentId), transfer + ); - return true; + // Perform the batch transfers or withdrawals. + return transfer.performBatchTransfer(operation); } /** @@ -123,41 +104,12 @@ contract TransferLogic is SharedLogic { */ function _processSplitBatchTransfer(SplitBatchTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { // Derive hash, validate expiry, consume nonce, and check allocator signature. - _notExpiredAndSignedByAllocator.usingSplitBatchTransfer()(transfer.toClaimHash(), _deriveConsistentAllocatorAndConsumeNonceWithSplit(transfer.transfers, transfer.nonce), transfer); - - // Declare a variable for tracking the id of each component. - uint256 id; - - // Retrieve the total number of components. - uint256 totalIds = transfer.transfers.length; - - unchecked { - // Iterate over each component in calldata. - for (uint256 i = 0; i < totalIds; ++i) { - // Navigate to location of the component in calldata. - SplitByIdComponent calldata component = transfer.transfers[i]; - - // Retrieve the id for the component. - id = component.id; - - // Navigate to location of component portions in calldata. - SplitComponent[] calldata portions = component.portions; - - // Retrieve the total number of portions on the component. - uint256 totalPortions = portions.length; - - // Iterate over each portion in calldata. - for (uint256 j = 0; j < totalPortions; ++j) { - // Navigate to the location of the portion in calldata. - SplitComponent calldata portion = portions[j]; - - // Perform the transfer or withdrawal for the portion. - operation(msg.sender, portion.claimant, id, portion.amount); - } - } - } + _notExpiredAndSignedByAllocator.usingSplitBatchTransfer()( + transfer.toClaimHash(), _deriveConsistentAllocatorAndConsumeNonce.usingSplitByIdComponent()(transfer.transfers, transfer.nonce, _allocatorIdOfSplitByIdComponent), transfer + ); - return true; + // Perform the split batch transfers or withdrawals. + return transfer.performSplitBatchTransfer(operation); } /** @@ -237,19 +189,24 @@ contract TransferLogic is SharedLogic { * @notice Private function that ensures all components in a batch transfer share the * same allocator and consumes the nonce. Reverts if any component has a different * allocator or if the batch is empty. - * @param components Array of transfer components to check. - * @param nonce The nonce to consume. - * @return allocator The validated allocator address. + * @param components Array of transfer components to check. + * @param nonce The nonce to consume. + * @param allocatorIdRetrieval Function pointer to retrieve allocatorId from components array (handles split components). + * @return allocator The validated allocator address. */ - function _deriveConsistentAllocatorAndConsumeNonce(TransferComponent[] calldata components, uint256 nonce) private returns (address allocator) { + function _deriveConsistentAllocatorAndConsumeNonce( + TransferComponent[] calldata components, + uint256 nonce, + function (TransferComponent[] calldata, uint256) internal pure returns (uint96) allocatorIdRetrieval + ) private returns (address allocator) { // Retrieve the total number of components. uint256 totalComponents = components.length; // Track errors, starting with whether total number of components is zero. uint256 errorBuffer = (totalComponents == 0).asUint256(); - // NOTE: bounds checks on these array accesses can be skipped as an optimization - uint96 allocatorId = components[0].id.toAllocatorId(); + // Retrieve the ID of the initial component and derive the allocator ID. + uint96 allocatorId = allocatorIdRetrieval(components, 0); // Retrieve the allocator address and consume the nonce. allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); @@ -257,8 +214,8 @@ contract TransferLogic is SharedLogic { unchecked { // Iterate over each additional component in calldata. for (uint256 i = 1; i < totalComponents; ++i) { - // Mark as an error if the allocatorId does not match the initial one. - errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); + // Retrieve ID and mark error if derived allocatorId differs from initial one. + errorBuffer |= (allocatorIdRetrieval(components, i) != allocatorId).asUint256(); } } @@ -273,41 +230,26 @@ contract TransferLogic is SharedLogic { } /** - * @notice Private function that ensures all components in a split batch transfer share - * the same allocator and consumes the nonce. Reverts if any component has a different - * allocator or if the batch is empty. - * @param components Array of split transfer components to check. - * @param nonce The nonce to consume. - * @return allocator The validated allocator address. + * @notice Private pure function that retrieves the ID of a batch transfer component from + * an array of components at a specific index and uses it to derive an allocator ID. + * @param components Array of batch transfer components. + * @param index The index of the batch transfer component to retrieve. + * @return allocatorId The allocator ID derived from the transfer component at the given index. */ - function _deriveConsistentAllocatorAndConsumeNonceWithSplit(SplitByIdComponent[] calldata components, uint256 nonce) private returns (address allocator) { - // Retrieve the total number of components. - uint256 totalComponents = components.length; - - // Track errors, starting with whether total number of components is zero. - uint256 errorBuffer = (totalComponents == 0).asUint256(); - - // NOTE: bounds checks on these array accesses can be skipped as an optimization - uint96 allocatorId = components[0].id.toAllocatorId(); - - // Retrieve the allocator address and consume the nonce. - allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); - - unchecked { - // Iterate over each additional component in calldata. - for (uint256 i = 1; i < totalComponents; ++i) { - // Mark as an error if the allocatorId does not match the initial one. - errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); - } - } + function _allocatorIdOfTransferComponentId(TransferComponent[] calldata components, uint256 index) private pure returns (uint96) { + // Retrieve ID from the component and derive corresponding allocator ID. + return components[index].id.toAllocatorId(); + } - // Revert if an error was encountered. - assembly ("memory-safe") { - if errorBuffer { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } + /** + * @notice Private pure function that retrieves the ID of a split batch transfer component + * from an array of components at a specific index and uses it to derive an allocator ID. + * @param components Array of split batch transfer components. + * @param index The index of the split batch transfer component to retrieve. + * @return allocatorId The allocator ID derived from the transfer component at the given index. + */ + function _allocatorIdOfSplitByIdComponent(SplitByIdComponent[] calldata components, uint256 index) private pure returns (uint96) { + // Retrieve ID from the component and derive corresponding allocator ID. + return components[index].id.toAllocatorId(); } } diff --git a/src/lib/ValidityLib.sol b/src/lib/ValidityLib.sol index be1d269..d88ec1e 100644 --- a/src/lib/ValidityLib.sol +++ b/src/lib/ValidityLib.sol @@ -6,7 +6,7 @@ import { Scope } from "../types/Scope.sol"; import { IdLib } from "./IdLib.sol"; import { ConsumerLib } from "./ConsumerLib.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; -import { HashLib } from "./HashLib.sol"; +import { DomainLib } from "./DomainLib.sol"; import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; /** @@ -19,7 +19,7 @@ library ValidityLib { using IdLib for uint256; using ConsumerLib for uint256; using EfficiencyLib for bool; - using HashLib for bytes32; + using DomainLib for bytes32; using SignatureCheckerLib for address; using ValidityLib for uint256;