diff --git a/foundry.toml b/foundry.toml index e179a00..98f1bdd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ solc = '0.8.28' evm_version='cancun' via_ir = true # optimizer_runs = 4_294_967_295 -optimizer_runs = 1_000 +optimizer_runs = 1_500 bytecode_hash = 'none' src = "src" out = "out" diff --git a/src/TheCompact.sol b/src/TheCompact.sol index 13f81b3..823af22 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -100,6 +100,8 @@ import { ExogenousQualifiedSplitBatchMultichainClaimWithWitness } from "./types/BatchMultichainClaims.sol"; +import { PERMIT2_WITNESS_FRAGMENT_HASH } from "./types/EIP712Types.sol"; + import { SplitComponent, TransferComponent, SplitByIdComponent, BatchClaimComponent, SplitBatchClaimComponent } from "./types/Components.sol"; import { IAllocator } from "./interfaces/IAllocator.sol"; @@ -211,6 +213,7 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { uint256 private constant _WITHDRAWAL_EVENT_SIGNATURE = 0xc2b4a290c20fb28939d29f102514fbffd2b73c059ffba8b78250c94161d5fcc6; uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; + uint32 private constant _PERMIT_WITNESS_TRANSFER_FROM_SELECTOR = 0x137c29fe; // Rage-quit functionality (TODO: optimize storage layout) mapping(address => mapping(uint256 => uint256)) private _cutoffTime; @@ -302,34 +305,69 @@ contract TheCompact is ITheCompact, ERC6909, Extsload { return true; } + // TODO: still need to implement one of the following: + // 1) check if permit2 has been deployed (can cache as an immutable) + // 2) deposit based on balance changes function deposit( - address depositor, address token, + uint256 amount, + uint256 nonce, + uint256 deadline, + address depositor, address allocator, ResetPeriod resetPeriod, Scope scope, - uint256 amount, address recipient, - uint256 nonce, - uint256 deadline, bytes calldata signature ) external returns (uint256 id) { id = token.excludingNative().toIdIfRegistered(scope, resetPeriod, allocator); - ISignatureTransfer.SignatureTransferDetails memory signatureTransferDetails = ISignatureTransfer.SignatureTransferDetails({ to: address(this), requestedAmount: amount }); + address permit2 = address(_PERMIT2); - ISignatureTransfer.TokenPermissions memory tokenPermissions = ISignatureTransfer.TokenPermissions({ token: token, amount: amount }); + assembly ("memory-safe") { + let m := mload(0x40) // Grab the free memory pointer; memory will be left dirtied. - ISignatureTransfer.PermitTransferFrom memory permitTransferFrom = ISignatureTransfer.PermitTransferFrom({ permitted: tokenPermissions, nonce: nonce, deadline: deadline }); + // NOTE: none of these arguments are sanitized; the assumption is that they have to + // match the signed values anyway, so *should* be fine not to sanitize them but could + // optionally check that there are no dirty upper bits on any of them. + mstore(m, PERMIT2_WITNESS_FRAGMENT_HASH) + calldatacopy(add(m, 0x20), 0x84, 0xa0) // depositor, allocator, resetPeriod, scope, recipient + let witness := keccak256(m, 0xc0) - _PERMIT2.permitWitnessTransferFrom( - permitTransferFrom, - signatureTransferDetails, - depositor, - allocator.toPermit2WitnessHash(depositor, resetPeriod, scope, recipient), - "CompactDeposit witness)CompactDeposit(address depositor,address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)", - signature - ); + let signatureLength := signature.length + let dataStart := add(m, 0x1c) + + mstore(m, _PERMIT_WITNESS_TRANSFER_FROM_SELECTOR) + calldatacopy(add(m, 0x20), 0x04, 0x80) // token, amount, nonce, deadline + mstore(add(m, 0xa0), address()) + mstore(add(m, 0xc0), calldataload(0x24)) // amount + mstore(add(m, 0xe0), calldataload(0x84)) // depositor + mstore(add(m, 0x100), witness) + mstore(add(m, 0x120), 0x140) + mstore(add(m, 0x140), 0x220) + // "CompactDeposit witness)CompactDeposit(address depositor,address allocator,uint8 resetPeriod,uint8 scope,address recipient)TokenPermissions(address token,uint256 amount)" + mstore(add(m, 0x160), 0xa8) + mstore(add(m, 0x180), 0x436f6d706163744465706f736974207769746e65737329436f6d706163744465) + mstore(add(m, 0x1a0), 0x706f7369742861646472657373206465706f7369746f722c6164647265737320) + mstore(add(m, 0x1c0), 0x616c6c6f6361746f722c75696e7438207265736574506572696f642c75696e74) + mstore(add(m, 0x1e0), 0x382073636f70652c6164647265737320726563697069656e7429546f6b656e50) + mstore(add(m, 0x208), 0x20616d6f756e7429) + mstore(add(m, 0x200), 0x65726d697373696f6e73286164647265737320746f6b656e2c75696e74323536) + mstore(add(m, 0x240), signatureLength) + calldatacopy(add(m, 0x260), signature.offset, signatureLength) + + if iszero(call(gas(), permit2, 0, add(m, 0x1c), add(0x244, signatureLength), 0, 0)) { + // bubble up if the call failed and there's data + // NOTE: consider evaluating remaining gas to protect against revert bombing + if returndatasize() { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + + // TODO: add proper revert on no data + revert(0, 0) + } + } _deposit(depositor, recipient, id, amount); } diff --git a/src/interfaces/ITheCompact.sol b/src/interfaces/ITheCompact.sol index 8bbac96..650ad4e 100644 --- a/src/interfaces/ITheCompact.sol +++ b/src/interfaces/ITheCompact.sol @@ -41,15 +41,15 @@ interface ITheCompact { function deposit(address token, address allocator, ResetPeriod resetPeriod, Scope scope, uint256 amount, address recipient) external returns (uint256 id); function deposit( - address depositor, address token, + uint256 amount, + uint256 nonce, + uint256 deadline, + address depositor, address allocator, ResetPeriod resetPeriod, Scope scope, - uint256 amount, address recipient, - uint256 nonce, - uint256 deadline, bytes calldata signature ) external returns (uint256 id); diff --git a/src/test/CalldataDebugger.sol b/src/test/CalldataDebugger.sol new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/test/CalldataDebugger.sol @@ -0,0 +1 @@ + diff --git a/test/TheCompact.t.sol b/test/TheCompact.t.sol index 4bc9b8e..19ea143 100644 --- a/test/TheCompact.t.sol +++ b/test/TheCompact.t.sol @@ -325,7 +325,7 @@ contract TheCompactTest is Test { vm.prank(allocator); uint96 allocatorId = theCompact.__register(allocator, ""); - uint256 id = theCompact.deposit(swapper, address(token), allocator, resetPeriod, scope, amount, recipient, nonce, deadline, signature); + uint256 id = theCompact.deposit(address(token), amount, nonce, deadline, swapper, allocator, resetPeriod, scope, recipient, signature); (address derivedToken, address derivedAllocator, ResetPeriod derivedResetPeriod, Scope derivedScope) = theCompact.getLockDetails(id); assertEq(derivedToken, address(token));