Skip to content

Commit

Permalink
add split batch claim with witness
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Oct 15, 2024
1 parent f0e25cf commit cc2e95d
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 49 deletions.
43 changes: 43 additions & 0 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,17 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
return _processSplitBatchClaim(claimPayload, _release);
}

function claim(SplitBatchClaimWithWitness calldata claimPayload) external returns (bool) {
return _processSplitBatchClaimWithWitness(claimPayload, _release);
}

function claimAndWithdraw(SplitBatchClaimWithWitness calldata claimPayload)
external
returns (bool)
{
return _processSplitBatchClaimWithWitness(claimPayload, _release);
}

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

Expand Down Expand Up @@ -827,6 +838,20 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
}
}

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

function _usingBatchClaimWithWitness(
function(bytes32, Claim calldata, address) internal view fnIn
)
Expand Down Expand Up @@ -1263,6 +1288,24 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
);
}

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

_usingSplitBatchClaimWithWitness(_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
84 changes: 36 additions & 48 deletions src/lib/HashLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,18 @@ library HashLib {
view
returns (bytes32 messageHash)
{
return _deriveSplitBatchMessageHash(claim, claim.claims);
return _toSplitBatchMessageHash(claim, claim.claims);
}

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

function _toSplitBatchMessageHash(
SplitBatchClaim calldata claim,
SplitBatchClaimComponent[] calldata claims
) internal view returns (bytes32 messageHash) {
Expand Down Expand Up @@ -490,6 +498,32 @@ library HashLib {
}
}

function _toSplitBatchMessageHashWithWitness(
SplitBatchClaimWithWitness 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.

// prepare full typestring
let witnessTypestringPtr := add(claim, calldataload(add(claim, 0xc0)))
let witnessTypestringLength := calldataload(witnessTypestringPtr)
mstore(m, BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE)
mstore(add(m, 0x20), BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO)
mstore(add(m, 0x46), BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR)
mstore(add(m, 0x40), BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE)
calldatacopy(add(m, 0x66), add(0x20, witnessTypestringPtr), witnessTypestringLength)
mstore(m, keccak256(m, add(0x66, witnessTypestringLength))) // 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)
mstore(add(m, 0xc0), calldataload(add(claim, 0xa0))) // witness
messageHash := keccak256(m, 0xe0)
}
}

function _usingQualifiedBatchClaim(
function(BatchClaim calldata, BatchClaimComponent[] calldata) internal view returns (bytes32)
fnIn
Expand Down Expand Up @@ -594,52 +628,6 @@ library HashLib {
return _usingSplitClaimWithWitness(toMessageHashWithWitness)(claim, 0);
}

function toMessageHash(SplitBatchClaimWithWitness memory claim)
internal
view
returns (bytes32 messageHash)
{
// 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
)
);
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)
}
}

function toMessageHash(QualifiedSplitBatchClaim memory claim)
internal
view
Expand Down
128 changes: 127 additions & 1 deletion test/TheCompact.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
QualifiedBatchClaim,
BatchClaimWithWitness,
QualifiedBatchClaimWithWitness,
SplitBatchClaim
SplitBatchClaim,
SplitBatchClaimWithWitness
} from "../src/types/BatchClaims.sol";

import {
Expand Down Expand Up @@ -2336,4 +2337,129 @@ contract TheCompactTest is Test {
assertEq(theCompact.balanceOf(recipientOne, anotherId), anotherAmount);
assertEq(theCompact.balanceOf(recipientTwo, aThirdId), aThirdAmount);
}

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

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

SplitBatchClaimWithWitness memory claim = SplitBatchClaimWithWitness(
allocatorSignature,
sponsorSignature,
swapper,
nonce,
expires,
witness,
witnessTypestring,
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 cc2e95d

Please sign in to comment.