Skip to content

Commit

Permalink
use expirations for registrations and optimize storage
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Oct 30, 2024
1 parent 579679e commit 311cb95
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 65 deletions.
72 changes: 36 additions & 36 deletions snapshots/TheCompactTest.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
{
"basicTransfer": "57297",
"basicTransfer": "57341",
"basicWithdrawal": "60321",
"batchClaim": "112336",
"batchClaimRegisteredWithDeposit": "112336",
"batchClaimRegisteredWithDepositWithWitness": "113049",
"batchClaimWithWitness": "113043",
"batchDepositAndRegisterViaPermit2": "221538",
"batchDepositAndRegisterWithWitnessViaPermit2": "221516",
"batchClaim": "112382",
"batchClaimRegisteredWithDeposit": "112382",
"batchClaimRegisteredWithDepositWithWitness": "113117",
"batchClaimWithWitness": "113111",
"batchDepositAndRegisterViaPermit2": "221918",
"batchDepositAndRegisterWithWitnessViaPermit2": "221896",
"batchTransfer": "82919",
"batchWithdrawal": "101290",
"claim": "57450",
"claimAndWithdraw": "73756",
"claimWithWitness": "59909",
"depositAndRegisterViaPermit2": "123906",
"depositBatchSingleERC20": "67814",
"depositBatchSingleNative": "28117",
"depositBatchViaPermit2NativeAndERC20": "129518",
"depositBatchViaPermit2SingleERC20": "104645",
"depositERC20AndURI": "67051",
"depositERC20Basic": "67080",
"batchWithdrawal": "101334",
"claim": "57533",
"claimAndWithdraw": "73824",
"claimWithWitness": "59977",
"depositAndRegisterViaPermit2": "124252",
"depositBatchSingleERC20": "67846",
"depositBatchSingleNative": "28149",
"depositBatchViaPermit2NativeAndERC20": "129562",
"depositBatchViaPermit2SingleERC20": "104689",
"depositERC20AndURI": "67095",
"depositERC20Basic": "67124",
"depositERC20ViaPermit2AndURI": "98277",
"depositETHAndURI": "26733",
"depositETHBasic": "28274",
"qualifiedBatchClaim": "113729",
"qualifiedBatchClaimWithWitness": "113172",
"qualifiedClaim": "60727",
"qualifiedClaimWithWitness": "59312",
"qualifiedSplitBatchClaim": "141275",
"qualifiedSplitBatchClaimWithWitness": "141246",
"qualifiedSplitClaim": "86999",
"qualifiedSplitClaimWithWitness": "87322",
"register": "24890",
"splitBatchClaim": "140762",
"splitBatchClaimWithWitness": "140699",
"splitBatchTransfer": "113576",
"depositETHAndURI": "26755",
"depositETHBasic": "28318",
"qualifiedBatchClaim": "113797",
"qualifiedBatchClaimWithWitness": "113240",
"qualifiedClaim": "60795",
"qualifiedClaimWithWitness": "59336",
"qualifiedSplitBatchClaim": "141343",
"qualifiedSplitBatchClaimWithWitness": "141314",
"qualifiedSplitClaim": "87067",
"qualifiedSplitClaimWithWitness": "87390",
"register": "25357",
"splitBatchClaim": "140830",
"splitBatchClaimWithWitness": "140767",
"splitBatchTransfer": "113620",
"splitBatchWithdrawal": "142828",
"splitClaim": "86925",
"splitClaimWithWitness": "86379",
"splitTransfer": "83209",
"splitWithdrawal": "94141"
"splitClaim": "86993",
"splitClaimWithWitness": "86447",
"splitTransfer": "83253",
"splitWithdrawal": "94163"
}
90 changes: 66 additions & 24 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,17 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
/// @dev `keccak256(bytes("Claim(address,address,address,bytes32)"))`.
uint256 private constant _CLAIM_EVENT_SIGNATURE = 0x770c32a2314b700d6239ee35ba23a9690f2fceb93a55d8c753e953059b3b18d4;

/// @dev `keccak256(bytes("CompactRegistered(address,bytes32,bytes32,uint256)"))`.
uint256 private constant _COMPACT_REGISTERED_SIGNATURE = 0xf78a2f33ff80ef4391f7449c748dc2d577a62cd645108f4f4069f4a7e0635b6a;

uint32 private constant _ATTEST_SELECTOR = 0x1a808f91;
uint32 private constant _PERMIT_WITNESS_TRANSFER_FROM_SELECTOR = 0x137c29fe;
uint32 private constant _BATCH_PERMIT_WITNESS_TRANSFER_FROM_SELECTOR = 0xfe8ec1a7;

