-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add SafeERC20 #469
feat: Add SafeERC20 #469
Conversation
ERC20(_token).transfer(vault, amount); | ||
ERC20 token = ERC20(_token); | ||
uint256 amount = token.balanceOf(this); | ||
require(ERC20(_token).safeTransfer(vault, amount), ERROR_TOKEN_TRANSFER_FAILED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we should add this change at this moment—making sure the token transfer was successful is the right thing to do, but it's really only a problem for non-transferable tokens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job!
contracts/common/SafeERC20.sol
Outdated
|
||
// Same as ERC20 except the return values of these interfaces have been removed to allow us to | ||
// manually check them | ||
interface GeneralERC20 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would call it BrokenERC20 ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose NotERC20
or ANERC20
('accidentally not ERC20')
contracts/common/SafeERC20.sol
Outdated
} | ||
|
||
// Not sure what was returned: don't mark as success | ||
default { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice assembly 👏
Will review in detail closer to releasing this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏👏👏👏👏
contracts/common/SafeERC20.sol
Outdated
// 32 bytes returned: check if non-zero | ||
case 0x20 { | ||
// Copy 32 bytes into scratch space | ||
returndatacopy(0x0, 0x0, 0x20) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how by doing this optimization we save a few mload
and mstore
. Given that we read this memory just immediately after we should be okey using this memory address, but I always feel a little uneasy that something else may be counting on this memory space not changing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something else may be counting on this memory space not changing
I really hope not... 0x00 - 0x3f
is scratch space for doing keccaks, so you should never rely on storing something there unless you know you're going to retrieve it immediately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it looks like the scratch space could be used by solidity even in-between inline assembly statements, so it's not safe to ever use (except for throwaway values). Will use the free memory pointer.
import "../../lib/math/SafeMath.sol"; | ||
|
||
|
||
contract TokenReturnMissingMock { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe inherit from the GeneralERC20
interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this is necessary, especially as the other token mocks wouldn't inherit from it.
contracts/common/SafeERC20.sol
Outdated
*/ | ||
function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) { | ||
GeneralERC20(_token).transfer(_to, _amount); | ||
return checkSuccess(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually wondering whether all these 'safe' functions should provide a unified way to handle errors.
Right now depending on implementation details at the token level safeTransfer
can either revert or return false to communicate that the transfer has failed. I think it would be a much better API if the call was really safe and it always returned false on transfer errors (by doing a raw rather than using the standard Solidity function calling syntax that just reverts on reverts).
0x20 // uint256 return | ||
) | ||
|
||
if gt(success, 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out this is a micro optimization over switch
. In particular, the docs say call
will only ever return 1
on success, but everyone seems to use case 0
for revert (and everything else as success) and so this mimics that behaviour.
@bingen @izqui Would be great if you could review Turns out using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Really nice job!!!
test/recovery_to_vault.js
Outdated
{ | ||
title: 'standards compliant, reverting token', | ||
tokenContract: TokenMock, | ||
revertsOnFailure: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are these being used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good catch, I must've copied those over from the safe_erc20
tests and not used them (since everything should revert in VaultDepositable
)!
contracts/common/SafeERC20.sol
Outdated
|
||
function invokeAndCheckSuccess(address _addr, bytes memory _calldata) | ||
private | ||
returns (bool ret) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not use an implicit name return value for this, and I would explicitly have bool ret = false
in the function body and then have an explicit return ret
as it makes the function easier to understand
Required for aragon/aragon-apps#562.
Adds a
SafeERC20
library that should be used to enhanceERC20
types. ThesafeTransfer()
,safeTransferFrom()
, andsafeApprove()
methods ensure standards-compliance (e.g. always returning a value) with different token interfaces in the wild.Note that the change for
VaultRecoverable.sol
means everyAragonApp
is affected.Bytecode comparison: