Skip to content

Commit

Permalink
implement reference deposit and register functions (3,267 over)
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Oct 27, 2024
1 parent 153101c commit 4b5062a
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 20 deletions.
199 changes: 190 additions & 9 deletions src/TheCompact.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.27;

import { ITheCompact } from "./interfaces/ITheCompact.sol";
import { CompactCategory } from "./types/CompactCategory.sol";
import { ActivatedCompactCategory } from "./types/ActivatedCompactCategory.sol";
import { Lock } from "./types/Lock.sol";
import { Scope } from "./types/Scope.sol";
import { ResetPeriod } from "./types/ResetPeriod.sol";
Expand Down Expand Up @@ -154,11 +156,13 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish {
using HashLib for ExogenousSplitBatchMultichainClaimWithWitness;
using HashLib for ExogenousQualifiedSplitBatchMultichainClaim;
using HashLib for ExogenousQualifiedSplitBatchMultichainClaimWithWitness;
using HashLib for ActivatedCompactCategory;
using IdLib for uint96;
using IdLib for uint256;
using IdLib for address;
using IdLib for Lock;
using IdLib for ResetPeriod;
using IdLib for CompactCategory;
using SafeTransferLib for address;
using FixedPointMathLib for uint256;
using ConsumerLib for uint256;
Expand Down Expand Up @@ -379,6 +383,179 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish {
_clearTstorish(_REENTRANCY_GUARD_SLOT);
}

function depositAndRegister(
uint256 id,
uint256 amount,
uint256 nonce,
uint256 deadline,
address depositor,
bytes32 claimHash,
CompactCategory compactCategory,
string calldata witness,
bytes calldata signature
) external returns (bool) {
_setTstorish(_REENTRANCY_GUARD_SLOT, 1);
id.toAllocatorId().mustHaveARegisteredAllocator();
address token = id.toToken().excludingNative();

uint256 initialBalance = token.balanceOf(address(this));

string memory activationTypestring;
string memory compactTypestring;
if (compactCategory == CompactCategory.Compact) {
compactTypestring = string.concat("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount", bytes(witness).length != 0 ? "," : "", witness, ")");
activationTypestring = string.concat("Activation(uint256 id,Compact compact)", compactTypestring);
} else if (compactCategory == CompactCategory.BatchCompact) {
compactTypestring =
string.concat("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts", bytes(witness).length != 0 ? "," : "", witness, ")");
activationTypestring = string.concat("Activation(uint256 id,BatchCompact compact)", compactTypestring);
} else {
compactTypestring = string.concat(
"MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts",
bytes(witness).length != 0 ? "," : "",
witness,
")"
);
activationTypestring = string.concat("Activation(uint256 id,MultichainCompact compact)", compactTypestring);
}

string memory witnessTypestring = string.concat("Activation witness)", activationTypestring, "TokenPermissions(address token,uint256 amount)");

bytes32 witnessHash = keccak256(abi.encodePacked(keccak256(bytes(activationTypestring)), id, claimHash));

ISignatureTransfer.SignatureTransferDetails memory details = ISignatureTransfer.SignatureTransferDetails({ to: address(this), requestedAmount: amount });

ISignatureTransfer.PermitTransferFrom memory permitTransferFrom =
ISignatureTransfer.PermitTransferFrom({ permitted: ISignatureTransfer.TokenPermissions({ token: token, amount: amount }), nonce: nonce, deadline: deadline });

_PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witnessHash, witnessTypestring, signature);

uint256 tokenBalance = token.balanceOf(address(this));

assembly ("memory-safe") {
if iszero(lt(initialBalance, tokenBalance)) {
// revert InvalidDepositBalanceChange()
mstore(0, 0x426d8dcf)
revert(0x1c, 0x04)
}
}

unchecked {
_deposit(depositor, id, tokenBalance - initialBalance);
}

_registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring));

_clearTstorish(_REENTRANCY_GUARD_SLOT);

return true;
}

function depositAndRegister(
address depositor,
ISignatureTransfer.TokenPermissions[] calldata permitted,
address allocator,
ResetPeriod resetPeriod,
Scope scope,
address recipient,
uint256 nonce,
uint256 deadline,
bytes32 claimHash,
CompactCategory compactCategory,
string calldata witness,
bytes calldata signature
) external payable returns (uint256[] memory ids) {
_setTstorish(_REENTRANCY_GUARD_SLOT, 1);

uint256 totalTokens = permitted.length;
bool firstUnderlyingTokenIsNative;
assembly ("memory-safe") {
let permittedOffset := permitted.offset
firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, add(permittedOffset, 0x20))))

// Revert if:
// * the array is empty
// * the callvalue is zero but the first token is native
// * the callvalue is nonzero but the first token is non-native
// * the first token is non-native and the callvalue doesn't equal the first amount
if or(iszero(totalTokens), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(permittedOffset, 0x40)))))))
{
// revert InvalidBatchDepositStructure()
mstore(0, 0xca0fc08e)
revert(0x1c, 0x04)
}
}

uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator);

string memory activationTypestring;
string memory compactTypestring;
if (compactCategory == CompactCategory.Compact) {
compactTypestring = string.concat("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount", bytes(witness).length != 0 ? "," : "", witness, ")");
activationTypestring = string.concat("BatchActivation(uint256[] ids,Compact compact)", compactTypestring);
} else if (compactCategory == CompactCategory.BatchCompact) {
compactTypestring =
string.concat("BatchCompact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256[2][] idsAndAmounts", bytes(witness).length != 0 ? "," : "", witness, ")");
activationTypestring = string.concat("BatchActivation(uint256[] ids,BatchCompact compact)", compactTypestring);
} else {
compactTypestring = string.concat(
"MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Segment[] segments)Segment(address arbiter,uint256 chainId,uint256[2][] idsAndAmounts",
bytes(witness).length != 0 ? "," : "",
witness,
")"
);
activationTypestring = string.concat("BatchActivation(uint256[] ids,MultichainCompact compact)", compactTypestring);
}

ids = new uint256[](totalTokens);

uint256 totalTokensLessInitialNative;
unchecked {
totalTokensLessInitialNative = totalTokens - firstUnderlyingTokenIsNative.asUint256();
}

if (firstUnderlyingTokenIsNative) {
_deposit(recipient, initialId, msg.value);
ids[0] = initialId;
}

(ISignatureTransfer.SignatureTransferDetails[] memory details, ISignatureTransfer.TokenPermissions[] memory permittedTokens, uint256[] memory initialTokenBalances) =
_preparePermit2ArraysAndGetBalances(ids, totalTokensLessInitialNative, firstUnderlyingTokenIsNative, permitted, initialId);

ISignatureTransfer.PermitBatchTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitBatchTransferFrom({ permitted: permittedTokens, nonce: nonce, deadline: deadline });

string memory witnessTypestring = string.concat("BatchActivation witness)", activationTypestring, "TokenPermissions(address token,uint256 amount)");

bytes32 witnessHash = keccak256(abi.encodePacked(keccak256(bytes(activationTypestring)), keccak256(abi.encodePacked(ids)), claimHash));

_PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witnessHash, witnessTypestring, signature);

uint256 tokenBalance;
uint256 initialBalance;
uint256 errorBuffer;
unchecked {
for (uint256 i = 0; i < totalTokensLessInitialNative; ++i) {
tokenBalance = permittedTokens[i].token.balanceOf(address(this));
initialBalance = initialTokenBalances[i];
errorBuffer |= (initialBalance >= tokenBalance).asUint256();

_deposit(recipient, ids[i + firstUnderlyingTokenIsNative.asUint256()], tokenBalance - initialBalance);
}
}

assembly ("memory-safe") {
if errorBuffer {
// revert InvalidDepositBalanceChange()
mstore(0, 0x426d8dcf)
revert(0x1c, 0x04)
}
}

_registeredClaimHashes[depositor][claimHash] = keccak256(bytes(compactTypestring));

_clearTstorish(_REENTRANCY_GUARD_SLOT);
}

