Skip to content
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

Merged
merged 16 commits into from
Feb 11, 2019
Merged

feat: Add SafeERC20 #469

merged 16 commits into from
Feb 11, 2019

Conversation

sohkai
Copy link
Contributor

@sohkai sohkai commented Dec 17, 2018

Required for aragon/aragon-apps#562.

Adds a SafeERC20 library that should be used to enhance ERC20 types. The safeTransfer(), safeTransferFrom(), and safeApprove() 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 every AragonApp is affected.

Bytecode comparison:

                               CODE DEPOSIT COST    DEPLOYED BYTES     INITIALIZATION BYTES
ACL.json                       49200 more gas       +246               0
APMRegistry.json               50600 more gas       +253               0
AppStub.json                   49000 more gas       +245               0
AppStub2.json                  49200 more gas       +246               0
AppStubConditionalRecovery.js… 49200 more gas       +246               0
AppStubDepositable.json        49200 more gas       +246               0
AragonApp.json                 49200 more gas       +246               0
ENSSubdomainRegistrar.json     47600 more gas       +238               0
EVMScriptRegistry.json         47600 more gas       +238               0
EVMScriptRegistryFactory.json  Same                 0                  +238
Kernel.json                    48600 more gas       +243               0
KernelConstantsMock.json       48600 more gas       +243               0
KernelDepositableMock.json     48600 more gas       +243               0
KernelPinnedStorageMock.json   48600 more gas       +243               0
KernelSetAppMock.json          48600 more gas       +243               0
MockScriptExecutorApp.json     47800 more gas       +239               0
Repo.json                      49000 more gas       +245               0
TestACLInterpreter.json        49200 more gas       +246               0
TokenMock.json                 202400 more gas      +1012              +13
UnsafeAppStub.json             49000 more gas       +245               0
UnsafeAppStubDepositable.json  49200 more gas       +246               0
UnsafeAragonApp.json           49200 more gas       +246               0
UnsafeAragonAppMock.json       49200 more gas       +246               0
UnsafeRepo.json                49000 more gas       +245               0
UpgradedKernel.json            48600 more gas       +243               0
VaultMock.json                 49200 more gas       +246               0

Warning: could not compare some contracts that might have been renamed or removed
  Missing from /Users/brett/Development/aragon/aragonOS/bytecode: GeneralERC20.json, SafeERC20.json, SafeERC20Mock.json, TokenReturnFalseMock.json, TokenReturnMissingMock.json

ERC20(_token).transfer(vault, amount);
ERC20 token = ERC20(_token);
uint256 amount = token.balanceOf(this);
require(ERC20(_token).safeTransfer(vault, amount), ERROR_TOKEN_TRANSFER_FAILED);
Copy link
Contributor Author

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.

Copy link
Contributor

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.

@coveralls
Copy link

coveralls commented Dec 17, 2018

Coverage Status

Coverage increased (+0.006%) to 98.767% when pulling b26941b on safe-erc20 into c4e0fb1 on dev.

Copy link
Contributor

@bingen bingen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job!


// Same as ERC20 except the return values of these interfaces have been removed to allow us to
// manually check them
interface GeneralERC20 {
Copy link
Contributor

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 ;-)

Copy link
Contributor

@izqui izqui Dec 17, 2018

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')

}

// Not sure what was returned: don't mark as success
default { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice assembly 👏

@izqui
Copy link
Contributor

izqui commented Dec 17, 2018

Will review in detail closer to releasing this

@sohkai sohkai added this to the A1 Sprint: 3.1 milestone Jan 2, 2019
Copy link
Contributor

@izqui izqui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏👏👏👏

// 32 bytes returned: check if non-zero
case 0x20 {
// Copy 32 bytes into scratch space
returndatacopy(0x0, 0x0, 0x20)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

*/
function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) {
GeneralERC20(_token).transfer(_to, _amount);
return checkSuccess();
Copy link
Contributor

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).

@izqui izqui mentioned this pull request Jan 4, 2019
@sohkai sohkai modified the milestones: A1 Sprint: 3.1, A1 Sprint: 3.2 Jan 14, 2019
@sohkai sohkai modified the milestones: A1 Sprint: 3.1, A1 Sprint: 3.2 Jan 14, 2019
@sohkai sohkai modified the milestones: A1 Sprint: 3.2, A1 Sprint: 4.1 Feb 4, 2019
0x20 // uint256 return
)

if gt(success, 0) {
Copy link
Contributor Author

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.

@sohkai
Copy link
Contributor Author

sohkai commented Feb 8, 2019

@bingen @izqui Would be great if you could review SafeERC20 again, given the assembly changes for #469 (comment).

Turns out using call in assembly results in much, much smaller (~100 bytes) bytecode than address.call.

Copy link
Contributor

@bingen bingen left a 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!!!

{
title: 'standards compliant, reverting token',
tokenContract: TokenMock,
revertsOnFailure: true,
Copy link
Contributor

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?

Copy link
Contributor Author

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)!


function invokeAndCheckSuccess(address _addr, bytes memory _calldata)
private
returns (bool ret)
Copy link
Contributor

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

@sohkai sohkai merged commit a026e22 into dev Feb 11, 2019
@sohkai sohkai deleted the safe-erc20 branch February 11, 2019 23:08
sohkai added a commit that referenced this pull request Mar 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants