Skip to content

Commit

Permalink
add split batch
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Oct 15, 2024
1 parent fd617e8 commit f0e25cf
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 41 deletions.
77 changes: 74 additions & 3 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import {
SplitComponent,
TransferComponent,
SplitByIdComponent,
BatchClaimComponent
BatchClaimComponent,
SplitBatchClaimComponent
} from "./types/Components.sol";

import { IAllocator } from "./interfaces/IAllocator.sol";
Expand Down Expand Up @@ -500,6 +501,14 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
return _processQualifiedBatchClaimWithWitness(claimPayload, _release);
}

function claim(SplitBatchClaim calldata claimPayload) external returns (bool) {
return _processSplitBatchClaim(claimPayload, _release);
}

function claimAndWithdraw(SplitBatchClaim calldata claimPayload) external returns (bool) {
return _processSplitBatchClaim(claimPayload, _release);
}

function enableForcedWithdrawal(uint256 id) external returns (uint256 withdrawableAt) {
withdrawableAt = block.timestamp + id.toResetPeriod().toSeconds();

Expand Down Expand Up @@ -808,6 +817,16 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
}
}

function _usingSplitBatchClaim(function(bytes32, Claim calldata, address) internal view fnIn)
internal
pure
returns (function(bytes32, SplitBatchClaim calldata, address) internal view fnOut)
{
assembly {
fnOut := fnIn
}
}

function _usingBatchClaimWithWitness(
function(bytes32, Claim calldata, address) internal view fnIn
)
Expand Down Expand Up @@ -1175,12 +1194,46 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
return true;
}

function _verifyAndProcessSplitBatchComponents(
uint96 allocatorId,
address sponsor,
bytes32 messageHash,
SplitBatchClaimComponent[] calldata claims,
function(address, address, uint256, uint256) internal returns (bool) operation
) internal returns (bool) {
uint256 totalClaims = claims.length;
uint256 errorBuffer = (totalClaims == 0).asUint256();

unchecked {
for (uint256 i = 0; i < totalClaims; ++i) {
SplitBatchClaimComponent calldata claim = claims[i];
errorBuffer |= (claim.id.toAllocatorId() != allocatorId).asUint256();

_verifyAndProcessSplitComponents(
sponsor, messageHash, claim.id, claim.allocatedAmount, claim.portions, operation
);
}
}

if (errorBuffer.asBool()) {
revert InvalidBatchAllocation();
}

return true;
}

function _processBatchClaim(
BatchClaim calldata batchClaim,
function(address, address, uint256, uint256) internal returns (bool) operation
) internal returns (bool) {
(bytes32 messageHash, uint96 allocatorId) =
_notExpiredAndWithValidSignaturesBatch(batchClaim);
bytes32 messageHash = batchClaim.toMessageHash();
uint96 allocatorId = batchClaim.claims[0].id.toAllocatorId();

_usingBatchClaim(_notExpiredAndWithValidSignatures)(
messageHash,
batchClaim,
allocatorId.fromRegisteredAllocatorIdWithConsumed(batchClaim.nonce)
);

return _verifyAndProcessBatchComponents(
allocatorId,
Expand All @@ -1192,6 +1245,24 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
);
}

function _processSplitBatchClaim(
SplitBatchClaim calldata batchClaim,
function(address, address, uint256, uint256) internal returns (bool) operation
) internal returns (bool) {
bytes32 messageHash = batchClaim.toMessageHash();
uint96 allocatorId = batchClaim.claims[0].id.toAllocatorId();

_usingSplitBatchClaim(_notExpiredAndWithValidSignatures)(
messageHash,
batchClaim,
allocatorId.fromRegisteredAllocatorIdWithConsumed(batchClaim.nonce)
);

return _verifyAndProcessSplitBatchComponents(
allocatorId, batchClaim.sponsor, messageHash, batchClaim.claims, operation
);
}

