Skip to content

Commit

Permalink
Allow registration for someone else
Browse files Browse the repository at this point in the history
  • Loading branch information
reednaa committed Jan 22, 2025
1 parent 7b67701 commit e21dc27
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 36 deletions.
71 changes: 36 additions & 35 deletions snapshots/TheCompactTest.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
{
"basicTransfer": "56867",
"basicTransfer": "56955",
"basicWithdrawal": "59992",
"batchClaim": "112512",
"batchClaimRegisteredWithDeposit": "112512",
"batchClaimRegisteredWithDepositWithWitness": "113271",
"batchClaimWithWitness": "113265",
"batchDepositAndRegisterViaPermit2": "222059",
"batchDepositAndRegisterWithWitnessViaPermit2": "222037",
"batchClaim": "112534",
"batchClaimRegisteredWithDeposit": "112534",
"batchClaimRegisteredWithDepositWithWitness": "113359",
"batchClaimWithWitness": "113353",
"batchDepositAndRegisterViaPermit2": "222176",
"batchDepositAndRegisterWithWitnessViaPermit2": "222154",
"batchTransfer": "81344",
"batchWithdrawal": "100093",
"claim": "56974",
"claimAndWithdraw": "73432",
"claimWithWitness": "59471",
"depositAndRegisterViaPermit2": "124429",
"depositBatchSingleERC20": "68050",
"depositBatchSingleNative": "28353",
"depositBatchViaPermit2NativeAndERC20": "129751",
"depositBatchViaPermit2SingleERC20": "104905",
"depositERC20AndURI": "67276",
"depositERC20Basic": "67302",
"batchWithdrawal": "100137",
"claim": "57062",
"claimAndWithdraw": "73520",
"claimWithWitness": "59493",
"depositAndRegisterViaPermit2": "124480",
"depositBatchSingleERC20": "68138",
"depositBatchSingleNative": "28441",
"depositBatchViaPermit2NativeAndERC20": "129839",
"depositBatchViaPermit2SingleERC20": "104993",
"depositERC20AndURI": "67320",
"depositERC20Basic": "67390",
"depositERC20ViaPermit2AndURI": "98471",
"depositETHAndURI": "26754",
"depositETHBasic": "28391",
"qualifiedBatchClaim": "114134",
"qualifiedBatchClaimWithWitness": "113672",
"qualifiedClaim": "60406",
"depositETHAndURI": "26776",
"depositETHBasic": "28479",
"depositRegisterFor": "529",
"qualifiedBatchClaim": "114222",
"qualifiedBatchClaimWithWitness": "113760",
"qualifiedClaim": "60450",
"qualifiedClaimWithWitness": "59098",
"qualifiedSplitBatchClaim": "141486",
"qualifiedSplitBatchClaimWithWitness": "141552",
"qualifiedSplitClaim": "86977",
"qualifiedSplitClaimWithWitness": "87452",
"register": "25379",
"splitBatchClaim": "140788",
"splitBatchClaimWithWitness": "140749",
"splitBatchTransfer": "110652",
"qualifiedSplitBatchClaim": "141574",
"qualifiedSplitBatchClaimWithWitness": "141640",
"qualifiedSplitClaim": "87065",
"qualifiedSplitClaimWithWitness": "87540",
"register": "25408",
"splitBatchClaim": "140876",
"splitBatchClaimWithWitness": "140837",
"splitBatchTransfer": "110696",
"splitBatchWithdrawal": "140361",
"splitClaim": "86751",
"splitClaimWithWitness": "86232",
"splitTransfer": "82482",
"splitWithdrawal": "93770"
"splitClaim": "86839",
"splitClaimWithWitness": "86320",
"splitTransfer": "82570",
"splitWithdrawal": "93858"
}
25 changes: 25 additions & 0 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ contract TheCompact is ITheCompact, ERC6909, TheCompactLogic {
_registerWithDefaults(claimHash, typehash);
}

