Merge pull request #2309 from 0xProject/feat/contracts/utils/LibERC20Token

LibERC20Token
This commit is contained in:
Lawrence Forman
2019-11-01 14:57:10 -04:00
committed by GitHub
17 changed files with 761 additions and 110 deletions

View File

@@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
import "../interfaces/IERC20Bridge.sol";
import "../interfaces/IEth2Dai.sol";
@@ -57,7 +58,7 @@ contract Eth2DaiBridge is
IEth2Dai exchange = _getEth2DaiContract();
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
IERC20Token(fromTokenAddress).approve(address(exchange), uint256(-1));
LibERC20Token.approve(fromTokenAddress, address(exchange), uint256(-1));
// Try to sell all of this contract's `fromTokenAddress` token balance.
uint256 boughtAmount = _getEth2DaiContract().sellAllAmount(
@@ -67,7 +68,7 @@ contract Eth2DaiBridge is
amount
);
// Transfer the converted `toToken`s to `to`.
_transferERC20Token(toTokenAddress, to, boughtAmount);
LibERC20Token.transfer(toTokenAddress, to, boughtAmount);
return BRIDGE_SUCCESS;
}
@@ -94,49 +95,4 @@ contract Eth2DaiBridge is
{
return IEth2Dai(ETH2DAI_ADDRESS);
}
/// @dev Permissively transfers an ERC20 token that may not adhere to
/// specs.
/// @param tokenAddress The token contract address.
/// @param to The token recipient.
/// @param amount The amount of tokens to transfer.
function _transferERC20Token(
address tokenAddress,
address to,
uint256 amount
)
private
{
// Transfer tokens.
// We do a raw call so we can check the success separate
// from the return data.
(bool didSucceed, bytes memory returnData) = tokenAddress.call(
abi.encodeWithSelector(
IERC20Token(0).transfer.selector,
to,
amount
)
);
if (!didSucceed) {
assembly { revert(add(returnData, 0x20), mload(returnData)) }
}
// Check return data.
// If there is no return data, we assume the token incorrectly
// does not return a bool. In this case we expect it to revert
// on failure, which was handled above.
// If the token does return data, we require that it is a single
// value that evaluates to true.
assembly {
if returndatasize {
didSucceed := 0
if eq(returndatasize, 32) {
// First 64 bytes of memory are reserved scratch space
returndatacopy(0, 0, 32)
didSucceed := mload(0)
}
}
}
require(didSucceed, "ERC20_TRANSFER_FAILED");
}
}

View File

@@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
import "../interfaces/IUniswapExchangeFactory.sol";
import "../interfaces/IUniswapExchange.sol";
@@ -77,7 +78,7 @@ contract UniswapBridge is
// Just transfer the tokens if they're the same.
if (fromTokenAddress == toTokenAddress) {
IERC20Token(fromTokenAddress).transfer(to, amount);
LibERC20Token.transfer(fromTokenAddress, to, amount);
return BRIDGE_SUCCESS;
}
@@ -189,7 +190,7 @@ contract UniswapBridge is
function _grantExchangeAllowance(IUniswapExchange exchange, address tokenAddress)
private
{
IERC20Token(tokenAddress).approve(address(exchange), uint256(-1));
LibERC20Token.approve(tokenAddress, address(exchange), uint256(-1));
}
/// @dev Retrieves the uniswap exchange for a given token pair.

View File

