Skip to content
Open
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
4 changes: 2 additions & 2 deletions snapshots/ERC7683Allocator_open.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"open_simpleOrder": "168301"
}
"open_simpleOrder": "168301"
}
4 changes: 2 additions & 2 deletions snapshots/ERC7683Allocator_openFor.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"openFor_simpleOrder_userHimself": "171750"
}
"openFor_simpleOrder_userHimself": "171753"
}
18 changes: 9 additions & 9 deletions snapshots/HybridAllocatorTest.json
Original file line number Diff line number Diff line change
@@ -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",
"hybrid_execute_single": "174395"
}
"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",
"hybrid_execute_single": "174395"
}
16 changes: 8 additions & 8 deletions snapshots/OnChainAllocatorTest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"allocateFor_success_withRegistration": "133753",
"allocate_and_delete_expired_allocation": "65921",
"allocate_erc20": "129192",
"allocate_native": "128952",
"allocate_second_erc20": "97204",
"onchain_execute_double": "347707",
"onchain_execute_single": "219699"
}
"allocateFor_success_withRegistration": "133756",
"allocate_and_delete_expired_allocation": "65921",
"allocate_erc20": "129192",
"allocate_native": "128952",
"allocate_second_erc20": "97204",
"onchain_execute_double": "347707",
"onchain_execute_single": "219699"
}
10 changes: 10 additions & 0 deletions src/allocators/HybridAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {ITheCompact} from '@uniswap/the-compact/interfaces/ITheCompact.sol';
import {IHybridAllocator} from 'src/interfaces/IHybridAllocator.sol';

contract HybridAllocator is IHybridAllocator {
uint256 private immutable _INITIAL_CHAIN_ID;
uint96 public immutable ALLOCATOR_ID;
ITheCompact internal immutable _COMPACT;
bytes32 internal immutable _COMPACT_DOMAIN_SEPARATOR;
Expand All @@ -35,6 +36,7 @@ contract HybridAllocator is IHybridAllocator {
if (signer_ == address(0)) {
revert InvalidSigner();
}
_INITIAL_CHAIN_ID = block.chainid;
_COMPACT = ITheCompact(compact_);
ALLOCATOR_ID = _COMPACT.__registerAllocator(address(this), '');
_COMPACT_DOMAIN_SEPARATOR = _COMPACT.DOMAIN_SEPARATOR();
Expand Down Expand Up @@ -170,6 +172,10 @@ contract HybridAllocator is IHybridAllocator {

// Check the allocator data for a valid signature by an authorized signer
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), _COMPACT_DOMAIN_SEPARATOR, claimHash));
if (block.chainid != _INITIAL_CHAIN_ID) {
// If the chain was forked, we can not use the cached domain separator
digest = keccak256(abi.encodePacked(bytes2(0x1901), _COMPACT.DOMAIN_SEPARATOR(), claimHash));
}
if (!_checkSignature(digest, allocatorData_)) {
revert InvalidSignature();
}
Expand All @@ -194,6 +200,10 @@ contract HybridAllocator is IHybridAllocator {

// Check the allocator data for a valid signature by an authorized allocator address
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), _COMPACT_DOMAIN_SEPARATOR, claimHash));
if (block.chainid != _INITIAL_CHAIN_ID) {
// If the chain was forked, we can not use the cached domain separator
digest = keccak256(abi.encodePacked(bytes2(0x1901), _COMPACT.DOMAIN_SEPARATOR(), claimHash));
}
return _checkSignature(digest, allocatorData);
}

