Skip to content

Commit

Permalink
witness + qualified batch
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Oct 15, 2024
1 parent bccf647 commit 61f5b1e
Show file tree
Hide file tree
Showing 4 changed files with 390 additions and 85 deletions.
91 changes: 91 additions & 0 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,28 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
return _processQualifiedBatchClaim(claimPayload, _release);
}

function claim(BatchClaimWithWitness calldata claimPayload) external returns (bool) {
return _processBatchClaimWithWitness(claimPayload, _release);
}

function claimAndWithdraw(BatchClaimWithWitness calldata claimPayload)
external
returns (bool)
{
return _processBatchClaimWithWitness(claimPayload, _release);
}

function claim(QualifiedBatchClaimWithWitness calldata claimPayload) external returns (bool) {
return _processQualifiedBatchClaimWithWitness(claimPayload, _release);
}

function claimAndWithdraw(QualifiedBatchClaimWithWitness calldata claimPayload)
external
returns (bool)
{
return _processQualifiedBatchClaimWithWitness(claimPayload, _release);
}

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

Expand Down Expand Up @@ -656,6 +678,23 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
);
}

function _notExpiredAndWithValidSignaturesBatchWithWitness(
BatchClaimWithWitness calldata claimPayload
) internal returns (bytes32 messageHash, uint96 allocatorId) {
claimPayload.expires.later();

allocatorId = claimPayload.claims[0].id.toAllocatorId();

messageHash = claimPayload.toMessageHash();
bytes32 domainSeparator = _INITIAL_DOMAIN_SEPARATOR.toLatest(_INITIAL_CHAIN_ID);
messageHash.signedBy(claimPayload.sponsor, claimPayload.sponsorSignature, domainSeparator);
messageHash.signedBy(
allocatorId.fromRegisteredAllocatorIdWithConsumed(claimPayload.nonce),
claimPayload.allocatorSignature,
domainSeparator
);
}

// NOTE: this function expects that there's at least one array element
function _notExpiredAndWithValidSignaturesQualifiedBatch(
QualifiedBatchClaim calldata claimPayload
Expand All @@ -675,6 +714,24 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
);
}

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

function _notExpiredAndWithValidSignaturesQualified(QualifiedClaim calldata claimPayload)
internal
returns (bytes32 messageHash)
Expand Down Expand Up @@ -1121,6 +1178,40 @@ contract TheCompact is ITheCompact, ERC6909, Extsload {
);
}

function _processBatchClaimWithWitness(
BatchClaimWithWitness calldata batchClaim,
function(address, address, uint256, uint256) internal returns (bool) operation
) internal returns (bool) {
(bytes32 messageHash, uint96 allocatorId) =
_notExpiredAndWithValidSignaturesBatchWithWitness(batchClaim);

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

function _processQualifiedBatchClaimWithWitness(
QualifiedBatchClaimWithWitness calldata batchClaim,
function(address, address, uint256, uint256) internal returns (bool) operation
) internal returns (bool) {
(bytes32 messageHash, uint96 allocatorId) =
_notExpiredAndWithValidSignaturesQualifiedBatchWithWitness(batchClaim);

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

function _processBatchPermit2Deposits(
bool firstUnderlyingTokenIsNative,
address recipient,
Expand Down
126 changes: 47 additions & 79 deletions src/lib/HashLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,32 @@ library HashLib {
}
}

function _toBatchMessageHashWithWitness(
BatchClaimWithWitness calldata claim,
BatchClaimComponent[] calldata claims
) internal view returns (bytes32 messageHash) {
bytes32 idsAndAmountsHash = toIdsAndAmountsHash(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 All @@ -399,6 +425,22 @@ library HashLib {
}
}

function _usingQualifiedBatchClaimWithWitness(
function(BatchClaimWithWitness calldata, BatchClaimComponent[] calldata) internal view returns (bytes32)
fnIn
)
internal
pure
returns (
function(QualifiedBatchClaimWithWitness calldata, BatchClaimComponent[] calldata) internal view returns (bytes32)
fnOut
)
{
assembly {
fnOut := fnIn
}
}

function toMessageHash(QualifiedBatchClaim calldata claim)
internal
view
Expand All @@ -412,96 +454,22 @@ library HashLib {
);
}

function toMessageHash(BatchClaimWithWitness memory claim)
function toMessageHash(BatchClaimWithWitness calldata 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)
}
return _toBatchMessageHashWithWitness(claim, claim.claims);
}

function toMessageHash(QualifiedBatchClaimWithWitness memory claim)
function toMessageHash(QualifiedBatchClaimWithWitness 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 = _usingQualifiedBatchClaimWithWitness(_toBatchMessageHashWithWitness)(
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(
Expand Down
13 changes: 8 additions & 5 deletions src/types/EIP712Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,21 @@ bytes32 constant BATCH_COMPACT_TYPEHASH =

// abi.decode(bytes("BatchCompact(address arbiter,add"), (bytes32))
bytes32 constant BATCH_COMPACT_TYPESTRING_FRAGMENT_ONE =
abi.decode(bytes("BatchCompact(address arbiter,add"), (bytes32));
0x4261746368436f6d70616374286164647265737320617262697465722c616464;
//abi.decode(bytes("BatchCompact(address arbiter,add"), (bytes32));

// abi.decode(bytes("ress sponsor,uint256 nonce,uint2"), (bytes32))
bytes32 constant BATCH_COMPACT_TYPESTRING_FRAGMENT_TWO =
abi.decode(bytes("ress sponsor,uint256 nonce,uint2"), (bytes32));
0x726573732073706f6e736f722c75696e74323536206e6f6e63652c75696e7432;
//abi.decode(bytes("ress sponsor,uint256 nonce,uint2"), (bytes32));

// abi.decode(bytes("56 expires,uint256[2][] idsAndAm"), (bytes32))
bytes32 constant BATCH_COMPACT_TYPESTRING_FRAGMENT_THREE =
abi.decode(bytes("56 expires,uint256[2][] idsAndAm"), (bytes32));
0x353620657870697265732c75696e743235365b325d5b5d20696473416e64416d;
//abi.decode(bytes("56 expires,uint256[2][] idsAndAm"), (bytes32));

// abi.decode(bytes("ounts,"), (bytes6))
bytes6 constant BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR = 0x6f756e74732c;
// uint48(abi.decode(bytes("ounts,"), (bytes6)))
uint48 constant BATCH_COMPACT_TYPESTRING_FRAGMENT_FOUR = 0x6f756e74732c;

// A multichain compact can declare tokens and amounts to allocate from multiple chains,
// each designated by their chainId. Any allocated tokens must designate the Multichain
Expand Down
Loading

0 comments on commit 61f5b1e

Please sign in to comment.