From 740e161fbaab24db8805d3e7d274a1941a88b221 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:52:49 -0700 Subject: [PATCH] finish all the batch cases --- src/TheCompact.sol | 223 ++++++++++++++++++++++++++++------ src/lib/HashLib.sol | 103 ++++++---------- test/TheCompact.t.sol | 277 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 499 insertions(+), 104 deletions(-) diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 9300099..65c964e 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -509,6 +509,17 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { return _processSplitBatchClaim(claimPayload, _release); } + function claim(QualifiedSplitBatchClaim calldata claimPayload) external returns (bool) { + return _processQualifiedSplitBatchClaim(claimPayload, _release); + } + + function claimAndWithdraw(QualifiedSplitBatchClaim calldata claimPayload) + external + returns (bool) + { + return _processQualifiedSplitBatchClaim(claimPayload, _release); + } + function claim(SplitBatchClaimWithWitness calldata claimPayload) external returns (bool) { return _processSplitBatchClaimWithWitness(claimPayload, _release); } @@ -520,6 +531,20 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { return _processSplitBatchClaimWithWitness(claimPayload, _release); } + function claim(QualifiedSplitBatchClaimWithWitness calldata claimPayload) + external + returns (bool) + { + return _processQualifiedSplitBatchClaimWithWitness(claimPayload, _release); + } + + function claimAndWithdraw(QualifiedSplitBatchClaimWithWitness calldata claimPayload) + external + returns (bool) + { + return _processQualifiedSplitBatchClaimWithWitness(claimPayload, _release); + } + function enableForcedWithdrawal(uint256 id) external returns (uint256 withdrawableAt) { withdrawableAt = block.timestamp + id.toResetPeriod().toSeconds(); @@ -675,6 +700,92 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { messageHash.signedBy(allocator, claimPayload.allocatorSignature, domainSeparator); } + function _usingQualifiedClaimWithWitness( + function (bytes32, bytes32, QualifiedClaim calldata, address) internal view fnIn + ) + internal + pure + returns ( + function (bytes32, bytes32, QualifiedClaimWithWitness calldata, address) internal view fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _usingQualifiedBatchClaim( + function (bytes32, bytes32, QualifiedClaim calldata, address) internal view fnIn + ) + internal + pure + returns ( + function (bytes32, bytes32, QualifiedBatchClaim calldata, address) internal view fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _usingQualifiedSplitBatchClaim( + function (bytes32, bytes32, QualifiedClaim calldata, address) internal view fnIn + ) + internal + pure + returns ( + function (bytes32, bytes32, QualifiedSplitBatchClaim calldata, address) internal view fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _usingQualifiedBatchClaimWithWitness( + function (bytes32, bytes32, QualifiedClaim calldata, address) internal view fnIn + ) + internal + pure + returns ( + function (bytes32, bytes32, QualifiedBatchClaimWithWitness calldata, address) internal view + fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _usingQualifiedSplitBatchClaimWithWitness( + function (bytes32, bytes32, QualifiedClaim calldata, address) internal view fnIn + ) + internal + pure + returns ( + function (bytes32, bytes32, QualifiedSplitBatchClaimWithWitness calldata, address) internal view + fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _notExpiredAndWithValidQualifiedSignatures( + bytes32 messageHash, + bytes32 qualificationMessageHash, + QualifiedClaim calldata claimPayload, + address allocator + ) internal view { + claimPayload.expires.later(); + bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID); + messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator); + qualificationMessageHash.signedBy( + allocator, claimPayload.allocatorSignature, domainSeparator + ); + } + // NOTE: this function expects that there's at least one array element function _notExpiredAndWithValidSignaturesBatch(BatchClaim calldata claimPayload) internal @@ -708,17 +819,14 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { QualifiedBatchClaim calldata claimPayload ) internal returns (bytes32 messageHash, uint96 allocatorId) { bytes32 qualificationMessageHash; - claimPayload.expires.later(); - allocatorId = claimPayload.claims[0].id.toAllocatorId(); - (messageHash, qualificationMessageHash) = claimPayload.toMessageHash(); - bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID); - messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator); - qualificationMessageHash.signedBy( - allocatorId.fromRegisteredAllocatorIdWithConsumed(claimPayload.nonce), - claimPayload.allocatorSignature, - domainSeparator + + _usingQualifiedBatchClaim(_notExpiredAndWithValidQualifiedSignatures)( + messageHash, + qualificationMessageHash, + claimPayload, + allocatorId.fromRegisteredAllocatorIdWithConsumed(claimPayload.nonce) ); } @@ -726,17 +834,14 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { QualifiedBatchClaimWithWitness calldata claimPayload ) internal returns (bytes32 messageHash, uint96 allocatorId) { bytes32 qualificationMessageHash; - claimPayload.expires.later(); - allocatorId = claimPayload.claims[0].id.toAllocatorId(); - (messageHash, qualificationMessageHash) = claimPayload.toMessageHash(); - bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID); - messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator); - qualificationMessageHash.signedBy( - allocatorId.fromRegisteredAllocatorIdWithConsumed(claimPayload.nonce), - claimPayload.allocatorSignature, - domainSeparator + + _usingQualifiedBatchClaimWithWitness(_notExpiredAndWithValidQualifiedSignatures)( + messageHash, + qualificationMessageHash, + claimPayload, + allocatorId.fromRegisteredAllocatorIdWithConsumed(claimPayload.nonce) ); } @@ -745,18 +850,26 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { returns (bytes32 messageHash) { bytes32 qualificationMessageHash; - claimPayload.expires.later(); - (messageHash, qualificationMessageHash) = claimPayload.toMessageHash(); - bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID); - messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator); - qualificationMessageHash.signedBy( - claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce), - claimPayload.allocatorSignature, - domainSeparator + + _notExpiredAndWithValidQualifiedSignatures( + messageHash, + qualificationMessageHash, + claimPayload, + claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce) ); } + function _usingClaimWithWitness(function (bytes32, Claim calldata, address) internal view fnIn) + internal + pure + returns (function (bytes32, ClaimWithWitness calldata, address) internal view fnOut) + { + assembly { + fnOut := fnIn + } + } + function _notExpiredAndWithValidSignaturesWithWitness( bytes32 messageHash, ClaimWithWitness calldata claimPayload, @@ -772,15 +885,13 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { QualifiedClaimWithWitness calldata claimPayload ) internal returns (bytes32 messageHash) { bytes32 qualificationMessageHash; - claimPayload.expires.later(); - (messageHash, qualificationMessageHash) = claimPayload.toMessageHash(); - bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID); - messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator); - qualificationMessageHash.signedBy( - claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce), - claimPayload.allocatorSignature, - domainSeparator + + _usingQualifiedClaimWithWitness(_notExpiredAndWithValidQualifiedSignatures)( + messageHash, + qualificationMessageHash, + claimPayload, + claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce) ); } @@ -797,7 +908,7 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { } function _usingSplitClaimWithWitness( - function(bytes32, ClaimWithWitness calldata, address) internal view fnIn + function(bytes32, Claim calldata, address) internal view fnIn ) internal pure @@ -1091,7 +1202,7 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { function(address, address, uint256, uint256) internal returns (bool) operation ) internal returns (bool) { bytes32 messageHash = claimPayload.toMessageHash(); - _notExpiredAndWithValidSignaturesWithWitness( + _usingClaimWithWitness(_notExpiredAndWithValidSignatures)( messageHash, claimPayload, claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce) @@ -1114,7 +1225,7 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { function(address, address, uint256, uint256) internal returns (bool) operation ) internal returns (bool) { bytes32 messageHash = claimPayload.toMessageHash(); - _usingSplitClaimWithWitness(_notExpiredAndWithValidSignaturesWithWitness)( + _usingSplitClaimWithWitness(_notExpiredAndWithValidSignatures)( messageHash, claimPayload, claimPayload.id.toRegisteredAllocatorWithConsumed(claimPayload.nonce) @@ -1288,6 +1399,44 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { ); } + function _processQualifiedSplitBatchClaim( + QualifiedSplitBatchClaim calldata batchClaim, + function(address, address, uint256, uint256) internal returns (bool) operation + ) internal returns (bool) { + (bytes32 messageHash, bytes32 qualifiedMessageHash) = batchClaim.toMessageHash(); + uint96 allocatorId = batchClaim.claims[0].id.toAllocatorId(); + + _usingQualifiedSplitBatchClaim(_notExpiredAndWithValidQualifiedSignatures)( + messageHash, + qualifiedMessageHash, + batchClaim, + allocatorId.fromRegisteredAllocatorIdWithConsumed(batchClaim.nonce) + ); + + return _verifyAndProcessSplitBatchComponents( + allocatorId, batchClaim.sponsor, messageHash, batchClaim.claims, operation + ); + } + + function _processQualifiedSplitBatchClaimWithWitness( + QualifiedSplitBatchClaimWithWitness calldata batchClaim, + function(address, address, uint256, uint256) internal returns (bool) operation + ) internal returns (bool) { + (bytes32 messageHash, bytes32 qualifiedMessageHash) = batchClaim.toMessageHash(); + uint96 allocatorId = batchClaim.claims[0].id.toAllocatorId(); + + _usingQualifiedSplitBatchClaimWithWitness(_notExpiredAndWithValidQualifiedSignatures)( + messageHash, + qualifiedMessageHash, + batchClaim, + allocatorId.fromRegisteredAllocatorIdWithConsumed(batchClaim.nonce) + ); + + return _verifyAndProcessSplitBatchComponents( + allocatorId, batchClaim.sponsor, messageHash, batchClaim.claims, operation + ); + } + function _processSplitBatchClaimWithWitness( SplitBatchClaimWithWitness calldata batchClaim, function(address, address, uint256, uint256) internal returns (bool) operation diff --git a/src/lib/HashLib.sol b/src/lib/HashLib.sol index 5ae52a1..ebacee3 100644 --- a/src/lib/HashLib.sol +++ b/src/lib/HashLib.sol @@ -455,6 +455,38 @@ library HashLib { return _toSplitBatchMessageHashWithWitness(claim, claim.claims); } + function _usingQualifiedSplitBatchClaim( + function (SplitBatchClaim calldata, SplitBatchClaimComponent[] calldata) internal view returns (bytes32) + fnIn + ) + internal + pure + returns ( + function (QualifiedSplitBatchClaim calldata, SplitBatchClaimComponent[] calldata) internal view returns (bytes32) + fnOut + ) + { + assembly { + fnOut := fnIn + } + } + + function _usingQualifiedSplitBatchClaimWithWitness( + function (SplitBatchClaimWithWitness calldata, SplitBatchClaimComponent[] calldata) internal view returns (bytes32) + fnIn + ) + internal + pure + returns ( + function (QualifiedSplitBatchClaimWithWitness calldata, SplitBatchClaimComponent[] calldata) internal view returns (bytes32) + fnOut + ) + { + assembly { + fnOut := fnIn + } + } + function _toSplitBatchMessageHash( SplitBatchClaim calldata claim, SplitBatchClaimComponent[] calldata claims @@ -628,37 +660,12 @@ library HashLib { return _usingSplitClaimWithWitness(toMessageHashWithWitness)(claim, 0); } - function toMessageHash(QualifiedSplitBatchClaim memory claim) + function toMessageHash(QualifiedSplitBatchClaim calldata claim) internal view returns (bytes32 messageHash, bytes32 qualificationMessageHash) { - // TODO: make this more efficient especially once using calldata - uint256[2][] memory idsAndAmounts = new uint256[2][](claim.claims.length); - for (uint256 i = 0; i < claim.claims.length; ++i) { - idsAndAmounts[i] = [claim.claims[i].id, claim.claims[i].allocatedAmount]; - } - bytes32 idsAndAmountsHash = keccak256(abi.encodePacked(idsAndAmounts)); - - assembly ("memory-safe") { - let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied. - - // TODO: calldatacopy this whole chunk at once as part of calldata implementation - let sponsor := mload(claim) - let expires := mload(add(claim, 0x20)) - let nonce := mload(add(claim, 0x40)) - - let id := mload(add(claim, 0x60)) - let amount := mload(add(claim, 0x80)) - - mstore(m, BATCH_COMPACT_TYPEHASH) - mstore(add(m, 0x20), sponsor) - mstore(add(m, 0x40), expires) - mstore(add(m, 0x60), nonce) - mstore(add(m, 0x80), caller()) // arbiter: msg.sender - mstore(add(m, 0xa0), idsAndAmountsHash) - messageHash := keccak256(m, 0xc0) - } + messageHash = _usingQualifiedSplitBatchClaim(_toSplitBatchMessageHash)(claim, claim.claims); // TODO: optimize once we're using calldata qualificationMessageHash = keccak256( @@ -666,50 +673,14 @@ library HashLib { ); } - function toMessageHash(QualifiedSplitBatchClaimWithWitness memory claim) + function toMessageHash(QualifiedSplitBatchClaimWithWitness calldata claim) internal view returns (bytes32 messageHash, bytes32 qualificationMessageHash) { - // derive the typehash (TODO: make this more efficient especially once using calldata) - bytes32 typehash = keccak256( - abi.encodePacked( - BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE, - BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO, - BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE, - BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR, - claim.witnessTypestring - ) + messageHash = _usingQualifiedSplitBatchClaimWithWitness(_toSplitBatchMessageHashWithWitness)( + claim, claim.claims ); - bytes32 witness = claim.witness; - - // TODO: make this more efficient especially once using calldata - uint256[2][] memory idsAndAmounts = new uint256[2][](claim.claims.length); - for (uint256 i = 0; i < claim.claims.length; ++i) { - idsAndAmounts[i] = [claim.claims[i].id, claim.claims[i].allocatedAmount]; - } - bytes32 idsAndAmountsHash = keccak256(abi.encodePacked(idsAndAmounts)); - - assembly ("memory-safe") { - let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied. - - // TODO: calldatacopy this whole chunk at once as part of calldata implementation - let sponsor := mload(claim) - let expires := mload(add(claim, 0x20)) - let nonce := mload(add(claim, 0x40)) - - let id := mload(add(claim, 0x60)) - let amount := mload(add(claim, 0x80)) - - mstore(m, typehash) - mstore(add(m, 0x20), sponsor) - mstore(add(m, 0x40), expires) - mstore(add(m, 0x60), nonce) - mstore(add(m, 0x80), caller()) // arbiter: msg.sender - mstore(add(m, 0xa0), idsAndAmountsHash) - mstore(add(m, 0xc0), witness) - messageHash := keccak256(m, 0xe0) - } // TODO: optimize once we're using calldata qualificationMessageHash = keccak256( diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 8c9fcf7..2c65318 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -29,7 +29,9 @@ import { BatchClaimWithWitness, QualifiedBatchClaimWithWitness, SplitBatchClaim, - SplitBatchClaimWithWitness + SplitBatchClaimWithWitness, + QualifiedSplitBatchClaim, + QualifiedSplitBatchClaimWithWitness } from "../src/types/BatchClaims.sol"; import { @@ -2462,4 +2464,277 @@ contract TheCompactTest is Test { assertEq(theCompact.balanceOf(recipientOne, anotherId), anotherAmount); assertEq(theCompact.balanceOf(recipientTwo, aThirdId), aThirdAmount); } + + function test_qualifiedSplitBatchClaim() public { + uint256 amount = 1e18; + uint256 anotherAmount = 1e18; + uint256 aThirdAmount = 1e18; + uint256 nonce = 0; + uint256 expires = block.timestamp + 1000; + address arbiter = 0x2222222222222222222222222222222222222222; + + address recipientOne = 0x1111111111111111111111111111111111111111; + address recipientTwo = 0x3333333333333333333333333333333333333333; + uint256 amountOne = 4e17; + uint256 amountTwo = 6e17; + + vm.prank(allocator); + theCompact.__register(allocator, ""); + + vm.startPrank(swapper); + uint256 id = theCompact.deposit{ value: amount }( + allocator, ResetPeriod.TenMinutes, Scope.Multichain, swapper + ); + + uint256 anotherId = theCompact.deposit( + address(token), + allocator, + ResetPeriod.TenMinutes, + Scope.Multichain, + anotherAmount, + swapper + ); + assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount); + + uint256 aThirdId = theCompact.deposit( + address(anotherToken), + allocator, + ResetPeriod.TenMinutes, + Scope.Multichain, + aThirdAmount, + swapper + ); + assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount); + + vm.stopPrank(); + + assertEq(theCompact.balanceOf(swapper, id), amount); + assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount); + assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount); + + uint256[2][] memory idsAndAmounts = new uint256[2][](3); + idsAndAmounts[0] = [id, amount]; + idsAndAmounts[1] = [anotherId, anotherAmount]; + idsAndAmounts[2] = [aThirdId, aThirdAmount]; + + bytes32 claimHash = keccak256( + abi.encode( + keccak256( + "BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)" + ), + arbiter, + swapper, + nonce, + expires, + keccak256(abi.encodePacked(idsAndAmounts)) + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash)); + + (bytes32 r, bytes32 vs) = vm.signCompact(swapperPrivateKey, digest); + bytes memory sponsorSignature = abi.encodePacked(r, vs); + + bytes32 qualificationTypehash = + keccak256("ExampleQualifiedClaim(bytes32 claimHash,uint256 qualifiedClaimArgument)"); + + uint256 qualifiedClaimArgument = 123; + bytes memory qualificationPayload = abi.encode(qualifiedClaimArgument); + + bytes32 qualifiedClaimHash = + keccak256(abi.encode(qualificationTypehash, claimHash, qualifiedClaimArgument)); + + digest = keccak256( + abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), qualifiedClaimHash) + ); + + (r, vs) = vm.signCompact(allocatorPrivateKey, digest); + bytes memory allocatorSignature = abi.encodePacked(r, vs); + + SplitBatchClaimComponent[] memory claims = new SplitBatchClaimComponent[](3); + SplitComponent[] memory portions = new SplitComponent[](2); + portions[0] = SplitComponent({ claimant: recipientOne, amount: amountOne }); + portions[1] = SplitComponent({ claimant: recipientTwo, amount: amountTwo }); + claims[0] = + SplitBatchClaimComponent({ id: id, allocatedAmount: amount, portions: portions }); + SplitComponent[] memory anotherPortion = new SplitComponent[](1); + anotherPortion[0] = SplitComponent({ claimant: recipientOne, amount: anotherAmount }); + claims[1] = SplitBatchClaimComponent({ + id: anotherId, + allocatedAmount: anotherAmount, + portions: anotherPortion + }); + SplitComponent[] memory aThirdPortion = new SplitComponent[](1); + aThirdPortion[0] = SplitComponent({ claimant: recipientTwo, amount: aThirdAmount }); + claims[2] = SplitBatchClaimComponent({ + id: aThirdId, + allocatedAmount: aThirdAmount, + portions: aThirdPortion + }); + + QualifiedSplitBatchClaim memory claim = QualifiedSplitBatchClaim( + allocatorSignature, + sponsorSignature, + swapper, + nonce, + expires, + qualificationTypehash, + qualificationPayload, + claims + ); + + vm.prank(arbiter); + (bool status) = theCompact.claim(claim); + assert(status); + + assertEq(address(theCompact).balance, amount); + assertEq(token.balanceOf(address(theCompact)), anotherAmount); + assertEq(anotherToken.balanceOf(address(theCompact)), aThirdAmount); + + assertEq(theCompact.balanceOf(recipientOne, id), amountOne); + assertEq(theCompact.balanceOf(recipientTwo, id), amountTwo); + assertEq(theCompact.balanceOf(recipientOne, anotherId), anotherAmount); + assertEq(theCompact.balanceOf(recipientTwo, aThirdId), aThirdAmount); + } + + function test_qualifiedSplitBatchClaimWithWitness() public { + uint256 amount = 1e18; + uint256 anotherAmount = 1e18; + uint256 aThirdAmount = 1e18; + uint256 nonce = 0; + uint256 expires = block.timestamp + 1000; + address arbiter = 0x2222222222222222222222222222222222222222; + + address recipientOne = 0x1111111111111111111111111111111111111111; + address recipientTwo = 0x3333333333333333333333333333333333333333; + uint256 amountOne = 4e17; + uint256 amountTwo = 6e17; + + vm.prank(allocator); + theCompact.__register(allocator, ""); + + vm.startPrank(swapper); + uint256 id = theCompact.deposit{ value: amount }( + allocator, ResetPeriod.TenMinutes, Scope.Multichain, swapper + ); + + uint256 anotherId = theCompact.deposit( + address(token), + allocator, + ResetPeriod.TenMinutes, + Scope.Multichain, + anotherAmount, + swapper + ); + assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount); + + uint256 aThirdId = theCompact.deposit( + address(anotherToken), + allocator, + ResetPeriod.TenMinutes, + Scope.Multichain, + aThirdAmount, + swapper + ); + assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount); + + vm.stopPrank(); + + assertEq(theCompact.balanceOf(swapper, id), amount); + assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount); + assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount); + + uint256[2][] memory idsAndAmounts = new uint256[2][](3); + idsAndAmounts[0] = [id, amount]; + idsAndAmounts[1] = [anotherId, anotherAmount]; + idsAndAmounts[2] = [aThirdId, aThirdAmount]; + + string memory witnessTypestring = "Witness witness)Witness(uint256 witnessArgument)"; + uint256 witnessArgument = 234; + bytes32 witness = keccak256(abi.encode(witnessArgument)); + + bytes32 claimHash = keccak256( + abi.encode( + keccak256( + "BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts,Witness witness)Witness(uint256 witnessArgument)" + ), + arbiter, + swapper, + nonce, + expires, + keccak256(abi.encodePacked(idsAndAmounts)), + witness + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash)); + + (bytes32 r, bytes32 vs) = vm.signCompact(swapperPrivateKey, digest); + bytes memory sponsorSignature = abi.encodePacked(r, vs); + + bytes32 qualificationTypehash = + keccak256("ExampleQualifiedClaim(bytes32 claimHash,uint256 qualifiedClaimArgument)"); + + uint256 qualifiedClaimArgument = 123; + bytes memory qualificationPayload = abi.encode(qualifiedClaimArgument); + + bytes32 qualifiedClaimHash = + keccak256(abi.encode(qualificationTypehash, claimHash, qualifiedClaimArgument)); + + digest = keccak256( + abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), qualifiedClaimHash) + ); + + (r, vs) = vm.signCompact(allocatorPrivateKey, digest); + bytes memory allocatorSignature = abi.encodePacked(r, vs); + + SplitBatchClaimComponent[] memory claims = new SplitBatchClaimComponent[](3); + SplitComponent[] memory portions = new SplitComponent[](2); + portions[0] = SplitComponent({ claimant: recipientOne, amount: amountOne }); + portions[1] = SplitComponent({ claimant: recipientTwo, amount: amountTwo }); + claims[0] = + SplitBatchClaimComponent({ id: id, allocatedAmount: amount, portions: portions }); + SplitComponent[] memory anotherPortion = new SplitComponent[](1); + anotherPortion[0] = SplitComponent({ claimant: recipientOne, amount: anotherAmount }); + claims[1] = SplitBatchClaimComponent({ + id: anotherId, + allocatedAmount: anotherAmount, + portions: anotherPortion + }); + SplitComponent[] memory aThirdPortion = new SplitComponent[](1); + aThirdPortion[0] = SplitComponent({ claimant: recipientTwo, amount: aThirdAmount }); + claims[2] = SplitBatchClaimComponent({ + id: aThirdId, + allocatedAmount: aThirdAmount, + portions: aThirdPortion + }); + + QualifiedSplitBatchClaimWithWitness memory claim = QualifiedSplitBatchClaimWithWitness( + allocatorSignature, + sponsorSignature, + swapper, + nonce, + expires, + witness, + witnessTypestring, + qualificationTypehash, + qualificationPayload, + claims + ); + + vm.prank(arbiter); + (bool status) = theCompact.claim(claim); + assert(status); + + assertEq(address(theCompact).balance, amount); + assertEq(token.balanceOf(address(theCompact)), anotherAmount); + assertEq(anotherToken.balanceOf(address(theCompact)), aThirdAmount); + + assertEq(theCompact.balanceOf(recipientOne, id), amountOne); + assertEq(theCompact.balanceOf(recipientTwo, id), amountTwo); + assertEq(theCompact.balanceOf(recipientOne, anotherId), anotherAmount); + assertEq(theCompact.balanceOf(recipientTwo, aThirdId), aThirdAmount); + } }