function _processQualifiedBatchClaim(
QualifiedBatchClaim calldata batchClaim,
function(address, address, uint256, uint256) internal returns (bool) operation
Expand Down
75 changes: 39 additions & 36 deletions src/lib/HashLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import {
ExogenousQualifiedSplitMultichainClaimWithWitness
} from "../types/MultichainClaims.sol";

import { BatchClaimComponent } from "../types/Components.sol";
import { BatchClaimComponent, SplitBatchClaimComponent } from "../types/Components.sol";

import { ResetPeriod } from "../types/ResetPeriod.sol";
import { Scope } from "../types/Scope.sol";
Expand Down Expand Up @@ -405,6 +405,19 @@ library HashLib {
idsAndAmountsHash = keccak256(abi.encodePacked(idsAndAmounts));
}

function toSplitIdsAndAmountsHash(SplitBatchClaimComponent[] calldata claims)
internal
pure
returns (bytes32 idsAndAmountsHash)
{
// TODO: make this more efficient ASAP
uint256[2][] memory idsAndAmounts = new uint256[2][](claims.length);
for (uint256 i = 0; i < claims.length; ++i) {
idsAndAmounts[i] = [claims[i].id, claims[i].allocatedAmount];
}
idsAndAmountsHash = keccak256(abi.encodePacked(idsAndAmounts));
}

function toMessageHash(BatchClaim calldata claim) internal view returns (bytes32 messageHash) {
return _deriveBatchMessageHash(claim, claim.claims);
}
Expand All @@ -426,6 +439,31 @@ library HashLib {
}
}

function toMessageHash(SplitBatchClaim calldata claim)
internal
view
returns (bytes32 messageHash)
{
return _deriveSplitBatchMessageHash(claim, claim.claims);
}

function _deriveSplitBatchMessageHash(
SplitBatchClaim calldata claim,
SplitBatchClaimComponent[] calldata claims
) internal view returns (bytes32 messageHash) {
bytes32 idsAndAmountsHash = toSplitIdsAndAmountsHash(claims);

assembly ("memory-safe") {
let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied.

mstore(m, BATCH_COMPACT_TYPEHASH)
mstore(add(m, 0x20), caller()) // arbiter: msg.sender
calldatacopy(add(m, 0x40), add(claim, 0x40), 0x60) // sponsor, nonce, expires
mstore(add(m, 0xa0), idsAndAmountsHash)
messageHash := keccak256(m, 0xc0)
}
}

function _toBatchMessageHashWithWitness(
BatchClaimWithWitness calldata claim,
BatchClaimComponent[] calldata claims
Expand Down Expand Up @@ -556,41 +594,6 @@ library HashLib {
return _usingSplitClaimWithWitness(toMessageHashWithWitness)(claim, 0);
}

// TODO: all these SplitBatch can just use a function cast to leverage
// BatchClaim.toMessageHash logic as the structs have the exact same offsets
function toMessageHash(SplitBatchClaim memory claim)
internal
view
returns (bytes32 messageHash)
{
// 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)
}
}

function toMessageHash(SplitBatchClaimWithWitness memory claim)
internal
view
Expand Down
118 changes: 116 additions & 2 deletions test/TheCompact.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ import {
BatchClaim,
QualifiedBatchClaim,
BatchClaimWithWitness,
QualifiedBatchClaimWithWitness
QualifiedBatchClaimWithWitness,
SplitBatchClaim
} from "../src/types/BatchClaims.sol";

import {
SplitComponent,
TransferComponent,
SplitByIdComponent,
BatchClaimComponent
BatchClaimComponent,
SplitBatchClaimComponent
} from "../src/types/Components.sol";

interface EIP712 {
Expand Down Expand Up @@ -2222,4 +2224,116 @@ contract TheCompactTest is Test {
assertEq(theCompact.balanceOf(claimant, anotherId), anotherAmount);
assertEq(theCompact.balanceOf(claimant, aThirdId), aThirdAmount);
}

function test_splitBatchClaim() 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);

(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
});

SplitBatchClaim memory claim =
SplitBatchClaim(allocatorSignature, sponsorSignature, swapper, nonce, expires, 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);
}
}

0 comments on commit f0e25cf

Please sign in to comment.