forked from Sol-DAO/solbase
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ SafeTransfer
- Loading branch information
Showing
1 changed file
with
149 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
} | ||
} |