// Rage-quit functionality (TODO: optimize storage layout)
mapping(address => mapping(uint256 => uint256)) private _cutoffTime;

// TODO: optimize
mapping(address => mapping(bytes32 => bytes32)) private _registeredClaimHashes;
// slot: keccak256(_ACTIVE_REGISTRATIONS_SCOPE ++ sponsor ++ claimHash ++ typehash) => expires
uint256 private constant _ACTIVE_REGISTRATIONS_SCOPE = 0x68a30dd0;

uint256 private immutable _INITIAL_CHAIN_ID;
bytes32 private immutable _INITIAL_DOMAIN_SEPARATOR;
Expand All @@ -258,7 +260,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {

_deposit(msg.sender, id, msg.value);

_register(msg.sender, claimHash, typehash);
_register(msg.sender, claimHash, typehash, 0x258);
}

function deposit(address token, address allocator, uint256 amount) external returns (uint256) {
Expand All @@ -268,7 +270,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
function depositAndRegister(address token, address allocator, uint256 amount, bytes32 claimHash, bytes32 typehash) external returns (uint256 id) {
id = _performBasicERC20Deposit(token, allocator, amount, msg.sender);

_register(msg.sender, claimHash, typehash);
_register(msg.sender, claimHash, typehash, 0x258);
}

function _performBasicERC20Deposit(address token, address allocator, uint256 amount, address recipient) internal returns (uint256 id) {
Expand All @@ -294,13 +296,17 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
}

function deposit(uint256[2][] calldata idsAndAmounts, address recipient) external payable returns (bool) {
return _processBatchDeposit(idsAndAmounts, recipient);
_processBatchDeposit(idsAndAmounts, recipient);

return true;
}

function depositAndRegister(uint256[2][] calldata idsAndAmounts, bytes32[2][] calldata claimHashesAndTypehashes) external payable returns (bool) {
_registerFor(msg.sender, claimHashesAndTypehashes);
function depositAndRegister(uint256[2][] calldata idsAndAmounts, bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) external payable returns (bool) {
_processBatchDeposit(idsAndAmounts, msg.sender);

return _processBatchDeposit(idsAndAmounts, msg.sender);
_registerBatch(claimHashesAndTypehashes, duration);

return true;
}

function deposit(
Expand Down Expand Up @@ -376,7 +382,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {

_checkBalanceAndDeposit(token, depositor, id, initialBalance);

_register(depositor, claimHash, compactTypehash);
_register(depositor, claimHash, compactTypehash, resetPeriod.toSeconds());

_clearTstorish(_REENTRANCY_GUARD_SLOT);

Expand Down Expand Up @@ -587,7 +593,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {

_verifyBalancesAndPerformDeposits(ids, permitted, initialTokenBalances, depositor, firstUnderlyingTokenIsNative);

_register(depositor, claimHash, compactTypehash);
_register(depositor, claimHash, compactTypehash, resetPeriod.toSeconds());
}

function allocatedTransfer(BasicTransfer calldata transfer) external returns (bool) {
Expand Down Expand Up @@ -1050,26 +1056,64 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
return _withdraw(msg.sender, recipient, id, amount);
}

function register(bytes32 claimHash, bytes32 typehash) external returns (bool) {
_register(msg.sender, claimHash, typehash);
function register(bytes32 claimHash, bytes32 typehash, uint256 duration) external returns (bool) {
_register(msg.sender, claimHash, typehash, duration);
return true;
}

function _register(address sponsor, bytes32 claimHash, bytes32 typehash) internal {
_registeredClaimHashes[sponsor][claimHash] = typehash;
emit CompactRegistered(sponsor, claimHash, typehash);
function _getRegistrationStatus(address sponsor, bytes32 claimHash, bytes32 typehash) internal view returns (uint256 expires) {
assembly ("memory-safe") {
let m := mload(0x40)
mstore(add(m, 0x14), sponsor)
mstore(m, _ACTIVE_REGISTRATIONS_SCOPE)
mstore(add(m, 0x34), claimHash)
mstore(add(m, 0x54), typehash)
expires := sload(keccak256(add(m, 0x1c), 0x58))
}
}

function _hasNoActiveRegistration(address sponsor, bytes32 claimHash, bytes32 typehash) internal view returns (bool) {
return _getRegistrationStatus(sponsor, claimHash, typehash) <= block.timestamp;
}

function getRegistrationStatus(address sponsor, bytes32 claimHash, bytes32 typehash) external view returns (bool isActive, uint256 expires) {
expires = _getRegistrationStatus(sponsor, claimHash, typehash);
isActive = expires > block.timestamp;
}

function register(bytes32[2][] calldata claimHashesAndTypehashes) external returns (bool) {
return _registerFor(msg.sender, claimHashesAndTypehashes);
function _register(address sponsor, bytes32 claimHash, bytes32 typehash, uint256 duration) internal {
assembly ("memory-safe") {
let m := mload(0x40)
mstore(add(m, 0x14), sponsor)
mstore(m, _ACTIVE_REGISTRATIONS_SCOPE)
mstore(add(m, 0x34), claimHash)
mstore(add(m, 0x54), typehash)
let cutoffSlot := keccak256(add(m, 0x1c), 0x58)

let expires := add(timestamp(), duration)
if or(lt(expires, sload(cutoffSlot)), gt(duration, 0x278d00)) {
// revert InvalidRegistrationDuration(uint256 duration)
mstore(0, 0x1f9a96f4)
mstore(0x20, duration)
revert(0x1c, 0x24)
}

sstore(cutoffSlot, expires)
mstore(add(m, 0x74), expires)
log2(add(m, 0x34), 0x60, _COMPACT_REGISTERED_SIGNATURE, shr(0x60, shl(0x60, sponsor)))
}
}

function register(bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) external returns (bool) {
return _registerBatch(claimHashesAndTypehashes, duration);
}

function _registerFor(address sponsor, bytes32[2][] calldata claimHashesAndTypehashes) internal returns (bool) {
function _registerBatch(bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) internal returns (bool) {
unchecked {
uint256 totalClaimHashes = claimHashesAndTypehashes.length;
for (uint256 i = 0; i < totalClaimHashes; ++i) {
bytes32[2] calldata claimHashAndTypehash = claimHashesAndTypehashes[i];
_register(sponsor, claimHashAndTypehash[0], claimHashAndTypehash[1]);
_register(msg.sender, claimHashAndTypehash[0], claimHashAndTypehash[1], duration);
}
}

Expand Down Expand Up @@ -1152,7 +1196,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
}
}

function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal returns (bool) {
function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal {
_setTstorish(_REENTRANCY_GUARD_SLOT, 1);
uint256 totalIds = idsAndAmounts.length;
bool firstUnderlyingTokenIsNative;
Expand Down Expand Up @@ -1198,8 +1242,6 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
}

_clearTstorish(_REENTRANCY_GUARD_SLOT);

return true;
}

function _notExpiredAndSignedByAllocator(bytes32 messageHash, address allocator, BasicTransfer calldata transferPayload) internal {
Expand Down Expand Up @@ -1391,7 +1433,7 @@ contract TheCompact is ITheCompact, ITheCompactClaims, ERC6909, Tstorish {
sponsorDomainSeparator := add(sponsorDomainSeparator, mul(iszero(sponsorDomainSeparator), domainSeparator))
}

if ((sponsorDomainSeparator != domainSeparator).or(sponsorSignature.length != 0) || _registeredClaimHashes[sponsor][messageHash] != typehash) {
if ((sponsorDomainSeparator != domainSeparator).or(sponsorSignature.length != 0) || _hasNoActiveRegistration(sponsor, messageHash, typehash)) {
messageHash.signedBy(sponsor, sponsorSignature, sponsorDomainSeparator);
}
qualificationMessageHash.signedBy(allocator, allocatorSignature, domainSeparator);
Expand Down
10 changes: 6 additions & 4 deletions src/interfaces/ITheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface ITheCompact {
event Claim(address indexed sponsor, address indexed allocator, address indexed arbiter, bytes32 claimHash);
event ForcedWithdrawalEnabled(address indexed account, uint256 indexed id, uint256 withdrawableAt);
event ForcedWithdrawalDisabled(address indexed account, uint256 indexed id);
event CompactRegistered(address indexed sponsor, bytes32 claimHash, bytes32 typehash);
event CompactRegistered(address indexed sponsor, bytes32 claimHash, bytes32 typehash, uint256 expires);
event AllocatorRegistered(uint96 allocatorId, address allocator);

error InvalidToken(address token);
Expand All @@ -38,6 +38,8 @@ interface ITheCompact {
error InvalidScope(uint256 id);
error InvalidDepositTokenOrdering();
error InvalidDepositBalanceChange();
error Permit2CallFailed();
error InvalidRegistrationDuration(uint256 duration);

function deposit(address allocator) external payable returns (uint256 id);

Expand All @@ -53,7 +55,7 @@ interface ITheCompact {

function deposit(uint256[2][] calldata idsAndAmounts, address recipient) external payable returns (bool);

function depositAndRegister(uint256[2][] calldata idsAndAmounts, bytes32[2][] calldata claimHashesAndTypehashes) external payable returns (bool);
function depositAndRegister(uint256[2][] calldata idsAndAmounts, bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) external payable returns (bool);

function deposit(
address token,
Expand Down Expand Up @@ -105,9 +107,9 @@ interface ITheCompact {

function forcedWithdrawal(uint256 id, address recipient, uint256 amount) external returns (bool);

function register(bytes32 claimHash, bytes32 typehash) external returns (bool);
function register(bytes32 claimHash, bytes32 typehash, uint256 duration) external returns (bool);

function register(bytes32[2][] calldata claimHashesAndTypehashes) external returns (bool);
function register(bytes32[2][] calldata claimHashesAndTypehashes, uint256 duration) external returns (bool);

function consume(uint256[] calldata nonces) external returns (bool);

Expand Down
1 change: 1 addition & 0 deletions src/lib/IdLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ library IdLib {
using IdLib for uint96;
using IdLib for uint256;
using IdLib for address;
using IdLib for ResetPeriod;
using MetadataLib for Lock;
using EfficiencyLib for bool;
using EfficiencyLib for uint8;
Expand Down
22 changes: 21 additions & 1 deletion test/TheCompact.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ contract TheCompactTest is Test {
vm.snapshotGasLastCall("depositAndRegisterViaPermit2");
assertEq(returnedId, id);

(bool isActive, uint256 expiresAt) = theCompact.getRegistrationStatus(swapper, claimHash, typehash);
assert(isActive);
assertEq(expiresAt, 0x258 + block.timestamp);

(address derivedToken, address derivedAllocator, ResetPeriod derivedResetPeriod, Scope derivedScope) = theCompact.getLockDetails(id);
assertEq(derivedToken, address(token));
assertEq(derivedAllocator, allocator);
Expand Down Expand Up @@ -1094,10 +1098,14 @@ contract TheCompactTest is Test {
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash));

vm.prank(swapper);
(bool status) = theCompact.register(claimHash, typehash);
(bool status) = theCompact.register(claimHash, typehash, 1000);
vm.snapshotGasLastCall("register");
assert(status);

(bool isActive, uint256 expiresAt) = theCompact.getRegistrationStatus(swapper, claimHash, typehash);
assert(isActive);
assertEq(expiresAt, block.timestamp + 1000);

bytes memory sponsorSignature = "";

(bytes32 r, bytes32 vs) = vm.signCompact(allocatorPrivateKey, digest);
Expand Down Expand Up @@ -1318,6 +1326,10 @@ contract TheCompactTest is Test {
vm.snapshotGasLastCall("depositAndRegisterViaPermit2");
assertEq(returnedId, id);

(bool isActive, uint256 expiresAt) = theCompact.getRegistrationStatus(swapper, claimHash, typehash);
assert(isActive);
assertEq(expiresAt, 0x258 + block.timestamp);

(address derivedToken, address derivedAllocator, ResetPeriod derivedResetPeriod, Scope derivedScope) = theCompact.getLockDetails(id);
assertEq(derivedToken, address(token));
assertEq(derivedAllocator, allocator);
Expand Down Expand Up @@ -1831,6 +1843,10 @@ contract TheCompactTest is Test {
assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount);
assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount);

(bool isActive, uint256 expiresAt) = theCompact.getRegistrationStatus(swapper, claimHash, typehash);
assert(isActive);
assertEq(expiresAt, 0x258 + block.timestamp);

claimHash = keccak256(
abi.encode(
keccak256("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts)"),
Expand Down Expand Up @@ -1965,6 +1981,10 @@ contract TheCompactTest is Test {
assertEq(theCompact.balanceOf(swapper, anotherId), anotherAmount);
assertEq(theCompact.balanceOf(swapper, aThirdId), aThirdAmount);

(bool isActive, uint256 expiresAt) = theCompact.getRegistrationStatus(swapper, claimHash, typehash);
assert(isActive);
assertEq(expiresAt, 0x258 + block.timestamp);

claimHash = keccak256(
abi.encode(
keccak256("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts,CompactWitness witness)CompactWitness(uint256 witnessArgument)"),
Expand Down

0 comments on commit 311cb95

Please sign in to comment.