Merge pull request #2309 from 0xProject/feat/contracts/utils/LibERC20Token
LibERC20Token
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "2.3.0-beta.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Create `LibERC20Token`",
|
||||
"pr": 2309
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.3.0-beta.0",
|
||||
"changes": [
|
||||
|
||||
119
contracts/erc20/contracts/src/LibERC20Token.sol
Normal file
119
contracts/erc20/contracts/src/LibERC20Token.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
72
contracts/erc20/contracts/test/TestLibERC20Token.sol
Normal file
72
contracts/erc20/contracts/test/TestLibERC20Token.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
98
contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol
Normal file
98
contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol
Normal 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)) }
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
419
contracts/erc20/test/lib_erc20_token.ts
Normal file
419
contracts/erc20/test/lib_erc20_token.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user