From efd14f46ff539954b653aca4714c4473591a96e0 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:03:49 -0700 Subject: [PATCH] create WithdrawalLogic --- src/lib/ClaimProcessorLogic.sol | 7 +- src/lib/InternalLogic.sol | 187 ++---------------------------- src/lib/TransferLogic.sol | 50 ++++++++ src/lib/WithdrawalLogic.sol | 196 ++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 182 deletions(-) create mode 100644 src/lib/WithdrawalLogic.sol diff --git a/src/lib/ClaimProcessorLogic.sol b/src/lib/ClaimProcessorLogic.sol index 24a6ce0..d77be58 100644 --- a/src/lib/ClaimProcessorLogic.sol +++ b/src/lib/ClaimProcessorLogic.sol @@ -56,10 +56,10 @@ import { EfficiencyLib } from "./EfficiencyLib.sol"; import { FunctionCastLib } from "./FunctionCastLib.sol"; import { HashLib } from "./HashLib.sol"; import { IdLib } from "./IdLib.sol"; -import { DepositLogic } from "./DepositLogic.sol"; +import { WithdrawalLogic } from "./WithdrawalLogic.sol"; import { ValidityLib } from "./ValidityLib.sol"; -contract ClaimProcessorLogic is DepositLogic { +contract ClaimProcessorLogic is WithdrawalLogic { using HashLib for address; using HashLib for bytes32; using HashLib for uint256; @@ -130,6 +130,9 @@ contract ClaimProcessorLogic is DepositLogic { using FunctionCastLib for function(bytes32, bytes32, uint256, uint256, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); using FunctionCastLib for function(bytes32, bytes32, uint256, uint256, bytes32, bytes32, function(address, address, uint256, uint256) internal returns (bool)) internal returns (bool); + /// @dev `keccak256(bytes("Claim(address,address,address,bytes32)"))`. + uint256 private constant _CLAIM_EVENT_SIGNATURE = 0x770c32a2314b700d6239ee35ba23a9690f2fceb93a55d8c753e953059b3b18d4; + function _processSimpleClaim(bytes32 messageHash, uint256 calldataPointer, uint256 offsetToId, bytes32 typehash, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) diff --git a/src/lib/InternalLogic.sol b/src/lib/InternalLogic.sol index a7e5de4..5807136 100644 --- a/src/lib/InternalLogic.sol +++ b/src/lib/InternalLogic.sol @@ -84,6 +84,13 @@ contract InternalLogic is Tstorish { _PERMIT2_INITIALLY_DEPLOYED = _checkPermit2Deployment(); } + function _emitClaim(address sponsor, bytes32 messageHash, address allocator) internal { + assembly ("memory-safe") { + mstore(0, messageHash) + log4(0, 0x20, _CLAIM_EVENT_SIGNATURE, shr(0x60, shl(0x60, sponsor)), shr(0x60, shl(0x60, allocator)), caller()) + } + } + function _notExpiredAndSignedByAllocator(bytes32 messageHash, address allocator, BasicTransfer calldata transferPayload) internal { transferPayload.expires.later(); @@ -100,71 +107,6 @@ contract InternalLogic is Tstorish { _clearTstorish(_REENTRANCY_GUARD_SLOT); } - function _deriveConsistentAllocatorAndConsumeNonce(TransferComponent[] calldata components, uint256 nonce) internal returns (address allocator) { - uint256 totalComponents = components.length; - - uint256 errorBuffer = (totalComponents == 0).asUint256(); - - // TODO: bounds checks on these array accesses can be skipped as an optimization - uint96 allocatorId = components[0].id.toAllocatorId(); - - allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); - - unchecked { - for (uint256 i = 1; i < totalComponents; ++i) { - errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); - } - } - - assembly ("memory-safe") { - if errorBuffer { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } - } - - function _deriveConsistentAllocatorAndConsumeNonce(SplitByIdComponent[] calldata components, uint256 nonce) internal returns (address allocator) { - uint256 totalComponents = components.length; - - uint256 errorBuffer = (totalComponents == 0).asUint256(); - - // TODO: bounds checks on these array accesses can be skipped as an optimization - uint96 allocatorId = components[0].id.toAllocatorId(); - - allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); - - unchecked { - for (uint256 i = 1; i < totalComponents; ++i) { - errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); - } - } - - assembly ("memory-safe") { - if errorBuffer { - // revert InvalidBatchAllocation() - mstore(0, 0x3a03d3bb) - revert(0x1c, 0x04) - } - } - } - - function _emitClaim(address sponsor, bytes32 messageHash, address allocator) internal { - assembly ("memory-safe") { - mstore(0, messageHash) - log4(0, 0x20, _CLAIM_EVENT_SIGNATURE, shr(0x60, shl(0x60, sponsor)), shr(0x60, shl(0x60, allocator)), caller()) - } - } - - function _emitForcedWithdrawalStatusUpdatedEvent(uint256 id, uint256 withdrawableAt) internal { - assembly ("memory-safe") { - mstore(0, iszero(iszero(withdrawableAt))) - mstore(0x20, withdrawableAt) - log3(0, 0x40, _FORCED_WITHDRAWAL_STATUS_UPDATED_SIGNATURE, caller(), id) - } - } - function _register(address sponsor, bytes32 claimHash, bytes32 typehash, uint256 duration) internal { assembly ("memory-safe") { let m := mload(0x40) @@ -204,56 +146,6 @@ contract InternalLogic is Tstorish { return true; } - function _enableForcedWithdrawal(uint256 id) internal returns (uint256 withdrawableAt) { - // overflow check not necessary as reset period is capped - unchecked { - withdrawableAt = block.timestamp + id.toResetPeriod().toSeconds(); - } - - uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); - assembly ("memory-safe") { - sstore(cutoffTimeSlotLocation, withdrawableAt) - } - - _emitForcedWithdrawalStatusUpdatedEvent(id, withdrawableAt); - } - - function _disableForcedWithdrawal(uint256 id) internal returns (bool) { - uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); - - assembly ("memory-safe") { - if iszero(sload(cutoffTimeSlotLocation)) { - // revert ForcedWithdrawalAlreadyDisabled(msg.sender, id) - mstore(0, 0xe632dbad) - mstore(0x20, caller()) - mstore(0x40, id) - revert(0x1c, 0x44) - } - - sstore(cutoffTimeSlotLocation, 0) - } - - _emitForcedWithdrawalStatusUpdatedEvent(id, uint256(0).asStubborn()); - - return true; - } - - function _processForcedWithdrawal(uint256 id, address recipient, uint256 amount) internal returns (bool) { - uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); - - assembly ("memory-safe") { - let withdrawableAt := sload(cutoffTimeSlotLocation) - if or(iszero(withdrawableAt), gt(withdrawableAt, timestamp())) { - // revert PrematureWithdrawal(id) - mstore(0, 0x9287bcb0) - mstore(0x20, id) - revert(0x1c, 0x24) - } - } - - return _withdraw(msg.sender, recipient, id, amount); - } - function _consume(uint256[] calldata nonces) internal returns (bool) { // NOTE: this may not be necessary, consider removing msg.sender.usingAllocatorId().mustHaveARegisteredAllocator(); @@ -292,60 +184,6 @@ contract InternalLogic is Tstorish { allocatorId = allocator.register(); } - function _getForcedWithdrawalStatus(address account, uint256 id) internal view returns (ForcedWithdrawalStatus status, uint256 forcedWithdrawalAvailableAt) { - uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(account, id); - assembly ("memory-safe") { - forcedWithdrawalAvailableAt := sload(cutoffTimeSlotLocation) - status := mul(iszero(iszero(forcedWithdrawalAvailableAt)), sub(2, gt(forcedWithdrawalAvailableAt, timestamp()))) - } - } - - /// @dev Burns `amount` token `id` from `from` without checking transfer hooks and sends - /// the corresponding underlying tokens to `to`. Emits a {Transfer} event. - function _withdraw(address from, address to, uint256 id, uint256 amount) internal returns (bool) { - _setReentrancyGuard(); - address token = id.toToken(); - - if (token == address(0)) { - to.safeTransferETH(amount); - } else { - uint256 initialBalance = token.balanceOf(address(this)); - token.safeTransfer(to, amount); - // NOTE: if the balance increased, this will underflow to a massive number causing - // the burn to fail; furthermore, this scenario would indicate a very broken token - unchecked { - amount = initialBalance - token.balanceOf(address(this)); - } - } - - assembly ("memory-safe") { - // Compute the balance slot. - mstore(0x20, _ERC6909_MASTER_SLOT_SEED) - mstore(0x14, from) - mstore(0x00, id) - let fromBalanceSlot := keccak256(0x00, 0x40) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient balance. - if gt(amount, fromBalance) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - - let account := shr(0x60, shl(0x60, from)) - - // Emit the {Transfer} and {Withdrawal} events. - mstore(0x00, caller()) - mstore(0x20, amount) - log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, account, 0, id) - } - - _clearReentrancyGuard(); - - return true; - } - function _getLockDetails(uint256 id) internal view returns (address token, address allocator, ResetPeriod resetPeriod, Scope scope) { token = id.toToken(); allocator = id.toAllocatorId().toRegisteredAllocator(); @@ -400,15 +238,4 @@ contract InternalLogic is Tstorish { function _tokenURI(uint256 id) internal view returns (string memory) { return _METADATA_RENDERER.uri(id.toLock(), id); } - - function _getCutoffTimeSlot(address account, uint256 id) internal pure returns (uint256 cutoffTimeSlotLocation) { - assembly ("memory-safe") { - let m := mload(0x40) - mstore(0x14, account) - mstore(0, _FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE) - mstore(0x34, id) - cutoffTimeSlotLocation := keccak256(0x1c, 0x38) - mstore(0x40, m) - } - } } diff --git a/src/lib/TransferLogic.sol b/src/lib/TransferLogic.sol index c69e9de..135876f 100644 --- a/src/lib/TransferLogic.sol +++ b/src/lib/TransferLogic.sol @@ -184,4 +184,54 @@ contract TransferLogic is InternalLogic { } } } + + function _deriveConsistentAllocatorAndConsumeNonce(TransferComponent[] calldata components, uint256 nonce) internal returns (address allocator) { + uint256 totalComponents = components.length; + + uint256 errorBuffer = (totalComponents == 0).asUint256(); + + // TODO: bounds checks on these array accesses can be skipped as an optimization + uint96 allocatorId = components[0].id.toAllocatorId(); + + allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); + + unchecked { + for (uint256 i = 1; i < totalComponents; ++i) { + errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); + } + } + + assembly ("memory-safe") { + if errorBuffer { + // revert InvalidBatchAllocation() + mstore(0, 0x3a03d3bb) + revert(0x1c, 0x04) + } + } + } + + function _deriveConsistentAllocatorAndConsumeNonce(SplitByIdComponent[] calldata components, uint256 nonce) internal returns (address allocator) { + uint256 totalComponents = components.length; + + uint256 errorBuffer = (totalComponents == 0).asUint256(); + + // TODO: bounds checks on these array accesses can be skipped as an optimization + uint96 allocatorId = components[0].id.toAllocatorId(); + + allocator = allocatorId.fromRegisteredAllocatorIdWithConsumed(nonce); + + unchecked { + for (uint256 i = 1; i < totalComponents; ++i) { + errorBuffer |= (components[i].id.toAllocatorId() != allocatorId).asUint256(); + } + } + + assembly ("memory-safe") { + if errorBuffer { + // revert InvalidBatchAllocation() + mstore(0, 0x3a03d3bb) + revert(0x1c, 0x04) + } + } + } } diff --git a/src/lib/WithdrawalLogic.sol b/src/lib/WithdrawalLogic.sol new file mode 100644 index 0000000..c100d00 --- /dev/null +++ b/src/lib/WithdrawalLogic.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { BatchTransfer, SplitBatchTransfer } from "../types/BatchClaims.sol"; +import { BasicTransfer, SplitTransfer } from "../types/Claims.sol"; +import { CompactCategory } from "../types/CompactCategory.sol"; +import { SplitComponent, TransferComponent, SplitByIdComponent } from "../types/Components.sol"; +import { ForcedWithdrawalStatus } from "../types/ForcedWithdrawalStatus.sol"; +import { Lock } from "../types/Lock.sol"; +import { ResetPeriod } from "../types/ResetPeriod.sol"; +import { Scope } from "../types/Scope.sol"; + +import { ConsumerLib } from "./ConsumerLib.sol"; +import { EfficiencyLib } from "./EfficiencyLib.sol"; +import { FunctionCastLib } from "./FunctionCastLib.sol"; +import { HashLib } from "./HashLib.sol"; +import { IdLib } from "./IdLib.sol"; +import { MetadataRenderer } from "./MetadataRenderer.sol"; +import { ValidityLib } from "./ValidityLib.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { Tstorish } from "tstorish/Tstorish.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; + +import { DepositLogic } from "./DepositLogic.sol"; + +contract WithdrawalLogic is DepositLogic { + using HashLib for address; + using HashLib for bytes32; + using HashLib for uint256; + using HashLib for BasicTransfer; + using HashLib for SplitTransfer; + using HashLib for BatchTransfer; + using HashLib for SplitBatchTransfer; + using IdLib for uint96; + using IdLib for uint256; + using IdLib for address; + using IdLib for Lock; + using IdLib for ResetPeriod; + using SafeTransferLib for address; + using ConsumerLib for uint256; + using EfficiencyLib for bool; + using EfficiencyLib for bytes32; + using EfficiencyLib for uint256; + using ValidityLib for address; + using ValidityLib for uint96; + using ValidityLib for uint256; + using ValidityLib for bytes32; + using FunctionCastLib for function(bytes32, address, BasicTransfer calldata) internal; + + address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + + uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; + + uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; + + /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; + + /// @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; + + /// @dev `keccak256(bytes("ForcedWithdrawalStatusUpdated(address,uint256,bool,uint256)"))`. + uint256 private constant _FORCED_WITHDRAWAL_STATUS_UPDATED_SIGNATURE = 0xe27f5e0382cf5347965fc81d5c81cd141897fe9ce402d22c496b7c2ddc84e5fd; + + uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; + + // slot: keccak256(_FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE ++ account ++ id) => activates + uint256 private constant _FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE = 0x41d0e04b; + + function _emitForcedWithdrawalStatusUpdatedEvent(uint256 id, uint256 withdrawableAt) internal { + assembly ("memory-safe") { + mstore(0, iszero(iszero(withdrawableAt))) + mstore(0x20, withdrawableAt) + log3(0, 0x40, _FORCED_WITHDRAWAL_STATUS_UPDATED_SIGNATURE, caller(), id) + } + } + + function _enableForcedWithdrawal(uint256 id) internal returns (uint256 withdrawableAt) { + // overflow check not necessary as reset period is capped + unchecked { + withdrawableAt = block.timestamp + id.toResetPeriod().toSeconds(); + } + + uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); + assembly ("memory-safe") { + sstore(cutoffTimeSlotLocation, withdrawableAt) + } + + _emitForcedWithdrawalStatusUpdatedEvent(id, withdrawableAt); + } + + function _disableForcedWithdrawal(uint256 id) internal returns (bool) { + uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); + + assembly ("memory-safe") { + if iszero(sload(cutoffTimeSlotLocation)) { + // revert ForcedWithdrawalAlreadyDisabled(msg.sender, id) + mstore(0, 0xe632dbad) + mstore(0x20, caller()) + mstore(0x40, id) + revert(0x1c, 0x44) + } + + sstore(cutoffTimeSlotLocation, 0) + } + + _emitForcedWithdrawalStatusUpdatedEvent(id, uint256(0).asStubborn()); + + return true; + } + + function _processForcedWithdrawal(uint256 id, address recipient, uint256 amount) internal returns (bool) { + uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(msg.sender, id); + + assembly ("memory-safe") { + let withdrawableAt := sload(cutoffTimeSlotLocation) + if or(iszero(withdrawableAt), gt(withdrawableAt, timestamp())) { + // revert PrematureWithdrawal(id) + mstore(0, 0x9287bcb0) + mstore(0x20, id) + revert(0x1c, 0x24) + } + } + + return _withdraw(msg.sender, recipient, id, amount); + } + + function _getForcedWithdrawalStatus(address account, uint256 id) internal view returns (ForcedWithdrawalStatus status, uint256 forcedWithdrawalAvailableAt) { + uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(account, id); + assembly ("memory-safe") { + forcedWithdrawalAvailableAt := sload(cutoffTimeSlotLocation) + status := mul(iszero(iszero(forcedWithdrawalAvailableAt)), sub(2, gt(forcedWithdrawalAvailableAt, timestamp()))) + } + } + + /// @dev Burns `amount` token `id` from `from` without checking transfer hooks and sends + /// the corresponding underlying tokens to `to`. Emits a {Transfer} event. + function _withdraw(address from, address to, uint256 id, uint256 amount) internal returns (bool) { + _setReentrancyGuard(); + address token = id.toToken(); + + if (token == address(0)) { + to.safeTransferETH(amount); + } else { + uint256 initialBalance = token.balanceOf(address(this)); + token.safeTransfer(to, amount); + // NOTE: if the balance increased, this will underflow to a massive number causing + // the burn to fail; furthermore, this scenario would indicate a very broken token + unchecked { + amount = initialBalance - token.balanceOf(address(this)); + } + } + + assembly ("memory-safe") { + // Compute the balance slot. + mstore(0x20, _ERC6909_MASTER_SLOT_SEED) + mstore(0x14, from) + mstore(0x00, id) + let fromBalanceSlot := keccak256(0x00, 0x40) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient balance. + if gt(amount, fromBalance) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + + let account := shr(0x60, shl(0x60, from)) + + // Emit the {Transfer} and {Withdrawal} events. + mstore(0x00, caller()) + mstore(0x20, amount) + log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, account, 0, id) + } + + _clearReentrancyGuard(); + + return true; + } + + function _getCutoffTimeSlot(address account, uint256 id) internal pure returns (uint256 cutoffTimeSlotLocation) { + assembly ("memory-safe") { + let m := mload(0x40) + mstore(0x14, account) + mstore(0, _FORCED_WITHDRAWAL_ACTIVATIONS_SCOPE) + mstore(0x34, id) + cutoffTimeSlotLocation := keccak256(0x1c, 0x38) + mstore(0x40, m) + } + } +}