diff --git a/src/TheCompact.sol b/src/TheCompact.sol index e4ee8c1..0fc8a18 100644 --- a/src/TheCompact.sol +++ b/src/TheCompact.sol @@ -11,9 +11,7 @@ import { Scope } from "./types/Scope.sol"; import { ResetPeriod } from "./types/ResetPeriod.sol"; import { ForcedWithdrawalStatus } from "./types/ForcedWithdrawalStatus.sol"; -import { AllocatorLogic } from "./lib/AllocatorLogic.sol"; -import { ClaimProcessor } from "./lib/ClaimProcessor.sol"; -import { Extsload } from "./lib/Extsload.sol"; +import { TheCompactLogic } from "./lib/TheCompactLogic.sol"; import { ERC6909 } from "solady/tokens/ERC6909.sol"; import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; @@ -26,7 +24,7 @@ import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.so * formation and mediation of reusable "resource locks." * This contract has not yet been properly tested, audited, or reviewed. */ -contract TheCompact is ITheCompact, AllocatorLogic, ClaimProcessor, ERC6909, Extsload { +contract TheCompact is ITheCompact, ERC6909, TheCompactLogic { function deposit(address allocator) external payable returns (uint256) { return _performBasicNativeTokenDeposit(allocator); } diff --git a/src/lib/ClaimProcessorLogic.sol b/src/lib/ClaimProcessorLogic.sol index e896e2b..d4e7129 100644 --- a/src/lib/ClaimProcessorLogic.sol +++ b/src/lib/ClaimProcessorLogic.sol @@ -56,10 +56,11 @@ import { EfficiencyLib } from "./EfficiencyLib.sol"; import { FunctionCastLib } from "./FunctionCastLib.sol"; import { HashLib } from "./HashLib.sol"; import { IdLib } from "./IdLib.sol"; +import { RegistrationLogic } from "./RegistrationLogic.sol"; import { ValidityLib } from "./ValidityLib.sol"; -import { WithdrawalLogic } from "./WithdrawalLogic.sol"; +import { SharedLogic } from "./SharedLogic.sol"; -contract ClaimProcessorLogic is WithdrawalLogic { +contract ClaimProcessorLogic is SharedLogic, RegistrationLogic { using HashLib for address; using HashLib for bytes32; using HashLib for uint256; diff --git a/src/lib/DepositLogic.sol b/src/lib/DepositLogic.sol index 7ca9963..27bffe6 100644 --- a/src/lib/DepositLogic.sol +++ b/src/lib/DepositLogic.sol @@ -1,24 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; -import { ResetPeriod } from "../types/ResetPeriod.sol"; -import { Scope } from "../types/Scope.sol"; - -import { EfficiencyLib } from "./EfficiencyLib.sol"; -import { IdLib } from "./IdLib.sol"; -import { RegistrationLogic } from "./RegistrationLogic.sol"; -import { TransferLogic } from "./TransferLogic.sol"; -import { ValidityLib } from "./ValidityLib.sol"; +import { ConstructorLogic } from "./ConstructorLogic.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; -contract DepositLogic is TransferLogic, RegistrationLogic { - using IdLib for uint96; - using IdLib for uint256; - using IdLib for address; - using EfficiencyLib for bool; - using ValidityLib for address; +contract DepositLogic is ConstructorLogic { using SafeTransferLib for address; uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; @@ -26,78 +13,6 @@ contract DepositLogic is TransferLogic, RegistrationLogic { /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; - function _performBasicNativeTokenDeposit(address allocator) internal returns (uint256 id) { - id = address(0).toIdIfRegistered(Scope.Multichain, ResetPeriod.TenMinutes, allocator); - - _deposit(msg.sender, id, msg.value); - } - - function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal { - _setReentrancyGuard(); - uint256 totalIds = idsAndAmounts.length; - bool firstUnderlyingTokenIsNative; - uint256 id; - - assembly ("memory-safe") { - let idsAndAmountsOffset := idsAndAmounts.offset - id := calldataload(idsAndAmountsOffset) - firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, id))) - // 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(totalIds), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(idsAndAmountsOffset, 0x20))))))) - { - // revert InvalidBatchDepositStructure() - mstore(0, 0xca0fc08e) - revert(0x1c, 0x04) - } - } - - uint96 currentAllocatorId = id.toRegisteredAllocatorId(); - - if (firstUnderlyingTokenIsNative) { - _deposit(recipient, id, msg.value); - } - - unchecked { - for (uint256 i = firstUnderlyingTokenIsNative.asUint256(); i < totalIds; ++i) { - uint256[2] calldata idAndAmount = idsAndAmounts[i]; - id = idAndAmount[0]; - uint256 amount = idAndAmount[1]; - - uint96 newAllocatorId = id.toAllocatorId(); - if (newAllocatorId != currentAllocatorId) { - newAllocatorId.mustHaveARegisteredAllocator(); - currentAllocatorId = newAllocatorId; - } - - _transferAndDeposit(id.toToken(), recipient, id, amount); - } - } - - _clearReentrancyGuard(); - } - - function _performBasicERC20Deposit(address token, address allocator, uint256 amount) internal returns (uint256 id) { - id = token.excludingNative().toIdIfRegistered(Scope.Multichain, ResetPeriod.TenMinutes, allocator); - - _transferAndDepositWithReentrancyGuard(token, msg.sender, id, amount); - } - - function _performCustomNativeTokenDeposit(address allocator, ResetPeriod resetPeriod, Scope scope, address recipient) internal returns (uint256 id) { - id = address(0).toIdIfRegistered(scope, resetPeriod, allocator); - - _deposit(recipient, id, msg.value); - } - - function _performCustomERC20Deposit(address token, address allocator, ResetPeriod resetPeriod, Scope scope, uint256 amount, address recipient) internal returns (uint256 id) { - id = token.excludingNative().toIdIfRegistered(scope, resetPeriod, allocator); - - _transferAndDepositWithReentrancyGuard(token, recipient, id, amount); - } - /// @dev Retrieves a token balance, compares against `initialBalance`, and mints the resulting balance /// change of `id` to `to`. Emits a {Transfer} event. function _checkBalanceAndDeposit(address token, address to, uint256 id, uint256 initialBalance) internal { @@ -143,24 +58,4 @@ contract DepositLogic is TransferLogic, RegistrationLogic { log4(0, 0x40, _TRANSFER_EVENT_SIGNATURE, 0, recipient, id) } } - - /// @dev Transfers `amount` of `token` and mints the resulting balance change of `id` to `to`. - /// Emits a {Transfer} event. - function _transferAndDeposit(address token, address to, uint256 id, uint256 amount) private { - uint256 initialBalance = token.balanceOf(address(this)); - - token.safeTransferFrom(msg.sender, address(this), amount); - - _checkBalanceAndDeposit(token, to, id, initialBalance); - } - - /// @dev Transfers `amount` of `token` and mints the resulting balance change of `id` to `to`. - /// Emits a {Transfer} event. - function _transferAndDepositWithReentrancyGuard(address token, address to, uint256 id, uint256 amount) private { - _setReentrancyGuard(); - - _transferAndDeposit(token, to, id, amount); - - _clearReentrancyGuard(); - } } diff --git a/src/lib/DepositViaPermit2Logic.sol b/src/lib/DepositViaPermit2Logic.sol index f8bfe08..8b9ca8f 100644 --- a/src/lib/DepositViaPermit2Logic.sol +++ b/src/lib/DepositViaPermit2Logic.sol @@ -43,6 +43,7 @@ import { ResetPeriod } from "../types/ResetPeriod.sol"; import { Scope } from "../types/Scope.sol"; import { DepositLogic } from "./DepositLogic.sol"; +import { RegistrationLogic } from "./RegistrationLogic.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { IdLib } from "./IdLib.sol"; import { ValidityLib } from "./ValidityLib.sol"; @@ -50,7 +51,7 @@ import { ValidityLib } from "./ValidityLib.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; -contract DepositViaPermit2Logic is DepositLogic { +contract DepositViaPermit2Logic is DepositLogic, RegistrationLogic { using IdLib for uint256; using IdLib for address; using IdLib for ResetPeriod; diff --git a/src/lib/DirectDepositLogic.sol b/src/lib/DirectDepositLogic.sol new file mode 100644 index 0000000..02dd07d --- /dev/null +++ b/src/lib/DirectDepositLogic.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { ResetPeriod } from "../types/ResetPeriod.sol"; +import { Scope } from "../types/Scope.sol"; + +import { EfficiencyLib } from "./EfficiencyLib.sol"; +import { IdLib } from "./IdLib.sol"; +import { DepositLogic } from "./DepositLogic.sol"; +import { ValidityLib } from "./ValidityLib.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; + +contract DirectDepositLogic is DepositLogic { + using IdLib for uint96; + using IdLib for uint256; + using IdLib for address; + using EfficiencyLib for bool; + using ValidityLib for address; + using SafeTransferLib for address; + + function _performBasicNativeTokenDeposit(address allocator) internal returns (uint256 id) { + id = address(0).toIdIfRegistered(Scope.Multichain, ResetPeriod.TenMinutes, allocator); + + _deposit(msg.sender, id, msg.value); + } + + function _processBatchDeposit(uint256[2][] calldata idsAndAmounts, address recipient) internal { + _setReentrancyGuard(); + uint256 totalIds = idsAndAmounts.length; + bool firstUnderlyingTokenIsNative; + uint256 id; + + assembly ("memory-safe") { + let idsAndAmountsOffset := idsAndAmounts.offset + id := calldataload(idsAndAmountsOffset) + firstUnderlyingTokenIsNative := iszero(shr(96, shl(96, id))) + // 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(totalIds), or(eq(firstUnderlyingTokenIsNative, iszero(callvalue())), and(firstUnderlyingTokenIsNative, iszero(eq(callvalue(), calldataload(add(idsAndAmountsOffset, 0x20))))))) + { + // revert InvalidBatchDepositStructure() + mstore(0, 0xca0fc08e) + revert(0x1c, 0x04) + } + } + + uint96 currentAllocatorId = id.toRegisteredAllocatorId(); + + if (firstUnderlyingTokenIsNative) { + _deposit(recipient, id, msg.value); + } + + unchecked { + for (uint256 i = firstUnderlyingTokenIsNative.asUint256(); i < totalIds; ++i) { + uint256[2] calldata idAndAmount = idsAndAmounts[i]; + id = idAndAmount[0]; + uint256 amount = idAndAmount[1]; + + uint96 newAllocatorId = id.toAllocatorId(); + if (newAllocatorId != currentAllocatorId) { + newAllocatorId.mustHaveARegisteredAllocator(); + currentAllocatorId = newAllocatorId; + } + + _transferAndDeposit(id.toToken(), recipient, id, amount); + } + } + + _clearReentrancyGuard(); + } + + function _performBasicERC20Deposit(address token, address allocator, uint256 amount) internal returns (uint256 id) { + id = token.excludingNative().toIdIfRegistered(Scope.Multichain, ResetPeriod.TenMinutes, allocator); + + _transferAndDepositWithReentrancyGuard(token, msg.sender, id, amount); + } + + function _performCustomNativeTokenDeposit(address allocator, ResetPeriod resetPeriod, Scope scope, address recipient) internal returns (uint256 id) { + id = address(0).toIdIfRegistered(scope, resetPeriod, allocator); + + _deposit(recipient, id, msg.value); + } + + function _performCustomERC20Deposit(address token, address allocator, ResetPeriod resetPeriod, Scope scope, uint256 amount, address recipient) internal returns (uint256 id) { + id = token.excludingNative().toIdIfRegistered(scope, resetPeriod, allocator); + + _transferAndDepositWithReentrancyGuard(token, recipient, id, amount); + } + + /// @dev Transfers `amount` of `token` and mints the resulting balance change of `id` to `to`. + /// Emits a {Transfer} event. + function _transferAndDeposit(address token, address to, uint256 id, uint256 amount) private { + uint256 initialBalance = token.balanceOf(address(this)); + + token.safeTransferFrom(msg.sender, address(this), amount); + + _checkBalanceAndDeposit(token, to, id, initialBalance); + } + + /// @dev Transfers `amount` of `token` and mints the resulting balance change of `id` to `to`. + /// Emits a {Transfer} event. + function _transferAndDepositWithReentrancyGuard(address token, address to, uint256 id, uint256 amount) private { + _setReentrancyGuard(); + + _transferAndDeposit(token, to, id, amount); + + _clearReentrancyGuard(); + } +} diff --git a/src/lib/SharedLogic.sol b/src/lib/SharedLogic.sol new file mode 100644 index 0000000..42ab5cf --- /dev/null +++ b/src/lib/SharedLogic.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { ConstructorLogic } from "./ConstructorLogic.sol"; +import { IdLib } from "./IdLib.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; + +contract SharedLogic is ConstructorLogic { + using IdLib for uint256; + using SafeTransferLib for address; + + /// @dev `keccak256(bytes("Claim(address,address,address,bytes32)"))`. + uint256 private constant _CLAIM_EVENT_SIGNATURE = 0x770c32a2314b700d6239ee35ba23a9690f2fceb93a55d8c753e953059b3b18d4; + + uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; + + /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. + uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; + + 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()) + } + } + + /// @dev Moves token `id` from `from` to `to` without checking + // allowances or _beforeTokenTransfer / _afterTokenTransfer hooks. + function _release(address from, address to, uint256 id, uint256 amount) internal returns (bool) { + assembly ("memory-safe") { + /// Compute the balance slot and load its value. + mstore(0x20, _ERC6909_MASTER_SLOT_SEED) + mstore(0x14, from) + mstore(0x00, id) + let fromBalanceSlot := keccak256(0x00, 0x40) + let fromBalance := sload(fromBalanceSlot) + // Revert if insufficient or zero balance. + if or(iszero(amount), gt(amount, fromBalance)) { + mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. + revert(0x1c, 0x04) + } + // Subtract and store the updated balance. + sstore(fromBalanceSlot, sub(fromBalance, amount)) + // Compute the balance slot of `to`. + mstore(0x14, to) + mstore(0x00, id) + let toBalanceSlot := keccak256(0x00, 0x40) + let toBalanceBefore := sload(toBalanceSlot) + let toBalanceAfter := add(toBalanceBefore, amount) + // Revert if the balance overflows. + if lt(toBalanceAfter, toBalanceBefore) { + mstore(0x00, 0x89560ca1) // `BalanceOverflow()`. + revert(0x1c, 0x04) + } + // Store the updated balance of `to`. + sstore(toBalanceSlot, toBalanceAfter) + // Emit the {Transfer} event. + mstore(0x00, caller()) + mstore(0x20, amount) + // forgefmt: disable-next-line + log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, shr(0x60, shl(0x60, from)), shr(0x60, shl(0x60, to)), id) + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x34, 0x00) + } + + return true; + } + + /// @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; + } +} diff --git a/src/lib/TheCompactLogic.sol b/src/lib/TheCompactLogic.sol new file mode 100644 index 0000000..db8eabf --- /dev/null +++ b/src/lib/TheCompactLogic.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { AllocatorLogic } from "./AllocatorLogic.sol"; +import { ClaimProcessor } from "./ClaimProcessor.sol"; +import { DepositViaPermit2Logic } from "./DepositViaPermit2Logic.sol"; +import { DirectDepositLogic } from "./DirectDepositLogic.sol"; +import { Extsload } from "./Extsload.sol"; +import { TransferLogic } from "./TransferLogic.sol"; +import { WithdrawalLogic } from "./WithdrawalLogic.sol"; + +contract TheCompactLogic is AllocatorLogic, ClaimProcessor, DepositViaPermit2Logic, DirectDepositLogic, Extsload, TransferLogic, WithdrawalLogic { } diff --git a/src/lib/TransferLogic.sol b/src/lib/TransferLogic.sol index 002d00b..4b05287 100644 --- a/src/lib/TransferLogic.sol +++ b/src/lib/TransferLogic.sol @@ -9,10 +9,10 @@ import { EfficiencyLib } from "./EfficiencyLib.sol"; import { FunctionCastLib } from "./FunctionCastLib.sol"; import { HashLib } from "./HashLib.sol"; import { IdLib } from "./IdLib.sol"; -import { ConstructorLogic } from "./ConstructorLogic.sol"; +import { SharedLogic } from "./SharedLogic.sol"; import { ValidityLib } from "./ValidityLib.sol"; -contract TransferLogic is ConstructorLogic { +contract TransferLogic is SharedLogic { using HashLib for BasicTransfer; using HashLib for SplitTransfer; using HashLib for BatchTransfer; @@ -24,23 +24,8 @@ contract TransferLogic is ConstructorLogic { using ValidityLib for bytes32; using FunctionCastLib for function(bytes32, address, BasicTransfer calldata) internal; - uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; - - /// @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; - uint32 private constant _ATTEST_SELECTOR = 0x1a808f91; - 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 _processBasicTransfer(BasicTransfer calldata transfer, function(address, address, uint256, uint256) internal returns (bool) operation) internal returns (bool) { _notExpiredAndSignedByAllocator(transfer.toMessageHash(), transfer.id.toRegisteredAllocatorWithConsumed(transfer.nonce), transfer); @@ -96,48 +81,6 @@ contract TransferLogic is ConstructorLogic { return true; } - /// @dev Moves token `id` from `from` to `to` without checking - // allowances or _beforeTokenTransfer / _afterTokenTransfer hooks. - function _release(address from, address to, uint256 id, uint256 amount) internal returns (bool) { - assembly ("memory-safe") { - /// Compute the balance slot and load its value. - mstore(0x20, _ERC6909_MASTER_SLOT_SEED) - mstore(0x14, from) - mstore(0x00, id) - let fromBalanceSlot := keccak256(0x00, 0x40) - let fromBalance := sload(fromBalanceSlot) - // Revert if insufficient or zero balance. - if or(iszero(amount), gt(amount, fromBalance)) { - mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. - revert(0x1c, 0x04) - } - // Subtract and store the updated balance. - sstore(fromBalanceSlot, sub(fromBalance, amount)) - // Compute the balance slot of `to`. - mstore(0x14, to) - mstore(0x00, id) - let toBalanceSlot := keccak256(0x00, 0x40) - let toBalanceBefore := sload(toBalanceSlot) - let toBalanceAfter := add(toBalanceBefore, amount) - // Revert if the balance overflows. - if lt(toBalanceAfter, toBalanceBefore) { - mstore(0x00, 0x89560ca1) // `BalanceOverflow()`. - revert(0x1c, 0x04) - } - // Store the updated balance of `to`. - sstore(toBalanceSlot, toBalanceAfter) - // Emit the {Transfer} event. - mstore(0x00, caller()) - mstore(0x20, amount) - // forgefmt: disable-next-line - log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, shr(0x60, shl(0x60, from)), shr(0x60, shl(0x60, to)), id) - // Restore the part of the free memory pointer that has been overwritten. - mstore(0x34, 0x00) - } - - return true; - } - function _ensureAttested(address from, address to, uint256 id, uint256 amount) internal { address allocator = id.toAllocator(); diff --git a/src/lib/WithdrawalLogic.sol b/src/lib/WithdrawalLogic.sol index ef75a69..6ba9e1a 100644 --- a/src/lib/WithdrawalLogic.sol +++ b/src/lib/WithdrawalLogic.sol @@ -4,22 +4,15 @@ pragma solidity ^0.8.27; import { ForcedWithdrawalStatus } from "../types/ForcedWithdrawalStatus.sol"; import { ResetPeriod } from "../types/ResetPeriod.sol"; -import { DepositViaPermit2Logic } from "./DepositViaPermit2Logic.sol"; +import { SharedLogic } from "./SharedLogic.sol"; import { EfficiencyLib } from "./EfficiencyLib.sol"; import { IdLib } from "./IdLib.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -contract WithdrawalLogic is DepositViaPermit2Logic { +contract WithdrawalLogic is SharedLogic { using IdLib for uint256; using IdLib for ResetPeriod; - using SafeTransferLib for address; using EfficiencyLib for uint256; - uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; - - /// @dev `keccak256(bytes("Transfer(address,address,address,uint256,uint256)"))`. - uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; - /// @dev `keccak256(bytes("ForcedWithdrawalStatusUpdated(address,uint256,bool,uint256)"))`. uint256 private constant _FORCED_WITHDRAWAL_STATUS_UPDATED_SIGNATURE = 0xe27f5e0382cf5347965fc81d5c81cd141897fe9ce402d22c496b7c2ddc84e5fd; @@ -76,52 +69,6 @@ contract WithdrawalLogic is DepositViaPermit2Logic { return _withdraw(msg.sender, recipient, id, amount); } - /// @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 _getForcedWithdrawalStatus(address account, uint256 id) internal view returns (ForcedWithdrawalStatus status, uint256 forcedWithdrawalAvailableAt) { uint256 cutoffTimeSlotLocation = _getCutoffTimeSlot(account, id); assembly ("memory-safe") {