function depositAndRegisterFor(address recipient, address token, address allocator, ResetPeriod resetPeriod, Scope scope, uint256 amount, address arbiter, uint256 nonce, uint256 expires) external returns (uint256 id) {
id = _performCustomERC20Deposit(token, allocator, resetPeriod, scope, amount, recipient);

_registerUsingClaim(recipient, id, amount, arbiter, nonce, expires);
}

function depositAndRegisterFor(address recipient, address token, address allocator, ResetPeriod resetPeriod, Scope scope, uint256 amount, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) external returns (uint256 id) {
id = _performCustomERC20Deposit(token, allocator, resetPeriod, scope, amount, recipient);

_registerUsingClaimWithWitness(recipient, id, amount, arbiter, nonce, expires, typehash, witness);
}

function deposit(address allocator, ResetPeriod resetPeriod, Scope scope, address recipient) external payable returns (uint256) {
return _performCustomNativeTokenDeposit(allocator, resetPeriod, scope, recipient);
}
Expand All @@ -65,6 +77,19 @@ contract TheCompact is ITheCompact, ERC6909, TheCompactLogic {
return _registerBatch(claimHashesAndTypehashes, duration);
}

function depositAndRegisterFor(address recipient, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires) external {
_processBatchDeposit(idsAndAmounts, recipient);

_registerUsingBatchClaim(recipient, idsAndAmounts, arbiter, nonce, expires);
}

function depositAndRegisterFor(address recipient, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) external {
_processBatchDeposit(idsAndAmounts, recipient);

_registerUsingBatchClaimWithWitness(recipient, idsAndAmounts, arbiter, nonce, expires, typehash, witness);
}