@@ -11,7 +11,7 @@ import {
TransactionHelper,
} from '@0x/contracts-test-utils';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { BigNumber, RawRevertError } from '@0x/utils';
import { DecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
@@ -179,14 +179,14 @@ blockchainTests.resets('Eth2DaiBridge unit tests', env => {
return expect(tx).to.revertWith(opts.toTokentransferRevertReason);
});
it('fails if `toTokenAddress.transfer()` returns falsey', async () => {
it('fails if `toTokenAddress.transfer()` returns false', async () => {
const opts = createWithdrawToOpts({ toTokenTransferReturnData: hexLeftPad(0) });
const tx = withdrawToAsync(opts);
return expect(tx).to.revertWith('ERC20_TRANSFER_FAILED');
return expect(tx).to.revertWith(new RawRevertError(hexLeftPad(0)));
});
it('succeeds if `toTokenAddress.transfer()` returns truthy', async () => {
await withdrawToAsync({ toTokenTransferReturnData: hexLeftPad(100) });
it('succeeds if `toTokenAddress.transfer()` returns true', async () => {
await withdrawToAsync({ toTokenTransferReturnData: hexLeftPad(1) });
});
});
});

View File

@@ -1,4 +1,13 @@
[
{
"version": "2.3.0-beta.1",
"changes": [
{
"note": "Create `LibERC20Token`",
"pr": 2309
}
]
},
{
"version": "2.3.0-beta.0",
"changes": [

View File

@@ -0,0 +1,119 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "../src/interfaces/IERC20Token.sol";
library LibERC20Token {
/// @dev Calls `IERC20Token(token).approve()`.
/// Reverts if `false` is returned or if the return
/// data length is nonzero and not 32 bytes.
/// @param token The address of the token contract.
/// @param spender The address that receives an allowance.
/// @param allowance The allowance to set.
function approve(
address token,
address spender,
uint256 allowance
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IERC20Token(0).approve.selector,
spender,
allowance
);
_callWithOptionalBooleanResult(token, callData);
}
/// @dev Calls `IERC20Token(token).transfer()`.
/// Reverts if `false` is returned or if the return
/// data length is nonzero and not 32 bytes.
/// @param token The address of the token contract.
/// @param to The address that receives the tokens
/// @param amount Number of tokens to transfer.
function transfer(
address token,
address to,
uint256 amount
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IERC20Token(0).transfer.selector,
to,
amount
);
_callWithOptionalBooleanResult(token, callData);
}
/// @dev Calls `IERC20Token(token).transferFrom()`.
/// Reverts if `false` is returned or if the return
/// data length is nonzero and not 32 bytes.
/// @param token The address of the token contract.
/// @param from The owner of the tokens.
/// @param to The address that receives the tokens
/// @param amount Number of tokens to transfer.
function transferFrom(
address token,
address from,
address to,
uint256 amount
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IERC20Token(0).transferFrom.selector,
from,
to,
amount
);
_callWithOptionalBooleanResult(token, callData);
}
/// @dev Executes a call on address `target` with calldata `callData`
/// and asserts that either nothing was returned or a single boolean
/// was returned equal to `true`.
/// @param target The call target.
/// @param callData The abi-encoded call data.
function _callWithOptionalBooleanResult(
address target,
bytes memory callData
)
private
{
(bool didSucceed, bytes memory resultData) = target.call(callData);
if (didSucceed) {
if (resultData.length == 0) {
return;
}
if (resultData.length == 32) {
uint256 result = LibBytes.readUint256(resultData, 0);
if (result == 1) {
return;
}
}
}
LibRichErrors.rrevert(resultData);
}
}

View File

@@ -0,0 +1,72 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "../src/LibERC20Token.sol";
import "./TestLibERC20TokenTarget.sol";
contract TestLibERC20Token {
TestLibERC20TokenTarget public target;
constructor() public {
target = new TestLibERC20TokenTarget();
}
function testApprove(
bool shouldRevert,
bytes calldata revertData,
bytes calldata returnData,
address spender,
uint256 allowance
)
external
{
target.setBehavior(shouldRevert, revertData, returnData);
LibERC20Token.approve(address(target), spender, allowance);
}
function testTransfer(
bool shouldRevert,
bytes calldata revertData,
bytes calldata returnData,
address to,
uint256 amount
)
external
{
target.setBehavior(shouldRevert, revertData, returnData);
LibERC20Token.transfer(address(target), to, amount);
}
function testTransferFrom(
bool shouldRevert,
bytes calldata revertData,
bytes calldata returnData,
address from,
address to,
uint256 amount
)
external
{
target.setBehavior(shouldRevert, revertData, returnData);
LibERC20Token.transferFrom(address(target), from, to, amount);
}
}

View File

@@ -0,0 +1,98 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
contract TestLibERC20TokenTarget {
event ApproveCalled(
address spender,
uint256 allowance
);
event TransferCalled(
address to,
uint256 amount
);
event TransferFromCalled(
address from,
address to,
uint256 amount
);
bool private _shouldRevert;
bytes private _revertData;
bytes private _returnData;
function setBehavior(
bool shouldRevert,
bytes calldata revertData,
bytes calldata returnData
)
external
{
_shouldRevert = shouldRevert;
_revertData = revertData;
_returnData = returnData;
}
function approve(
address spender,
uint256 allowance
)
external
returns (bool)
{
emit ApproveCalled(spender, allowance);
_execute();
}
function transfer(
address to,
uint256 amount
)
external
returns (bool)
{
emit TransferCalled(to, amount);
_execute();
}
function transferFrom(
address from,
address to,
uint256 amount
)
external
returns (bool)
{
emit TransferFromCalled(from, to, amount);
_execute();
}
function _execute() private view {
if (_shouldRevert) {
bytes memory revertData = _revertData;
assembly { revert(add(revertData, 0x20), mload(revertData)) }
}
bytes memory returnData = _returnData;
assembly { return(add(returnData, 0x20), mload(returnData)) }
}
}

View File

@@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "./generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IEtherToken|MintableERC20Token|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json",
"abis": "./generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IEtherToken|LibERC20Token|MintableERC20Token|TestLibERC20Token|TestLibERC20TokenTarget|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@@ -11,13 +11,17 @@ import * as DummyNoReturnERC20Token from '../generated-artifacts/DummyNoReturnER
import * as ERC20Token from '../generated-artifacts/ERC20Token.json';
import * as IERC20Token from '../generated-artifacts/IERC20Token.json';
import * as IEtherToken from '../generated-artifacts/IEtherToken.json';
import * as LibERC20Token from '../generated-artifacts/LibERC20Token.json';
import * as MintableERC20Token from '../generated-artifacts/MintableERC20Token.json';
import * as TestLibERC20Token from '../generated-artifacts/TestLibERC20Token.json';
import * as TestLibERC20TokenTarget from '../generated-artifacts/TestLibERC20TokenTarget.json';
import * as UnlimitedAllowanceERC20Token from '../generated-artifacts/UnlimitedAllowanceERC20Token.json';
import * as UntransferrableDummyERC20Token from '../generated-artifacts/UntransferrableDummyERC20Token.json';
import * as WETH9 from '../generated-artifacts/WETH9.json';
import * as ZRXToken from '../generated-artifacts/ZRXToken.json';
export const artifacts = {
ERC20Token: ERC20Token as ContractArtifact,
LibERC20Token: LibERC20Token as ContractArtifact,
MintableERC20Token: MintableERC20Token as ContractArtifact,
UnlimitedAllowanceERC20Token: UnlimitedAllowanceERC20Token as ContractArtifact,
WETH9: WETH9 as ContractArtifact,
@@ -27,5 +31,7 @@ export const artifacts = {
DummyERC20Token: DummyERC20Token as ContractArtifact,
DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,
DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact,
TestLibERC20Token: TestLibERC20Token as ContractArtifact,
TestLibERC20TokenTarget: TestLibERC20TokenTarget as ContractArtifact,
UntransferrableDummyERC20Token: UntransferrableDummyERC20Token as ContractArtifact,
};

View File

@@ -9,7 +9,10 @@ export * from '../generated-wrappers/dummy_no_return_erc20_token';
export * from '../generated-wrappers/erc20_token';
export * from '../generated-wrappers/i_erc20_token';
export * from '../generated-wrappers/i_ether_token';
export * from '../generated-wrappers/lib_erc20_token';
export * from '../generated-wrappers/mintable_erc20_token';
export * from '../generated-wrappers/test_lib_erc20_token';
export * from '../generated-wrappers/test_lib_erc20_token_target';
export * from '../generated-wrappers/unlimited_allowance_erc20_token';
export * from '../generated-wrappers/untransferrable_dummy_erc20_token';
export * from '../generated-wrappers/weth9';

View File

@@ -0,0 +1,419 @@
import {
blockchainTests,
constants,
expect,
getRandomInteger,
hexLeftPad,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { RawRevertError, StringRevertError } from '@0x/utils';
import { artifacts, TestLibERC20TokenContract, TestLibERC20TokenTargetEvents } from '../src';
blockchainTests('LibERC20Token', env => {
let testContract: TestLibERC20TokenContract;
const REVERT_STRING = 'WHOOPSIE';
const ENCODED_TRUE = hexLeftPad(1);
const ENCODED_FALSE = hexLeftPad(0);
const ENCODED_TWO = hexLeftPad(2);
const ENCODED_SHORT_TRUE = hexLeftPad(2, 31);
const ENCODED_LONG_TRUE = hexLeftPad(2, 33);
before(async () => {
testContract = await TestLibERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestLibERC20Token,
env.provider,
env.txDefaults,
artifacts,
);
});
function encodeRevert(message: string): string {
return new StringRevertError(message).encode();
}
describe('approve()', () => {
it('calls the target with the correct arguments', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const { logs } = await testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
spender,
allowance,
);
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled);
});
it('succeeds if the target returns true', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
await testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
spender,
allowance,
);
});
it('succeeds if the target returns nothing', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
await testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
constants.NULL_BYTES,
spender,
allowance,
);
});
it('fails if the target returns false', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_FALSE,
spender,
allowance,
);
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TWO,
spender,
allowance,
);
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_SHORT_TRUE,
spender,
allowance,
);
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_LONG_TRUE,
spender,
allowance,
);
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
true,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
spender,
allowance,
);
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18);
const tx = testContract.testApprove.awaitTransactionSuccessAsync(
true,
constants.NULL_BYTES,
ENCODED_TRUE,
spender,
allowance,
);
return expect(tx).to.be.rejectedWith('revert');
});
});
describe('transfer()', () => {
it('calls the target with the correct arguments', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
to,
amount,
);
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled);
});
it('succeeds if the target returns true', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
to,
amount,
);
});
it('succeeds if the target returns nothing', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
constants.NULL_BYTES,
to,
amount,
);
});
it('fails if the target returns false', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_FALSE,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TWO,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_SHORT_TRUE,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_LONG_TRUE,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
true,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
to,
amount,
);
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
true,
constants.NULL_BYTES,
ENCODED_TRUE,
to,
amount,
);
return expect(tx).to.be.rejectedWith('revert');
});
});
describe('transferFrom()', () => {
it('calls the target with the correct arguments', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
owner,
to,
amount,
);
expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled);
});
it('succeeds if the target returns true', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
owner,
to,
amount,
);
});
it('succeeds if the target returns nothing', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
await testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
constants.NULL_BYTES,
owner,
to,
amount,
);
});
it('fails if the target returns false', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_FALSE,
owner,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns nonzero and not true', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_TWO,
owner,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns less than 32 bytes', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_SHORT_TRUE,
owner,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target returns greater than 32 bytes', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
false,
encodeRevert(REVERT_STRING),
ENCODED_LONG_TRUE,
owner,
to,
amount,
);
const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError);
});
it('fails if the target reverts', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
true,
encodeRevert(REVERT_STRING),
ENCODED_TRUE,
owner,
to,
amount,
);
return expect(tx).to.revertWith(REVERT_STRING);
});
it('fails if the target reverts with no data', async () => {
const owner = randomAddress();
const to = randomAddress();
const amount = getRandomInteger(0, 100e18);
const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
true,
constants.NULL_BYTES,
ENCODED_TRUE,
owner,
to,
amount,
);
return expect(tx).to.be.rejectedWith('revert');
});
});
});

