From 1c146217250b43d618e090c1e51260a319f6036e Mon Sep 17 00:00:00 2001 From: mgretzke Date: Wed, 27 Aug 2025 12:27:08 +0200 Subject: [PATCH] empty recipient makes the caller the recipient --- snapshots/ERC7683Allocator_open.json | 2 +- snapshots/ERC7683Allocator_openFor.json | 2 +- snapshots/HybridAllocatorTest.json | 14 +++++------ src/allocators/HybridAllocator.sol | 1 + src/allocators/OnChainAllocator.sol | 1 + src/allocators/lib/AllocatorLib.sol | 7 ++++++ test/HybridAllocator.t.sol | 29 ++++++++++++++++++++++ test/OnChainAllocator.t.sol | 32 +++++++++++++++++++++++++ 8 files changed, 79 insertions(+), 9 deletions(-) diff --git a/snapshots/ERC7683Allocator_open.json b/snapshots/ERC7683Allocator_open.json index 9c7f3d9..b2e46f7 100644 --- a/snapshots/ERC7683Allocator_open.json +++ b/snapshots/ERC7683Allocator_open.json @@ -1,3 +1,3 @@ { - "open_simpleOrder": "168301" + "open_simpleOrder": "168393" } \ No newline at end of file diff --git a/snapshots/ERC7683Allocator_openFor.json b/snapshots/ERC7683Allocator_openFor.json index c719551..6b968e5 100644 --- a/snapshots/ERC7683Allocator_openFor.json +++ b/snapshots/ERC7683Allocator_openFor.json @@ -1,3 +1,3 @@ { - "openFor_simpleOrder_userHimself": "171750" + "openFor_simpleOrder_userHimself": "171749" } \ No newline at end of file diff --git a/snapshots/HybridAllocatorTest.json b/snapshots/HybridAllocatorTest.json index c5840e6..11a16cc 100644 --- a/snapshots/HybridAllocatorTest.json +++ b/snapshots/HybridAllocatorTest.json @@ -1,10 +1,10 @@ { - "allocateAndRegister_erc20Token": "187668", - "allocateAndRegister_erc20Token_emptyAmountInput": "188578", - "allocateAndRegister_multipleTokens": "223574", - "allocateAndRegister_nativeToken": "139204", - "allocateAndRegister_nativeToken_emptyAmountInput": "139040", - "allocateAndRegister_second_erc20Token": "114874", - "allocateAndRegister_second_nativeToken": "104840", + "allocateAndRegister_erc20Token": "187684", + "allocateAndRegister_erc20Token_emptyAmountInput": "188594", + "allocateAndRegister_multipleTokens": "223590", + "allocateAndRegister_nativeToken": "139220", + "allocateAndRegister_nativeToken_emptyAmountInput": "139056", + "allocateAndRegister_second_erc20Token": "114890", + "allocateAndRegister_second_nativeToken": "104856", "hybrid_execute_single": "174395" } \ No newline at end of file diff --git a/src/allocators/HybridAllocator.sol b/src/allocators/HybridAllocator.sol index e59737c..574ecde 100644 --- a/src/allocators/HybridAllocator.sol +++ b/src/allocators/HybridAllocator.sol @@ -88,6 +88,7 @@ contract HybridAllocator is IHybridAllocator { bytes32 typehash, bytes32 witness ) public payable returns (bytes32, uint256[] memory, uint256) { + recipient = AL.getRecipient(recipient); idsAndAmounts = _actualIdsAndAmounts(idsAndAmounts); (bytes32 claimHash, uint256[] memory registeredAmounts) = _COMPACT.batchDepositAndRegisterFor{value: msg.value}( diff --git a/src/allocators/OnChainAllocator.sol b/src/allocators/OnChainAllocator.sol index ac0fe19..41909f9 100644 --- a/src/allocators/OnChainAllocator.sol +++ b/src/allocators/OnChainAllocator.sol @@ -87,6 +87,7 @@ contract OnChainAllocator is IOnChainAllocator { bytes32 typehash, bytes32 witness ) public returns (bytes32 claimHash, uint256[] memory registeredAmounts, uint256 nonce) { + recipient = AL.getRecipient(recipient); nonce = _getAndUpdateNonce(msg.sender, recipient); uint256[2][] memory idsAndAmounts = new uint256[2][](commitments.length); diff --git a/src/allocators/lib/AllocatorLib.sol b/src/allocators/lib/AllocatorLib.sol index ef06512..e234d8c 100644 --- a/src/allocators/lib/AllocatorLib.sol +++ b/src/allocators/lib/AllocatorLib.sol @@ -152,6 +152,13 @@ library AllocatorLib { return ecrecover(digest, v, r, s); } + function getRecipient(address recipient) internal view returns (address) { + assembly ("memory-safe") { + recipient := xor(recipient, mul(caller(), iszero(recipient))) + } + return recipient; + } + function splitId(uint256 id) internal pure returns (uint96 allocatorId_, address token_) { return (splitAllocatorId(id), splitToken(id)); } diff --git a/test/HybridAllocator.t.sol b/test/HybridAllocator.t.sol index 52b35c4..08f9f35 100644 --- a/test/HybridAllocator.t.sol +++ b/test/HybridAllocator.t.sol @@ -520,6 +520,35 @@ contract HybridAllocatorTest is Test, TestHelper { assertTrue(allocator.isClaimAuthorized(createdHash, address(0), address(0), 0, 0, new uint256[2][](0), '')); } + function test_allocateAndRegister_success_emptyRecipientBecomesCaller() public { + uint256[2][] memory idsAndAmounts = new uint256[2][](1); + idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(usdc)); + idsAndAmounts[0][1] = defaultAmount; + + // Provide tokens + vm.prank(user); + usdc.transfer(address(allocator), defaultAmount); + assertEq(usdc.balanceOf(address(allocator)), defaultAmount); + + vm.prank(user); + (bytes32 claimHash, uint256[] memory registeredAmounts, uint256 nonce) = allocator.allocateAndRegister( + address(0), /* allocate for an empty recipient */ + idsAndAmounts, + arbiter, + defaultExpiration, + BATCH_COMPACT_TYPEHASH, + '' + ); + + // Ensure the allocation happened for the caller (user), not address(0) + assertTrue(compact.isRegistered(user, claimHash, BATCH_COMPACT_TYPEHASH)); + assertTrue(allocator.isClaimAuthorized(claimHash, address(0), address(0), 0, 0, new uint256[2][](0), '')); + assertEq(registeredAmounts[0], defaultAmount); + assertEq(usdc.balanceOf(address(compact)), defaultAmount); + assertEq(compact.balanceOf(address(user), idsAndAmounts[0][0]), defaultAmount); + assertEq(nonce, 1); + } + function test_allocateAndRegister_slot() public { uint256[2][] memory idsAndAmounts = new uint256[2][](1); idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(0)); diff --git a/test/OnChainAllocator.t.sol b/test/OnChainAllocator.t.sol index 0314499..60f596f 100644 --- a/test/OnChainAllocator.t.sol +++ b/test/OnChainAllocator.t.sol @@ -1304,4 +1304,36 @@ contract OnChainAllocatorTest is Test, TestHelper { vm.expectRevert(abi.encodeWithSelector(IOnChainAllocator.InsufficientBalance.selector, recipient, id2, 0, 1)); allocator.attest(address(this), recipient, address(this), id2, 1); } + + function test_allocateAndRegister_emptyRecipientBecomesCaller() public { + Lock[] memory commitments = new Lock[](1); + commitments[0] = _makeLock(address(usdc), defaultAmount); + + usdc.mint(address(allocator), defaultAmount); + + vm.prank(caller); + (bytes32 claimHash, uint256[] memory registeredAmounts, uint256 nonce) = allocator.allocateAndRegister( + address(0), /* allocate for an empty recipient */ + commitments, + arbiter, + defaultExpiration, + BATCH_COMPACT_TYPEHASH, + bytes32(0) + ); + + uint256[2][] memory idsAndAmounts = new uint256[2][](1); + idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(usdc)); + idsAndAmounts[0][1] = defaultAmount; + + assertEq(nonce, _composeNonceUint(caller, 1)); + assertEq(registeredAmounts.length, 1); + assertEq(registeredAmounts[0], defaultAmount); + // Ensure the allocation happened for the caller, not address(0) + assertEq(ERC6909(address(compact)).balanceOf(caller, idsAndAmounts[0][0]), defaultAmount); + assertTrue(allocator.isClaimAuthorized(claimHash, arbiter, caller, nonce, defaultExpiration, idsAndAmounts, '')); + assertTrue(compact.isRegistered(caller, claimHash, BATCH_COMPACT_TYPEHASH)); + bytes32 claimHashRecreated = + _createClaimHash(caller, arbiter, nonce, defaultExpiration, commitments, bytes32(0)); + assertEq(claimHashRecreated, claimHash); + } }