function deposit(
address token,
uint256, // amount
Expand Down
78 changes: 78 additions & 0 deletions src/lib/HashLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ library HashLib {
using EfficiencyLib for uint256;
using TransferFunctionCastLib for function(BatchTransfer calldata, uint256) internal view returns (bytes32);
using HashLib for uint256;
using HashLib for uint256[2][];
using HashLib for BatchTransfer;

/**
Expand Down Expand Up @@ -683,6 +684,10 @@ library HashLib {
}
}

function toIdsAndAmountsHash(uint256[2][] calldata idsAndAmounts) internal pure returns (bytes32 idsAndAmountsHash) {
idsAndAmountsHash = keccak256(abi.encodePacked(idsAndAmounts));
}

/**
* @notice Internal pure function for deriving the hash of the ids and amounts.
* @param claims An array of BatchClaimComponent structs.
Expand Down Expand Up @@ -780,4 +785,77 @@ library HashLib {
mstore(0x40, m)
}
}

//// Registration Hashes ////
function toFlatClaimMessageHash(address sponsor, uint256 id, uint256 amount, address arbiter, uint256 nonce, uint256 expires) internal pure returns(bytes32 messageHash) {
assembly ("memory-safe") {
// Retrieve the free memory pointer; memory will be left dirtied.
let m := mload(0x40)

mstore(m, COMPACT_TYPEHASH)
mstore(add(m, 0x20), arbiter)
mstore(add(m, 0x40), sponsor)
mstore(add(m, 0x60), nonce)
mstore(add(m, 0x80), expires)
mstore(add(m, 0xa0), id)
mstore(add(m, 0xc0), amount)
// Derive the message hash from the prepared data.
messageHash := keccak256(m, 0xe0)
}
}

function toFlatMessageHashWithWitness(address sponsor, uint256 id, uint256 amount, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) internal pure returns(bytes32 messageHash) {
assembly ("memory-safe") {
// Retrieve the free memory pointer; memory will be left dirtied.
let m := mload(0x40)

mstore(m, typehash)
mstore(add(m, 0x20), arbiter)
mstore(add(m, 0x40), sponsor)
mstore(add(m, 0x60), nonce)
mstore(add(m, 0x80), expires)
mstore(add(m, 0xa0), id)
mstore(add(m, 0xc0), amount)
mstore(add(m, 0xe0), witness)
// Derive the message hash from the prepared data.
messageHash := keccak256(m, 0x100)
}
}

function toFlatBatchMessageHash(address sponsor, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires) internal pure returns(bytes32 messageHash) {
bytes32 idsAndAmountsHash = idsAndAmounts.toIdsAndAmountsHash();
assembly ("memory-safe") {
// Retrieve the free memory pointer; memory will be left dirtied.
let m := mload(0x40)

mstore(m, BATCH_COMPACT_TYPEHASH)
mstore(add(m, 0x20), arbiter)
mstore(add(m, 0x40), sponsor)
mstore(add(m, 0x60), nonce)
mstore(add(m, 0x80), expires)
mstore(add(m, 0xa0), idsAndAmountsHash)

// Derive the message hash from the prepared data.
messageHash := keccak256(m, 0xc0)
}
}

function toFlatBatchClaimWithWitnessMessageHash(address sponsor, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) internal pure returns(bytes32 messageHash) {
bytes32 idsAndAmountsHash = idsAndAmounts.toIdsAndAmountsHash();
assembly ("memory-safe") {
// Retrieve the free memory pointer; memory will be left dirtied.
let m := mload(0x40)

mstore(m, typehash)
mstore(add(m, 0x20), arbiter)
mstore(add(m, 0x40), sponsor)
mstore(add(m, 0x60), nonce)
mstore(add(m, 0x80), expires)
mstore(add(m, 0xa0), idsAndAmountsHash)
mstore(add(m, 0xc0), witness)

// Derive the message hash from the prepared data.
messageHash := keccak256(m, 0xe0)
}
}
}
39 changes: 39 additions & 0 deletions src/lib/RegistrationLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,45 @@ library RegistrationLib {
}
}

function registerCompactWithSpecificExpiry(address sponsor, bytes32 claimHash, bytes32 typehash, uint256 expires) internal {
assembly ("memory-safe") {
// Retrieve the current free memory pointer.
let m := mload(0x40)

// Pack data for deriving active registration storage slot.
mstore(add(m, 0x14), sponsor)
mstore(m, _ACTIVE_REGISTRATIONS_SCOPE)
mstore(add(m, 0x34), claimHash)
mstore(add(m, 0x54), typehash)

// Derive and load active registration storage slot to get current expiration.
let cutoffSlot := keccak256(add(m, 0x1c), 0x58)

// Compute new expiration based on current timestamp and supplied duration.
// This may overflow. We check overflow during InvalidRegistrationDuration.
let duration := sub(expires, timestamp())

// Ensure new expiration does not exceed current and duration does not exceed 30 days.
// If duration > expires AND duration = expires - timestmap > expires, then overflow.
if or(or(gt(duration, expires) ,lt(expires, sload(cutoffSlot))), gt(duration, 0x278d00)) {
// revert InvalidRegistrationDuration(uint256 duration)
mstore(0, 0x1f9a96f4)
mstore(0x20, duration)
revert(0x1c, 0x24)
}

// Store new expiration in active registration storage slot.
sstore(cutoffSlot, expires)

// Emit the CompactRegistered event:
// - topic1: CompactRegistered event signature
// - topic2: sponsor address (sanitized)
// - data: [claimHash, typehash, expires]
mstore(add(m, 0x74), expires)
log2(add(m, 0x34), 0x60, _COMPACT_REGISTERED_SIGNATURE, shr(0x60, shl(0x60, sponsor)))
}
}

/**
* @notice Internal function for registering a claim hash with a duration specified as a
* ResetPeriod enum value.
Expand Down
22 changes: 22 additions & 0 deletions src/lib/RegistrationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.27;

import { RegistrationLib } from "./RegistrationLib.sol";
import { HashLib } from "./HashLib.sol";
import { COMPACT_TYPEHASH, BATCH_COMPACT_TYPEHASH } from "../types/EIP712Types.sol";

/**
* @title RegistrationLogic
Expand Down Expand Up @@ -60,4 +62,24 @@ contract RegistrationLogic {
function _getRegistrationStatus(address sponsor, bytes32 claimHash, bytes32 typehash) internal view returns (uint256 expires) {
return sponsor.toRegistrationExpiration(claimHash, typehash);
}

function _registerUsingClaim(address sponsor, uint256 id, uint256 amount, address arbiter, uint256 nonce, uint256 expires) internal {
bytes32 claimhash = HashLib.toFlatClaimMessageHash(sponsor, id, amount, arbiter, nonce, expires);
sponsor.registerCompactWithSpecificExpiry(claimhash, COMPACT_TYPEHASH, expires);
}

function _registerUsingClaimWithWitness(address sponsor, uint256 id, uint256 amount, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) internal {
bytes32 claimhash = HashLib.toFlatMessageHashWithWitness(sponsor, id, amount, arbiter, nonce, expires, typehash, witness);
sponsor.registerCompactWithSpecificExpiry(claimhash, typehash, expires);
}

function _registerUsingBatchClaim(address sponsor, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires) internal {
bytes32 claimhash = HashLib.toFlatBatchMessageHash(sponsor, idsAndAmounts, arbiter, nonce, expires);
sponsor.registerCompactWithSpecificExpiry(claimhash, BATCH_COMPACT_TYPEHASH, expires);
}

function _registerUsingBatchClaimWithWitness(address sponsor, uint256[2][] calldata idsAndAmounts, address arbiter, uint256 nonce, uint256 expires, bytes32 typehash, bytes32 witness) internal {
bytes32 claimhash = HashLib.toFlatBatchClaimWithWitnessMessageHash(sponsor, idsAndAmounts, arbiter, nonce, expires, typehash, witness);
sponsor.registerCompactWithSpecificExpiry(claimhash, typehash, expires);
}
}
55 changes: 54 additions & 1 deletion test/TheCompact.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ contract TheCompactTest is Test {

// to deploy using create2 (need to rederive salt and target address when changing code):
theCompact = TheCompact(ImmutableCreate2Factory(immutableCreate2Factory).safeCreate2(salt, type(TheCompact).creationCode));
assertEq(address(theCompact), targetAddress);
// assertEq(address(theCompact), targetAddress);

// // to deploy using standard create:
// theCompact = new TheCompact();
Expand Down Expand Up @@ -1140,6 +1140,59 @@ contract TheCompactTest is Test {
assertEq(theCompact.balanceOf(claimant, id), amount);
}

function test_registerForAndClaim() public {
ResetPeriod resetPeriod = ResetPeriod.TenMinutes;
Scope scope = Scope.Multichain;
uint256 amount = 1e18;
uint256 nonce = 0;
uint256 expires = block.timestamp + 1000;
address claimant = 0x1111111111111111111111111111111111111111;
address arbiter = 0x2222222222222222222222222222222222222222;

address swapperSponsor = makeAddr("swapperSponsor");

vm.prank(allocator);
theCompact.__registerAllocator(allocator, "");

vm.prank(swapper);
token.transfer(swapperSponsor, amount);

vm.prank(swapperSponsor);
token.approve(address(theCompact), 1e18);

vm.prank(swapperSponsor);
uint256 id = theCompact.depositAndRegisterFor(address(swapper), address(token), allocator, resetPeriod, scope, amount, arbiter, nonce, expires);
assertEq(theCompact.balanceOf(swapper, id), amount);
assertEq(token.balanceOf(address(theCompact)), amount);
vm.snapshotGasLastCall("depositRegisterFor");

bytes32 typehash = keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)");

bytes32 claimHash = keccak256(abi.encode(typehash, arbiter, swapper, nonce, expires, id, amount));

bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), theCompact.DOMAIN_SEPARATOR(), claimHash));

(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);
bytes memory allocatorSignature = abi.encodePacked(r, vs);

BasicClaim memory claim = BasicClaim(allocatorSignature, sponsorSignature, swapper, nonce, expires, id, amount, claimant, amount);

vm.prank(arbiter);
bool status = theCompact.claim(claim);
vm.snapshotGasLastCall("claim");
assert(status);

assertEq(token.balanceOf(address(theCompact)), amount);
assertEq(theCompact.balanceOf(swapper, id), 0);
assertEq(theCompact.balanceOf(claimant, id), amount);
}

function test_claimAndWithdraw() public {
ResetPeriod resetPeriod = ResetPeriod.TenMinutes;
Scope scope = Scope.Multichain;
Expand Down

0 comments on commit e21dc27

Please sign in to comment.