function deposit(
address depositor,
ISignatureTransfer.TokenPermissions[] calldata permitted,
Expand Down Expand Up @@ -412,7 +589,17 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish {
uint256 initialId = address(0).toIdIfRegistered(scope, resetPeriod, allocator);

return _processBatchPermit2Deposits(
firstUnderlyingTokenIsNative, recipient, initialId, totalTokens, permitted, depositor, nonce, deadline, allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient), signature
firstUnderlyingTokenIsNative,
recipient,
initialId,
totalTokens,
permitted,
depositor,
nonce,
deadline,
allocator.toPermit2DepositWitnessHash(resetPeriod, scope, recipient),
"CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)",
signature
);
}

Expand Down Expand Up @@ -1768,6 +1955,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish {
uint256 nonce,
uint256 deadline,
bytes32 witness,
string memory witnessTypestring,
bytes calldata signature
) internal returns (uint256[] memory ids) {
_setTstorish(_REENTRANCY_GUARD_SLOT, 1);
Expand All @@ -1789,14 +1977,7 @@ contract TheCompact is ITheCompact, ERC6909, Tstorish {

ISignatureTransfer.PermitBatchTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitBatchTransferFrom({ permitted: permittedTokens, nonce: nonce, deadline: deadline });

_PERMIT2.permitWitnessTransferFrom(
permitTransferFrom,
details,
depositor,
witness,
"CompactDeposit witness)CompactDeposit(address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)",
signature
);
_PERMIT2.permitWitnessTransferFrom(permitTransferFrom, details, depositor, witness, witnessTypestring, signature);

uint256 tokenBalance;
uint256 initialBalance;
Expand Down
69 changes: 60 additions & 9 deletions src/lib/HashLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,15 @@ import {
PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_THREE,
PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FOUR,
PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_FIVE,
PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX
PERMIT2_ACTIVATION_MULTICHAIN_COMPACT_TYPESTRING_FRAGMENT_SIX,
COMPACT_ACTIVATION_TYPEHASH,
BATCH_COMPACT_ACTIVATION_TYPEHASH,
MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH,
COMPACT_BATCH_ACTIVATION_TYPEHASH,
BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH,
MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH,
TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE,
TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO
} from "../types/EIP712Types.sol";

import {
Expand Down Expand Up @@ -922,13 +930,20 @@ library HashLib {
}
}

function toPermit2ActivatedCompactTypehash(ActivatedCompactCategory category, string calldata witness) internal pure returns (bytes32 typehash) {
function toPermit2DepositAndRegisterTypehashes(ActivatedCompactCategory category, string calldata compactWitnessTypestringFragment)
internal
pure
returns (bytes32 permit2Typehash, bytes32 activationTypehash, bytes32 compactTypehash)
{
assembly ("memory-safe") {
function toTypehash(c, witnessOffset, witnessLength) -> t {
function toTypehash(c, witnessOffset, witnessLength) -> p, a, t {
let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied

let isBatch := gt(c, 2)
c := sub(c, mul(isBatch, 3))
let indexWords := shl(5, c)

let activationStart

// 1. handle no-witness cases or prepare first witness fragment based on deposit vs batch deposit
let fragmentTwoStart
Expand All @@ -937,7 +952,18 @@ library HashLib {
mstore(0, PERMIT2_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH)
mstore(0x20, PERMIT2_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH)
mstore(0x40, PERMIT2_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH)
t := mload(shl(5, c))
p := mload(indexWords)

mstore(0, COMPACT_ACTIVATION_TYPEHASH)
mstore(0x20, BATCH_COMPACT_ACTIVATION_TYPEHASH)
mstore(0x40, MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH)
a := mload(indexWords)

mstore(0, COMPACT_TYPEHASH)
mstore(0x20, BATCH_COMPACT_TYPEHASH)
mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH)
t := mload(indexWords)

mstore(0x40, m)
leave
}
Expand All @@ -948,14 +974,26 @@ library HashLib {
mstore(add(m, 0x6d), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE)
mstore(add(m, 0x60), PERMIT2_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FOUR)
fragmentTwoStart := add(m, 0x8d)
activationStart := add(m, 0x77)
}

if iszero(fragmentTwoStart) {
if iszero(witnessLength) {
mstore(0, PERMIT2_BATCH_DEPOSIT_WITH_COMPACT_ACTIVATION_TYPEHASH)
mstore(0x20, PERMIT2_BATCH_DEPOSIT_WITH_BATCH_COMPACT_ACTIVATION_TYPEHASH)
mstore(0x40, PERMIT2_BATCH_DEPOSIT_WITH_MULTICHAIN_COMPACT_ACTIVATION_TYPEHASH)
t := mload(shl(5, c))
p := mload(indexWords)

mstore(0, COMPACT_BATCH_ACTIVATION_TYPEHASH)
mstore(0x20, BATCH_COMPACT_BATCH_ACTIVATION_TYPEHASH)
mstore(0x40, MULTICHAIN_COMPACT_BATCH_ACTIVATION_TYPEHASH)
a := mload(indexWords)

mstore(0, COMPACT_TYPEHASH)
mstore(0x20, BATCH_COMPACT_TYPEHASH)
mstore(0x40, MULTICHAIN_COMPACT_TYPEHASH)
t := mload(indexWords)

mstore(0x40, m)
leave
}
Expand All @@ -967,6 +1005,7 @@ library HashLib {
mstore(add(m, 0x80), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_FIVE)
mstore8(add(m, 0xa0), PERMIT2_BATCH_DEPOSIT_WITH_ACTIVATION_TYPESTRING_FRAGMENT_SIX)
fragmentTwoStart := add(m, 0xa1)
activationStart := add(m, 0x83)
}

// 2. prepare second witness fragment based on compact category
Expand Down Expand Up @@ -997,14 +1036,26 @@ library HashLib {
fragmentThreeStart := add(fragmentTwoStart, 0xb0)
}

// 3. insert the supplied witness (must also include TokenPermissions)
// 3. insert the supplied compact witness
calldatacopy(fragmentThreeStart, witnessOffset, witnessLength)

// 4. derive the typehash
t := keccak256(m, add(sub(fragmentThreeStart, m), witnessLength))
// 4. insert tokenPermissions
let tokenPermissionsFragmentStart := add(fragmentThreeStart, witnessLength)
mstore(add(tokenPermissionsFragmentStart, 0x0e), TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_TWO)
mstore(tokenPermissionsFragmentStart, TOKEN_PERMISSIONS_TYPSTRING_FRAGMENT_ONE)

// 5. derive the permit2 typehash
let totalPayloadSizeWithoutTokenPermissions := sub(tokenPermissionsFragmentStart, m)
p := keccak256(m, add(totalPayloadSizeWithoutTokenPermissions, 0x2e))

// 6. derive the activation typehash
a := keccak256(activationStart, sub(totalPayloadSizeWithoutTokenPermissions, activationStart))

// 7. derive the compact typehash
t := keccak256(fragmentTwoStart, sub(totalPayloadSizeWithoutTokenPermissions, fragmentTwoStart))
}

typehash := toTypehash(category, witness.offset, witness.length)
permit2Typehash, activationTypehash, compactTypehash := toTypehash(category, compactWitnessTypestringFragment.offset, compactWitnessTypestringFragment.length)
}
}
}
8 changes: 8 additions & 0 deletions src/lib/IdLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Lock } from "../types/Lock.sol";
import { MetadataLib } from "./MetadataLib.sol";
import { EfficiencyLib } from "./EfficiencyLib.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { CompactCategory } from "../types/CompactCategory.sol";
import { ActivatedCompactCategory } from "../types/ActivatedCompactCategory.sol";

library IdLib {
using IdLib for uint96;
Expand Down Expand Up @@ -192,6 +194,12 @@ library IdLib {
return (msg.sender == allocator).or(allocator.code.length > 0).or(proof.length == 86 && (proof[0] == 0xff).and(allocator == address(uint160(uint256(keccak256(proof))))));
}

function toActivated(CompactCategory category, bool batch) internal pure returns (ActivatedCompactCategory activatedCategory) {
assembly ("memory-safe") {
activatedCategory := add(category, mul(batch, 3))
}
}

function register(address allocator) internal returns (uint96 allocatorId) {
allocatorId = allocator.usingAllocatorId();

Expand Down
Loading

0 comments on commit 4b5062a

Please sign in to comment.