View File

@@ -9,7 +9,10 @@
"generated-artifacts/ERC20Token.json",
"generated-artifacts/IERC20Token.json",
"generated-artifacts/IEtherToken.json",
"generated-artifacts/LibERC20Token.json",
"generated-artifacts/MintableERC20Token.json",
"generated-artifacts/TestLibERC20Token.json",
"generated-artifacts/TestLibERC20TokenTarget.json",
"generated-artifacts/UnlimitedAllowanceERC20Token.json",
"generated-artifacts/UntransferrableDummyERC20Token.json",
"generated-artifacts/WETH9.json",

View File

@@ -1,4 +1,13 @@
[
{
"version": "3.1.0-beta.1",
"changes": [
{
"note": "Use `LibERC20Token` in `MixinAssets`",
"pr": 2309
}
]
},
{
"version": "3.1.0-beta.0",
"changes": [

View File

@@ -22,6 +22,7 @@ import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
@@ -105,38 +106,8 @@ contract MixinAssets is
internal
{
address token = assetData.readAddress(16);
// Transfer tokens.
// We do a raw call so we can check the success separate
// from the return data.
(bool success, bytes memory returnData) = token.call(abi.encodeWithSelector(
ERC20_TRANSFER_SELECTOR,
msg.sender,
amount
));
if (!success) {
LibRichErrors.rrevert(LibForwarderRichErrors.TransferFailedError(returnData));
}
// Check return data.
// If there is no return data, we assume the token incorrectly
// does not return a bool. In this case we expect it to revert
// on failure, which was handled above.
// If the token does return data, we require that it is a single
// value that evaluates to true.
assembly {
if returndatasize {
success := 0
if eq(returndatasize, 32) {
// First 64 bytes of memory are reserved scratch space
returndatacopy(0, 0, 32)
success := mload(0)
}
}
}
if (!success) {
LibRichErrors.rrevert(LibForwarderRichErrors.TransferFailedError(returnData));
}
LibERC20Token.transfer(token, msg.sender, amount);
}
/// @dev Decodes ERC721 assetData and transfers given amount to sender.

View File

@@ -51,10 +51,6 @@ library LibForwarderRichErrors {
bytes4 internal constant OVERSPENT_WETH_ERROR_SELECTOR =
0xcdcbed5d;
// bytes4(keccak256("TransferFailedError(bytes)"))
bytes4 internal constant TRANSFER_FAILED_ERROR_SELECTOR =
0x5e7eb60f;
// bytes4(keccak256("DefaultFunctionWethContractOnlyError(address)"))
bytes4 internal constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR =
0x08b18698;
@@ -160,19 +156,6 @@ library LibForwarderRichErrors {
);
}
function TransferFailedError(
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
TRANSFER_FAILED_ERROR_SELECTOR,
errorData
);
}
function DefaultFunctionWethContractOnlyError(
address senderAddress
)

View File

@@ -1,4 +1,13 @@
[
{
"version": "8.5.0-beta.1",
"changes": [
{
"note": "Remove `TransferFailedError` from `ForwarderRevertErrors`.",
"pr": 2309
}
]
},
{
"version": "8.5.0-beta.0",
"changes": [

View File

@@ -60,12 +60,6 @@ export class OverspentWethError extends RevertError {
}
}
export class TransferFailedError extends RevertError {
constructor(errorData?: string) {
super('TransferFailedError', 'TransferFailedError(bytes errorData)', { errorData });
}
}
export class DefaultFunctionWethContractOnlyError extends RevertError {
constructor(senderAddress?: string) {
super('DefaultFunctionWethContractOnlyError', 'DefaultFunctionWethContractOnlyError(address senderAddress)', {
@@ -96,7 +90,6 @@ const types = [
FeePercentageTooLargeError,
InsufficientEthForFeeError,
OverspentWethError,
TransferFailedError,
DefaultFunctionWethContractOnlyError,
MsgValueCannotEqualZeroError,
Erc721AmountMustEqualOneError,