Skip to content

Commit

Permalink
Create SafeTransfer.sol
Browse files Browse the repository at this point in the history
✨  SafeTransfer
  • Loading branch information
z0r0z authored Oct 21, 2022
1 parent e3d11f7 commit 5c8aff2
Showing 1 changed file with 149 additions and 0 deletions.
149 changes: 149 additions & 0 deletions src/utils/SafeTransfer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 free function transfer collection that gracefully handles missing return values.
/// @author SolDAO (https://github.com/Sol-DAO/solbase/blob/main/src/utils/SafeTransfer.sol)
/// @author Modified from Zolidity (https://github.com/z0r0z/zolidity/blob/main/src/utils/SafeTransfer.sol)

/// @dev The ETH transfer has failed.
error ETHTransferFailed();

/// @dev Sends `amount` (in wei) ETH to `to`.
/// Reverts upon failure.
function safeTransferETH(address to, uint256 amount) {
assembly {
// Transfer the ETH and check if it succeeded or not.
if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}

/// @dev The ERC20 `approve` has failed.
error ApproveFailed();

/// @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
) {
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)

// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0x00, 0x095ea7b3)
mstore(0x20, to) // Append the "to" argument.
mstore(0x40, amount) // Append the "amount" argument.

if iszero(
and(
// 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())),
// We use 0x44 because that's the total length of our calldata (0x04 + 0x20 * 2)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0x1c, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `ApproveFailed()`.
mstore(0x00, 0x3e3f8f73)
// Revert with (offset, size).
revert(0x1c, 0x04)
}

mstore(0x40, memPointer) // Restore the memPointer.
}
}

/// @dev The ERC20 `transfer` has failed.
error TransferFailed();

/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(
address token,
address to,
uint256 amount
) {
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)

// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0x00, 0xa9059cbb)
mstore(0x20, to) // Append the "to" argument.
mstore(0x40, amount) // Append the "amount" argument.

if iszero(
and(
// 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())),
// We use 0x44 because that's the total length of our calldata (0x04 + 0x20 * 2)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0x1c, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}

mstore(0x40, memPointer) // Restore the memPointer.
}
}

/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();

/// @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
) {
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)

// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0x00, 0x23b872dd)
mstore(0x20, from) // Append the "from" argument.
mstore(0x40, to) // Append the "to" argument.
mstore(0x60, amount) // Append the "amount" argument.

if iszero(
and(
// 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())),
// We use 0x64 because that's the total length of our calldata (0x04 + 0x20 * 3)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}

mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, memPointer) // Restore the memPointer.
}
}

0 comments on commit 5c8aff2

Please sign in to comment.