Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 0 additions & 76 deletions src/NonceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,6 @@ abstract contract NonceManager is INonceManager, EIP712 {
revert WrongChainId(uint64(block.chainid), tree.currentChainInvalidations.chainId);
}

// Validate that at least one nonce is being cancelled
if (tree.currentChainInvalidations.salts.length == 0) {
revert EmptyArray();
}

// Hash the current chain's NoncesToInvalidate as a leaf
bytes32 currentChainHash = hashNoncesToInvalidate(tree.currentChainInvalidations);

Expand Down Expand Up @@ -289,75 +284,4 @@ abstract contract NonceManager is INonceManager, EIP712 {
revert InvalidSignature(owner);
}
}

/**
* @dev Hash a NonceNode structure for EIP-712 signing
* @dev Recursively hashes the complete tree structure for UI transparency
* @dev Mirrors hashPermitNode implementation for consistency
* @dev Binary tree where leaves are NoncesToInvalidate structs (chain-specific nonce lists)
* @dev Sorts hashes to match TreeNodeLib reconstruction behavior
* @param nonceNode The nonce node tree structure to hash
* @return bytes32 The EIP-712 hash of the complete nonce node structure
*/
function hashNonceNode(
NonceNode memory nonceNode
) internal pure returns (bytes32) {
bytes32 nodesArrayHash;
bytes32 noncesArrayHash;

// Hash child nodes in separate block (stack management)
{
bytes32[] memory nodeHashes = new bytes32[](nonceNode.nodes.length);
for (uint256 i = 0; i < nonceNode.nodes.length; i++) {
nodeHashes[i] = hashNonceNode(nonceNode.nodes[i]);
}
// Sort to match TreeNodeLib.combineNodeAndNode behavior
_sortBytes32Array(nodeHashes);
nodesArrayHash = keccak256(abi.encodePacked(nodeHashes));
}

// Hash NoncesToInvalidate array in separate block (stack management)
{
bytes32[] memory nonceInvalidationHashes = new bytes32[](nonceNode.nonces.length);
for (uint256 i = 0; i < nonceNode.nonces.length; i++) {
// For single nonce, use the nonce directly (matches hashNoncesToInvalidate logic)
if (nonceNode.nonces[i].salts.length == 1) {
nonceInvalidationHashes[i] = nonceNode.nonces[i].salts[0];
} else {
// Multiple nonces - hash as NoncesToInvalidate struct (no sorting, preserve order)
nonceInvalidationHashes[i] = keccak256(
abi.encode(
NONCES_TO_INVALIDATE_TYPEHASH,
nonceNode.nonces[i].chainId,
keccak256(abi.encodePacked(nonceNode.nonces[i].salts))
)
);
}
}
// Sort to match TreeNodeLib.combineLeafAndLeaf behavior
_sortBytes32Array(nonceInvalidationHashes);
noncesArrayHash = keccak256(abi.encodePacked(nonceInvalidationHashes));
}

return keccak256(abi.encode(NONCE_NODE_TYPEHASH, nodesArrayHash, noncesArrayHash));
}

/**
* @dev Helper function to sort bytes32 array in place (bubble sort)
* @param arr Array to sort
*/
function _sortBytes32Array(
bytes32[] memory arr
) private pure {
uint256 n = arr.length;
for (uint256 i = 0; i < n; i++) {
for (uint256 j = i + 1; j < n; j++) {
if (uint256(arr[i]) > uint256(arr[j])) {
bytes32 temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
}
2 changes: 1 addition & 1 deletion test/Permit3Edge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ contract Permit3EdgeTest is Test {
(amount, expiration, ts) = permit3.allowance(owner, address(token), spender);
assertEq(amount, 0); // Amount remains unchanged by unlock operation
assertEq(expiration, 0); // No expiration (unlocked)
// Note: timestamp should remain from lock operation since unlock only changes expiration
// Note: timestamp should remain from lock operation since unlock only changes expiration
assertEq(ts, uint48(block.timestamp)); // Timestamp remains from lock operation
}

Expand Down