// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;

import { ERC20 } from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
  /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

  function safeTransferETH(address to, uint256 amount) internal {
    bool success;

    /// @solidity memory-safe-assembly
    assembly {
      // Transfer the ETH and store if it succeeded or not.
      success := call(gas(), to, amount, 0, 0, 0, 0)
    }

    require(success, "ETH_TRANSFER_FAILED");
  }

  /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

  function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal {
    bool success;

    /// @solidity memory-safe-assembly
    assembly {
      // Get a pointer to some free memory.
      let freeMemoryPointer := mload(0x40)

      // Write the abi-encoded calldata into memory, beginning with the function selector.
      mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
      mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
      mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
      mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

      // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
      // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
      success := call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)

      // 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 and token has code.
      if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
        success := iszero(or(iszero(extcodesize(token)), returndatasize()))
      }
    }

    require(success, "TRANSFER_FROM_FAILED");
  }

  function safeTransfer(ERC20 token, address to, uint256 amount) internal {
    bool success;

    /// @solidity memory-safe-assembly
    assembly {
      // Get a pointer to some free memory.
      let freeMemoryPointer := mload(0x40)

      // Write the abi-encoded calldata into memory, beginning with the function selector.
      mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
      mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
      mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

      // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
      // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
      success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)

      // 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 and token has code.
      if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
        success := iszero(or(iszero(extcodesize(token)), returndatasize()))
      }
    }

    require(success, "TRANSFER_FAILED");
  }

  function safeApprove(ERC20 token, address to, uint256 amount) internal {
    bool success;

    /// @solidity memory-safe-assembly
    assembly {
      // Get a pointer to some free memory.
      let freeMemoryPointer := mload(0x40)

      // Write the abi-encoded calldata into memory, beginning with the function selector.
      mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
      mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
      mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

      // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
      // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
      success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)

      // 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 and token has code.
      if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
        success := iszero(or(iszero(extcodesize(token)), returndatasize()))
      }
    }

    require(success, "APPROVE_FAILED");
  }
}
