diff --git a/src/PimlicoERC20Paymaster.sol b/src/PimlicoERC20Paymaster.sol index cc67ff6..0bdaeca 100644 --- a/src/PimlicoERC20Paymaster.sol +++ b/src/PimlicoERC20Paymaster.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import "./interfaces/IOracle.sol"; import "@account-abstraction/contracts/core/EntryPoint.sol"; -import "solady/src/utils/SafeTransferLib.sol"; +import "./utils/SafeTransferLib.sol"; /// @title PimlicoERC20Paymaster /// @notice An ERC-4337 Paymaster contract by Pimlico which is able to sponsor gas fees in exchange for ERC20 tokens. diff --git a/src/utils/SafeTransferLib.sol b/src/utils/SafeTransferLib.sol new file mode 100644 index 0000000..b818242 --- /dev/null +++ b/src/utils/SafeTransferLib.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller. +library SafeTransferLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /// @dev The ERC20 `transferFrom` has failed. + error TransferFromFailed(); + + /// @dev The ERC20 `transfer` has failed. + error TransferFailed(); + + /// @dev The ERC20 `approve` has failed. + error ApproveFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Suggested gas stipend for contract receiving ETH + /// that disallows any storage writes. + uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300; + + /// @dev Suggested gas stipend for contract receiving ETH to perform a few + /// storage reads and writes, but low enough to prevent griefing. + /// Multiply by a small constant (e.g. 2), if needed. + uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC20 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for + /// the current contract to manage. + function safeTransferFrom(address token, address from, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + + mstore(0x60, amount) // Store the `amount` argument. + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + // Store the function selector of `transferFrom(address,address,uint256)`. + mstore(0x0c, 0x23b872dd000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends all of ERC20 `token` from `from` to `to`. + /// Reverts upon failure. + /// + /// The `from` account must have at least `amount` approved for + /// the current contract to manage. + function safeTransferAllFrom(address token, address from, address to) + internal + returns (uint256 amount) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + + mstore(0x40, to) // Store the `to` argument. + mstore(0x2c, shl(96, from)) // Store the `from` argument. + // Store the function selector of `balanceOf(address)`. + mstore(0x0c, 0x70a08231000000000000000000000000) + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + + // Store the function selector of `transferFrom(address,address,uint256)`. + mstore(0x00, 0x23b872dd) + // The `amount` argument is already written to the memory word at 0x6c. + amount := mload(0x60) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFromFailed()`. + mstore(0x00, 0x7939f424) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + + mstore(0x60, 0) // Restore the zero slot to zero. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransfer(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + // Store the function selector of `transfer(address,uint256)`. + mstore(0x00, 0xa9059cbb000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Sends all of ERC20 `token` from the current contract to `to`. + /// Reverts upon failure. + function safeTransferAll(address token, address to) internal returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. + mstore(0x20, address()) // Store the address of the current contract. + if iszero( + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + + mstore(0x14, to) // Store the `to` argument. + // The `amount` argument is already written to the memory word at 0x34. + amount := mload(0x34) + // Store the function selector of `transfer(address,uint256)`. + mstore(0x00, 0xa9059cbb000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `TransferFailed()`. + mstore(0x00, 0x90b8ec18) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. + /// Reverts upon failure. + function safeApprove(address token, address to, uint256 amount) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + // Store the function selector of `approve(address,uint256)`. + mstore(0x00, 0x095ea7b3000000000000000000000000) + + if iszero( + and( // The arguments of `and` are evaluated from right to left. + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0x00), 1), iszero(returndatasize())), + call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) + ) + ) { + // Store the function selector of `ApproveFailed()`. + mstore(0x00, 0x3e3f8f73) + // Revert with (offset, size). + revert(0x1c, 0x20) + } + // Restore the part of the free memory pointer that was overwritten. + mstore(0x34, 0) + } + } + + /// @dev Returns the amount of ERC20 `token` owned by `account`. + /// Returns zero if the `token` does not exist. + function balanceOf(address token, address account) internal view returns (uint256 amount) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x14, account) // Store the `account` argument. + // Store the function selector of `balanceOf(address)`. + mstore(0x00, 0x70a08231000000000000000000000000) + amount := + mul( + mload(0x20), + and( // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) + ) + ) + } + } +}