|
| 1 | +// Inspired by AdEx (https://github.com/AdExNetwork/adex-protocol-eth/blob/b9df617829661a7518ee10f4cb6c4108659dd6d5/contracts/libs/SafeERC20.sol) |
| 2 | +// and 0x (https://github.com/0xProject/0x-monorepo/blob/737d1dc54d72872e24abce5a1dbe1b66d35fa21a/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol#L143) |
| 3 | + |
| 4 | +pragma solidity ^0.4.24; |
| 5 | + |
| 6 | +import "../lib/token/ERC20.sol"; |
| 7 | + |
| 8 | + |
| 9 | +library SafeERC20 { |
| 10 | + // Before 0.5, solidity has a mismatch between `address.transfer()` and `token.transfer()`: |
| 11 | + // https://github.com/ethereum/solidity/issues/3544 |
| 12 | + bytes4 private constant TRANSFER_SELECTOR = 0xa9059cbb; |
| 13 | + |
| 14 | + string private constant ERROR_TOKEN_BALANCE_REVERTED = "SAFE_ERC_20_BALANCE_REVERTED"; |
| 15 | + string private constant ERROR_TOKEN_ALLOWANCE_REVERTED = "SAFE_ERC_20_ALLOWANCE_REVERTED"; |
| 16 | + |
| 17 | + function invokeAndCheckSuccess(address _addr, bytes memory _calldata) |
| 18 | + private |
| 19 | + returns (bool) |
| 20 | + { |
| 21 | + bool ret; |
| 22 | + assembly { |
| 23 | + let ptr := mload(0x40) // free memory pointer |
| 24 | + |
| 25 | + let success := call( |
| 26 | + gas, // forward all gas |
| 27 | + _addr, // address |
| 28 | + 0, // no value |
| 29 | + add(_calldata, 0x20), // calldata start |
| 30 | + mload(_calldata), // calldata length |
| 31 | + ptr, // write output over free memory |
| 32 | + 0x20 // uint256 return |
| 33 | + ) |
| 34 | + |
| 35 | + if gt(success, 0) { |
| 36 | + // Check number of bytes returned from last function call |
| 37 | + switch returndatasize |
| 38 | + |
| 39 | + // No bytes returned: assume success |
| 40 | + case 0 { |
| 41 | + ret := 1 |
| 42 | + } |
| 43 | + |
| 44 | + // 32 bytes returned: check if non-zero |
| 45 | + case 0x20 { |
| 46 | + // Only return success if returned data was true |
| 47 | + // Already have output in ptr |
| 48 | + ret := eq(mload(ptr), 1) |
| 49 | + } |
| 50 | + |
| 51 | + // Not sure what was returned: don't mark as success |
| 52 | + default { } |
| 53 | + } |
| 54 | + } |
| 55 | + return ret; |
| 56 | + } |
| 57 | + |
| 58 | + function staticInvoke(address _addr, bytes memory _calldata) |
| 59 | + private |
| 60 | + view |
| 61 | + returns (bool, uint256) |
| 62 | + { |
| 63 | + bool success; |
| 64 | + uint256 ret; |
| 65 | + assembly { |
| 66 | + let ptr := mload(0x40) // free memory pointer |
| 67 | + |
| 68 | + success := staticcall( |
| 69 | + gas, // forward all gas |
| 70 | + _addr, // address |
| 71 | + add(_calldata, 0x20), // calldata start |
| 72 | + mload(_calldata), // calldata length |
| 73 | + ptr, // write output over free memory |
| 74 | + 0x20 // uint256 return |
| 75 | + ) |
| 76 | + |
| 77 | + if gt(success, 0) { |
| 78 | + ret := mload(ptr) |
| 79 | + } |
| 80 | + } |
| 81 | + return (success, ret); |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * @dev Same as a standards-compliant ERC20.transfer() that never reverts (returns false). |
| 86 | + * Note that this makes an external call to the token. |
| 87 | + */ |
| 88 | + function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) { |
| 89 | + bytes memory transferCallData = abi.encodeWithSelector( |
| 90 | + TRANSFER_SELECTOR, |
| 91 | + _to, |
| 92 | + _amount |
| 93 | + ); |
| 94 | + return invokeAndCheckSuccess(_token, transferCallData); |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * @dev Same as a standards-compliant ERC20.transferFrom() that never reverts (returns false). |
| 99 | + * Note that this makes an external call to the token. |
| 100 | + */ |
| 101 | + function safeTransferFrom(ERC20 _token, address _from, address _to, uint256 _amount) internal returns (bool) { |
| 102 | + bytes memory transferFromCallData = abi.encodeWithSelector( |
| 103 | + _token.transferFrom.selector, |
| 104 | + _from, |
| 105 | + _to, |
| 106 | + _amount |
| 107 | + ); |
| 108 | + return invokeAndCheckSuccess(_token, transferFromCallData); |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * @dev Same as a standards-compliant ERC20.approve() that never reverts (returns false). |
| 113 | + * Note that this makes an external call to the token. |
| 114 | + */ |
| 115 | + function safeApprove(ERC20 _token, address _spender, uint256 _amount) internal returns (bool) { |
| 116 | + bytes memory approveCallData = abi.encodeWithSelector( |
| 117 | + _token.approve.selector, |
| 118 | + _spender, |
| 119 | + _amount |
| 120 | + ); |
| 121 | + return invokeAndCheckSuccess(_token, approveCallData); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * @dev Static call into ERC20.balanceOf(). |
| 126 | + * Reverts if the call fails for some reason (should never fail). |
| 127 | + */ |
| 128 | + function staticBalanceOf(ERC20 _token, address _owner) internal view returns (uint256) { |
| 129 | + bytes memory balanceOfCallData = abi.encodeWithSelector( |
| 130 | + _token.balanceOf.selector, |
| 131 | + _owner |
| 132 | + ); |
| 133 | + |
| 134 | + (bool success, uint256 tokenBalance) = staticInvoke(_token, balanceOfCallData); |
| 135 | + require(success, ERROR_TOKEN_BALANCE_REVERTED); |
| 136 | + |
| 137 | + return tokenBalance; |
| 138 | + } |
| 139 | + |
| 140 | + /** |
| 141 | + * @dev Static call into ERC20.allowance(). |
| 142 | + * Reverts if the call fails for some reason (should never fail). |
| 143 | + */ |
| 144 | + function staticAllowance(ERC20 _token, address _owner, address _spender) internal view returns (uint256) { |
| 145 | + bytes memory allowanceCallData = abi.encodeWithSelector( |
| 146 | + _token.allowance.selector, |
| 147 | + _owner, |
| 148 | + _spender |
| 149 | + ); |
| 150 | + |
| 151 | + (bool success, uint256 allowance) = staticInvoke(_token, allowanceCallData); |
| 152 | + require(success, ERROR_TOKEN_ALLOWANCE_REVERTED); |
| 153 | + |
| 154 | + return allowance; |
| 155 | + } |
| 156 | +} |
0 commit comments