Expand Down
7 changes: 7 additions & 0 deletions src/allocators/OnChainAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {Lock} from '@uniswap/the-compact/types/EIP712Types.sol';
/// @dev The contract ensures tokens can not be double spent by a user in a fully decentralized manner.
/// @dev Users can open orders for themselves or for others by providing a signature or the tokens directly.
contract OnChainAllocator is IOnChainAllocator {
uint256 private immutable _INITIAL_CHAIN_ID;
address public immutable COMPACT_CONTRACT;
bytes32 public immutable COMPACT_DOMAIN_SEPARATOR;
uint96 public immutable ALLOCATOR_ID;
Expand All @@ -33,6 +34,7 @@ contract OnChainAllocator is IOnChainAllocator {
}

constructor(address compactContract_) {
_INITIAL_CHAIN_ID = block.chainid;
COMPACT_CONTRACT = compactContract_;
COMPACT_DOMAIN_SEPARATOR = ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR();
ALLOCATOR_ID = ITheCompact(COMPACT_CONTRACT).__registerAllocator(address(this), '');
Expand Down Expand Up @@ -65,6 +67,11 @@ contract OnChainAllocator is IOnChainAllocator {
if (signature.length > 0) {
// confirm the provided signature is valid
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), COMPACT_DOMAIN_SEPARATOR, claimHash));
if (block.chainid != _INITIAL_CHAIN_ID) {
digest = keccak256(
abi.encodePacked(bytes2(0x1901), ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(), claimHash)
);
}
address signer_ = AL.recoverSigner(digest, signature);
if (sponsor != signer_ || signer_ == address(0)) {
revert InvalidSignature(signer_, sponsor);
Expand Down
82 changes: 82 additions & 0 deletions test/HybridAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,88 @@ contract HybridAllocatorTest is Test, TestHelper {
compact.batchClaim(claim);
}

function test_authorizeClaim_revert_oldSignatureAfterFork(uint128 nonce) public {
uint256[2][] memory idsAndAmounts = new uint256[2][](2);
idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(0));
idsAndAmounts[0][1] = defaultAmount;

idsAndAmounts[1][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(usdc));
idsAndAmounts[1][1] = defaultAmount;

// Approve tokens
vm.prank(user);
usdc.approve(address(compact), defaultAmount);

bytes32 witness = keccak256(abi.encode(WITNESS_TYPEHASH, 1));

bytes32 claimHash = _toBatchCompactHashWithWitness(
BATCH_COMPACT_TYPEHASH_WITH_WITNESS,
BatchCompact({
arbiter: arbiter,
sponsor: user,
nonce: nonce,
expires: defaultExpiration,
commitments: _idsAndAmountsToCommitments(idsAndAmounts)
}),
witness
);

bytes32[2][] memory claimHashesAndTypehashes = new bytes32[2][](1);
claimHashesAndTypehashes[0][0] = claimHash;
claimHashesAndTypehashes[0][1] = BATCH_COMPACT_TYPEHASH_WITH_WITNESS;

// Deposit and register
vm.prank(user);
compact.batchDepositAndRegisterMultiple{value: defaultAmount}(idsAndAmounts, claimHashesAndTypehashes);

// Off chain signing the claim
bytes32 digest = _toDigest(claimHash, compact.DOMAIN_SEPARATOR());
(bytes32 r, bytes32 vs) = vm.signCompact(signerPrivateKey, digest);
bytes memory allocatorData = abi.encodePacked(r, vs);

address target = makeAddr('target');

BatchClaimComponent[] memory claims = new BatchClaimComponent[](2);
{
Component[] memory portions = new Component[](1);
portions[0] = Component({
claimant: uint256(bytes32(abi.encodePacked(bytes12(0), target))), // indicating a withdrawal
amount: defaultAmount
});

claims[0] =
BatchClaimComponent({id: idsAndAmounts[0][0], allocatedAmount: defaultAmount, portions: portions});
claims[1] =
BatchClaimComponent({id: idsAndAmounts[1][0], allocatedAmount: defaultAmount, portions: portions});
}

BatchClaim memory claim = BatchClaim({
allocatorData: allocatorData,
sponsorSignature: '',
sponsor: user,
nonce: nonce,
expires: defaultExpiration,
witness: witness,
witnessTypestring: WITNESS_STRING,
claims: claims
});

uint256 snap = vm.snapshot();
assertEq(block.chainid, 31_337);

vm.prank(arbiter);
compact.batchClaim(claim);
// Call did not revert

vm.revertTo(snap);
vm.chainId(1);
assertEq(block.chainid, 1);

vm.prank(arbiter);
vm.expectRevert(abi.encodeWithSelector(IHybridAllocator.InvalidSignature.selector));
compact.batchClaim(claim);
}

function test_authorizeClaim_success_onChain() public {
uint256[2][] memory idsAndAmounts = new uint256[2][](2);
idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(0));
Expand Down
44 changes: 44 additions & 0 deletions test/OnChainAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,50 @@ contract OnChainAllocatorTest is Test, TestHelper {
allocator.allocateFor(user, commitments, arbiter, defaultExpiration, BATCH_COMPACT_TYPEHASH, 0x0, sig);
}

function test_allocateFor_revert_oldSignatureAfterFork(address relayer) public {
Lock[] memory commitments = new Lock[](1);
commitments[0] = _makeLock(address(0), defaultAmount);
vm.prank(user);
compact.depositNative{value: defaultAmount}(commitments[0].lockTag, user);

// build digest exactly like allocator expects
uint256 expectedNonce = _composeNonceUint(user, allocator.nonces(user) + 1);
bytes32 commitmentsHash = _commitmentsHash(commitments);
bytes32 claimHash = keccak256(
abi.encode(BATCH_COMPACT_TYPEHASH, arbiter, user, expectedNonce, defaultExpiration, commitmentsHash)
);
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), compact.DOMAIN_SEPARATOR(), claimHash));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(userPK, digest);
bytes memory sig = abi.encodePacked(r, s, v);

uint256 snap = vm.snapshot();
assertEq(block.chainid, 31_337);

vm.prank(relayer);
(bytes32 returnedHash, uint256 nonce) =
allocator.allocateFor(user, commitments, arbiter, defaultExpiration, BATCH_COMPACT_TYPEHASH, 0x0, sig);

uint256[2][] memory idsAndAmounts = new uint256[2][](1);
idsAndAmounts[0][0] = _toId(Scope.Multichain, ResetPeriod.TenMinutes, address(allocator), address(0));
idsAndAmounts[0][1] = defaultAmount;

assertEq(returnedHash, claimHash);
assertEq(nonce, expectedNonce);
assertTrue(allocator.isClaimAuthorized(claimHash, arbiter, user, nonce, defaultExpiration, idsAndAmounts, ''));

vm.revertTo(snap);
vm.chainId(1);
assertEq(block.chainid, 1);

vm.prank(relayer);
vm.expectRevert(
abi.encodeWithSelector(
IOnChainAllocator.InvalidSignature.selector, address(0x71efFb57bf7C717a0a5012186792C45A4851ef5d), user
)
);
allocator.allocateFor(user, commitments, arbiter, defaultExpiration, BATCH_COMPACT_TYPEHASH, 0x0, sig);
}

function test_allocateFor_success_withCompactSignature(address relayer) public {
Lock[] memory commitments = new Lock[](1);
commitments[0] = _makeLock(address(0), defaultAmount);
Expand Down