EP: misc fixes (#38)
* `@0x/contracts-zero-ex`: Fix NativeOrdersFeature order hash cancellation not emitting proper maker. `@0x/contracts-zero-ex`: Revert to original (deployed) ZeroEx/EP proxy implementation. Optimized one is now at `ZeroExOptimized.sol`. `@0x/contracts-zero-ex`: Add gas limits to first `transferFrom()` call in `LibTokenSpender` and `UniswapFeature`. * `@0x/contracts-zero-ex`: Update changelog * disable `no-empty-blocks` solidity linter rule * `@0x/contracts-zero-ex`: Use bloom filters of greedy tokens in token transfer logic `@0x/contracts-zero-ex`: Turn `LibTokenSpender` into `FixinTokenSpender`. `@0x/contracts-zero-ex`: Misc renames for consistency. * `@0x/contracts-zero-ex`: Export `GREEDY_TOKENS` list * rebase and update changelog * `@0x/contracts-zero-ex`: Change bloom filter hash algo based on discussions * `@0x/contracts-zero-ex`: Fix changelog * update orders docs * `@0x/contracts-zero-ex`: revert if allowance call fails in uniswap feature Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
b463a39bfa
commit
ab698cec14
@ -13,6 +13,7 @@
|
|||||||
"indent": ["error", 4],
|
"indent": ["error", 4],
|
||||||
"max-line-length": ["warn", 160],
|
"max-line-length": ["warn", 160],
|
||||||
"no-inline-assembly": false,
|
"no-inline-assembly": false,
|
||||||
|
"no-empty-blocks": false,
|
||||||
"quotes": ["error", "double"],
|
"quotes": ["error", "double"],
|
||||||
"separate-by-one-line-in-contract": "error",
|
"separate-by-one-line-in-contract": "error",
|
||||||
"space-after-comma": "error",
|
"space-after-comma": "error",
|
||||||
|
@ -1,4 +1,29 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.11.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Turn `LibTokenSpender` into `FixinTokenSpender`",
|
||||||
|
"pr": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Use bloom filters to check if a token is greedy and do not optimistically fall through transferFrom() if so",
|
||||||
|
"pr": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Revert to original proxy implementation",
|
||||||
|
"pr": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Fix incorrect cancel order event param",
|
||||||
|
"pr": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add a gas limit to first `LibTokenSpender` and `UniswapFeature` transfer",
|
||||||
|
"pr": 38
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
@ -9,6 +34,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Use new `checkAllowance` flag in LiquidityProviderFeature, TransformERC20Feature, and MetaTransactionsFeature",
|
"note": "Use new `checkAllowance` flag in LiquidityProviderFeature, TransformERC20Feature, and MetaTransactionsFeature",
|
||||||
"pr": 39
|
"pr": 39
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add native orders features",
|
||||||
|
"pr": 27
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timestamp": 1605763885
|
"timestamp": 1605763885
|
||||||
|
@ -19,12 +19,19 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||||
|
import "./migrations/LibBootstrap.sol";
|
||||||
import "./features/BootstrapFeature.sol";
|
import "./features/BootstrapFeature.sol";
|
||||||
import "./storage/LibProxyStorage.sol";
|
import "./storage/LibProxyStorage.sol";
|
||||||
|
import "./errors/LibProxyRichErrors.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev An extensible proxy contract that serves as a universal entry point for
|
/// @dev An extensible proxy contract that serves as a universal entry point for
|
||||||
/// interacting with the 0x protocol.
|
/// interacting with the 0x protocol.
|
||||||
contract ZeroEx {
|
contract ZeroEx {
|
||||||
|
// solhint-disable separate-by-one-line-in-contract,indent,var-name-mixedcase
|
||||||
|
using LibBytesV06 for bytes;
|
||||||
|
|
||||||
/// @dev Construct this contract and register the `BootstrapFeature` feature.
|
/// @dev Construct this contract and register the `BootstrapFeature` feature.
|
||||||
/// After constructing this contract, `bootstrap()` should be called
|
/// After constructing this contract, `bootstrap()` should be called
|
||||||
/// by `bootstrap()` to seed the initial feature set.
|
/// by `bootstrap()` to seed the initial feature set.
|
||||||
@ -37,55 +44,48 @@ contract ZeroEx {
|
|||||||
address(bootstrap);
|
address(bootstrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable state-visibility
|
// solhint-disable state-visibility
|
||||||
|
|
||||||
/// @dev Forwards calls to the appropriate implementation contract.
|
/// @dev Forwards calls to the appropriate implementation contract.
|
||||||
fallback() external payable {
|
fallback() external payable {
|
||||||
// This is used in assembly below as impls_slot.
|
bytes4 selector = msg.data.readBytes4(0);
|
||||||
mapping(bytes4 => address) storage impls =
|
address impl = getFunctionImplementation(selector);
|
||||||
LibProxyStorage.getStorage().impls;
|
if (impl == address(0)) {
|
||||||
|
_revertWithData(LibProxyRichErrors.NotImplementedError(selector));
|
||||||
assembly {
|
|
||||||
let cdlen := calldatasize()
|
|
||||||
|
|
||||||
// equivalent of receive() external payable {}
|
|
||||||
if iszero(cdlen) {
|
|
||||||
return(0, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store at 0x40, to leave 0x00-0x3F for slot calculation below.
|
(bool success, bytes memory resultData) = impl.delegatecall(msg.data);
|
||||||
calldatacopy(0x40, 0, cdlen)
|
if (!success) {
|
||||||
let selector := and(mload(0x40), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
|
_revertWithData(resultData);
|
||||||
|
}
|
||||||
// Slot for impls[selector] is keccak256(selector . impls_slot).
|
_returnWithData(resultData);
|
||||||
mstore(0, selector)
|
|
||||||
mstore(0x20, impls_slot)
|
|
||||||
let slot := keccak256(0, 0x40)
|
|
||||||
|
|
||||||
let delegate := sload(slot)
|
|
||||||
if iszero(delegate) {
|
|
||||||
// Revert with:
|
|
||||||
// abi.encodeWithSelector(
|
|
||||||
// bytes4(keccak256("NotImplementedError(bytes4)")),
|
|
||||||
// selector)
|
|
||||||
mstore(0, 0x734e6e1c00000000000000000000000000000000000000000000000000000000)
|
|
||||||
mstore(4, selector)
|
|
||||||
revert(0, 0x24)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let success := delegatecall(
|
/// @dev Fallback for just receiving ether.
|
||||||
gas(),
|
receive() external payable {}
|
||||||
delegate,
|
|
||||||
0x40, cdlen,
|
// solhint-enable state-visibility
|
||||||
0, 0
|
|
||||||
)
|
/// @dev Get the implementation contract of a registered function.
|
||||||
let rdlen := returndatasize()
|
/// @param selector The function selector.
|
||||||
returndatacopy(0, 0, rdlen)
|
/// @return impl The implementation contract address.
|
||||||
if success {
|
function getFunctionImplementation(bytes4 selector)
|
||||||
return(0, rdlen)
|
public
|
||||||
}
|
view
|
||||||
revert(0, rdlen)
|
returns (address impl)
|
||||||
|
{
|
||||||
|
return LibProxyStorage.getStorage().impls[selector];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Revert with arbitrary bytes.
|
||||||
|
/// @param data Revert data.
|
||||||
|
function _revertWithData(bytes memory data) private pure {
|
||||||
|
assembly { revert(add(data, 32), mload(data)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Return with arbitrary bytes.
|
||||||
|
/// @param data Return data.
|
||||||
|
function _returnWithData(bytes memory data) private pure {
|
||||||
|
assembly { return(add(data, 32), mload(data)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
91
contracts/zero-ex/contracts/src/ZeroExOptimized.sol
Normal file
91
contracts/zero-ex/contracts/src/ZeroExOptimized.sol
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2020 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.6.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "./features/BootstrapFeature.sol";
|
||||||
|
import "./storage/LibProxyStorage.sol";
|
||||||
|
|
||||||
|
/// @dev An extensible proxy contract that serves as a universal entry point for
|
||||||
|
/// interacting with the 0x protocol. Optimized version of ZeroEx.
|
||||||
|
contract ZeroExOptimized {
|
||||||
|
/// @dev Construct this contract and register the `BootstrapFeature` feature.
|
||||||
|
/// After constructing this contract, `bootstrap()` should be called
|
||||||
|
/// by `bootstrap()` to seed the initial feature set.
|
||||||
|
/// @param bootstrapper Who can call `bootstrap()`.
|
||||||
|
constructor(address bootstrapper) public {
|
||||||
|
// Temporarily create and register the bootstrap feature.
|
||||||
|
// It will deregister itself after `bootstrap()` has been called.
|
||||||
|
BootstrapFeature bootstrap = new BootstrapFeature(bootstrapper);
|
||||||
|
LibProxyStorage.getStorage().impls[bootstrap.bootstrap.selector] =
|
||||||
|
address(bootstrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// solhint-disable state-visibility
|
||||||
|
|
||||||
|
/// @dev Forwards calls to the appropriate implementation contract.
|
||||||
|
fallback() external payable {
|
||||||
|
// This is used in assembly below as impls_slot.
|
||||||
|
mapping(bytes4 => address) storage impls =
|
||||||
|
LibProxyStorage.getStorage().impls;
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
let cdlen := calldatasize()
|
||||||
|
|
||||||
|
// equivalent of receive() external payable {}
|
||||||
|
if iszero(cdlen) {
|
||||||
|
return(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store at 0x40, to leave 0x00-0x3F for slot calculation below.
|
||||||
|
calldatacopy(0x40, 0, cdlen)
|
||||||
|
let selector := and(mload(0x40), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
|
||||||
|
|
||||||
|
// Slot for impls[selector] is keccak256(selector . impls_slot).
|
||||||
|
mstore(0, selector)
|
||||||
|
mstore(0x20, impls_slot)
|
||||||
|
let slot := keccak256(0, 0x40)
|
||||||
|
|
||||||
|
let delegate := sload(slot)
|
||||||
|
if iszero(delegate) {
|
||||||
|
// Revert with:
|
||||||
|
// abi.encodeWithSelector(
|
||||||
|
// bytes4(keccak256("NotImplementedError(bytes4)")),
|
||||||
|
// selector)
|
||||||
|
mstore(0, 0x734e6e1c00000000000000000000000000000000000000000000000000000000)
|
||||||
|
mstore(4, selector)
|
||||||
|
revert(0, 0x24)
|
||||||
|
}
|
||||||
|
|
||||||
|
let success := delegatecall(
|
||||||
|
gas(),
|
||||||
|
delegate,
|
||||||
|
0x40, cdlen,
|
||||||
|
0, 0
|
||||||
|
)
|
||||||
|
let rdlen := returndatasize()
|
||||||
|
returndatacopy(0, 0, rdlen)
|
||||||
|
if success {
|
||||||
|
return(0, rdlen)
|
||||||
|
}
|
||||||
|
revert(0, rdlen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,12 +57,4 @@ interface ISimpleFunctionRegistryFeature {
|
|||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (address impl);
|
returns (address impl);
|
||||||
|
|
||||||
/// @dev Get the implementation contract of a registered function.
|
|
||||||
/// @param selector The function selector.
|
|
||||||
/// @return impl The implementation contract address.
|
|
||||||
function getFunctionImplementation(bytes4 selector)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (address impl);
|
|
||||||
}
|
}
|
||||||
|
@ -26,16 +26,17 @@ import "../errors/LibLiquidityProviderRichErrors.sol";
|
|||||||
import "../external/ILiquidityProviderSandbox.sol";
|
import "../external/ILiquidityProviderSandbox.sol";
|
||||||
import "../external/LiquidityProviderSandbox.sol";
|
import "../external/LiquidityProviderSandbox.sol";
|
||||||
import "../fixins/FixinCommon.sol";
|
import "../fixins/FixinCommon.sol";
|
||||||
|
import "../fixins/FixinTokenSpender.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "./IFeature.sol";
|
import "./IFeature.sol";
|
||||||
import "./ILiquidityProviderFeature.sol";
|
import "./ILiquidityProviderFeature.sol";
|
||||||
import "./libs/LibTokenSpender.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract LiquidityProviderFeature is
|
contract LiquidityProviderFeature is
|
||||||
IFeature,
|
IFeature,
|
||||||
ILiquidityProviderFeature,
|
ILiquidityProviderFeature,
|
||||||
FixinCommon
|
FixinCommon,
|
||||||
|
FixinTokenSpender
|
||||||
{
|
{
|
||||||
using LibSafeMathV06 for uint256;
|
using LibSafeMathV06 for uint256;
|
||||||
using LibRichErrorsV06 for bytes;
|
using LibRichErrorsV06 for bytes;
|
||||||
@ -60,9 +61,10 @@ contract LiquidityProviderFeature is
|
|||||||
address recipient
|
address recipient
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor(LiquidityProviderSandbox sandbox_)
|
constructor(LiquidityProviderSandbox sandbox_, bytes32 greedyTokensBloomFilter)
|
||||||
public
|
public
|
||||||
FixinCommon()
|
FixinCommon()
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
{
|
{
|
||||||
sandbox = sandbox_;
|
sandbox = sandbox_;
|
||||||
}
|
}
|
||||||
@ -112,12 +114,11 @@ contract LiquidityProviderFeature is
|
|||||||
if (inputToken == ETH_TOKEN_ADDRESS) {
|
if (inputToken == ETH_TOKEN_ADDRESS) {
|
||||||
provider.transfer(sellAmount);
|
provider.transfer(sellAmount);
|
||||||
} else {
|
} else {
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
IERC20TokenV06(inputToken),
|
IERC20TokenV06(inputToken),
|
||||||
msg.sender,
|
msg.sender,
|
||||||
provider,
|
provider,
|
||||||
sellAmount,
|
sellAmount
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
|||||||
import "../errors/LibMetaTransactionsRichErrors.sol";
|
import "../errors/LibMetaTransactionsRichErrors.sol";
|
||||||
import "../fixins/FixinCommon.sol";
|
import "../fixins/FixinCommon.sol";
|
||||||
import "../fixins/FixinReentrancyGuard.sol";
|
import "../fixins/FixinReentrancyGuard.sol";
|
||||||
|
import "../fixins/FixinTokenSpender.sol";
|
||||||
import "../fixins/FixinEIP712.sol";
|
import "../fixins/FixinEIP712.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../storage/LibMetaTransactionsStorage.sol";
|
import "../storage/LibMetaTransactionsStorage.sol";
|
||||||
@ -33,8 +34,6 @@ import "./IMetaTransactionsFeature.sol";
|
|||||||
import "./ITransformERC20Feature.sol";
|
import "./ITransformERC20Feature.sol";
|
||||||
import "./ISignatureValidatorFeature.sol";
|
import "./ISignatureValidatorFeature.sol";
|
||||||
import "./IFeature.sol";
|
import "./IFeature.sol";
|
||||||
import "./libs/LibTokenSpender.sol";
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev MetaTransactions feature.
|
/// @dev MetaTransactions feature.
|
||||||
contract MetaTransactionsFeature is
|
contract MetaTransactionsFeature is
|
||||||
@ -42,7 +41,8 @@ contract MetaTransactionsFeature is
|
|||||||
IMetaTransactionsFeature,
|
IMetaTransactionsFeature,
|
||||||
FixinCommon,
|
FixinCommon,
|
||||||
FixinReentrancyGuard,
|
FixinReentrancyGuard,
|
||||||
FixinEIP712
|
FixinEIP712,
|
||||||
|
FixinTokenSpender
|
||||||
{
|
{
|
||||||
using LibBytesV06 for bytes;
|
using LibBytesV06 for bytes;
|
||||||
using LibRichErrorsV06 for bytes;
|
using LibRichErrorsV06 for bytes;
|
||||||
@ -105,10 +105,11 @@ contract MetaTransactionsFeature is
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(address zeroExAddress)
|
constructor(address zeroExAddress, bytes32 greedyTokensBloomFilter)
|
||||||
public
|
public
|
||||||
FixinCommon()
|
FixinCommon()
|
||||||
FixinEIP712(zeroExAddress)
|
FixinEIP712(zeroExAddress)
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
{
|
{
|
||||||
// solhint-disable-next-line no-empty-blocks
|
// solhint-disable-next-line no-empty-blocks
|
||||||
}
|
}
|
||||||
@ -279,12 +280,11 @@ contract MetaTransactionsFeature is
|
|||||||
|
|
||||||
// Pay the fee to the sender.
|
// Pay the fee to the sender.
|
||||||
if (mtx.feeAmount > 0) {
|
if (mtx.feeAmount > 0) {
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
mtx.feeToken,
|
mtx.feeToken,
|
||||||
mtx.signer,
|
mtx.signer,
|
||||||
sender,
|
sender,
|
||||||
mtx.feeAmount,
|
mtx.feeAmount
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|||||||
import "../fixins/FixinCommon.sol";
|
import "../fixins/FixinCommon.sol";
|
||||||
import "../fixins/FixinProtocolFees.sol";
|
import "../fixins/FixinProtocolFees.sol";
|
||||||
import "../fixins/FixinEIP712.sol";
|
import "../fixins/FixinEIP712.sol";
|
||||||
|
import "../fixins/FixinTokenSpender.sol";
|
||||||
import "../errors/LibNativeOrdersRichErrors.sol";
|
import "../errors/LibNativeOrdersRichErrors.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../storage/LibNativeOrdersStorage.sol";
|
import "../storage/LibNativeOrdersStorage.sol";
|
||||||
import "../vendor/v3/IStaking.sol";
|
import "../vendor/v3/IStaking.sol";
|
||||||
import "./libs/LibTokenSpender.sol";
|
|
||||||
import "./libs/LibSignature.sol";
|
import "./libs/LibSignature.sol";
|
||||||
import "./libs/LibNativeOrder.sol";
|
import "./libs/LibNativeOrder.sol";
|
||||||
import "./INativeOrdersFeature.sol";
|
import "./INativeOrdersFeature.sol";
|
||||||
@ -45,7 +45,8 @@ contract NativeOrdersFeature is
|
|||||||
INativeOrdersFeature,
|
INativeOrdersFeature,
|
||||||
FixinCommon,
|
FixinCommon,
|
||||||
FixinProtocolFees,
|
FixinProtocolFees,
|
||||||
FixinEIP712
|
FixinEIP712,
|
||||||
|
FixinTokenSpender
|
||||||
{
|
{
|
||||||
using LibSafeMathV06 for uint256;
|
using LibSafeMathV06 for uint256;
|
||||||
using LibSafeMathV06 for uint128;
|
using LibSafeMathV06 for uint128;
|
||||||
@ -107,11 +108,13 @@ contract NativeOrdersFeature is
|
|||||||
address zeroExAddress,
|
address zeroExAddress,
|
||||||
IEtherTokenV06 weth,
|
IEtherTokenV06 weth,
|
||||||
IStaking staking,
|
IStaking staking,
|
||||||
uint32 protocolFeeMultiplier
|
uint32 protocolFeeMultiplier,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
FixinEIP712(zeroExAddress)
|
FixinEIP712(zeroExAddress)
|
||||||
FixinProtocolFees(weth, staking, protocolFeeMultiplier)
|
FixinProtocolFees(weth, staking, protocolFeeMultiplier)
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
{
|
{
|
||||||
// solhint-disable no-empty-blocks
|
// solhint-disable no-empty-blocks
|
||||||
}
|
}
|
||||||
@ -733,7 +736,7 @@ contract NativeOrdersFeature is
|
|||||||
// a cancel. It's OK to cancel twice.
|
// a cancel. It's OK to cancel twice.
|
||||||
stor.orderHashToTakerTokenFilledAmount[orderHash] |= HIGH_BIT;
|
stor.orderHashToTakerTokenFilledAmount[orderHash] |= HIGH_BIT;
|
||||||
|
|
||||||
emit OrderCancelled(orderHash, msg.sender);
|
emit OrderCancelled(orderHash, maker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Fill a limit order. Private variant. Does not refund protocol fees.
|
/// @dev Fill a limit order. Private variant. Does not refund protocol fees.
|
||||||
@ -811,12 +814,11 @@ contract NativeOrdersFeature is
|
|||||||
params.order.takerAmount,
|
params.order.takerAmount,
|
||||||
params.order.takerTokenFeeAmount
|
params.order.takerTokenFeeAmount
|
||||||
));
|
));
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
params.order.takerToken,
|
params.order.takerToken,
|
||||||
params.taker,
|
params.taker,
|
||||||
params.order.feeRecipient,
|
params.order.feeRecipient,
|
||||||
uint256(results.takerTokenFeeFilledAmount),
|
uint256(results.takerTokenFeeFilledAmount)
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -948,21 +950,19 @@ contract NativeOrdersFeature is
|
|||||||
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
||||||
|
|
||||||
// Transfer taker -> maker.
|
// Transfer taker -> maker.
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
settleInfo.takerToken,
|
settleInfo.takerToken,
|
||||||
settleInfo.taker,
|
settleInfo.taker,
|
||||||
settleInfo.maker,
|
settleInfo.maker,
|
||||||
takerTokenFilledAmount,
|
takerTokenFilledAmount
|
||||||
false
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transfer maker -> taker.
|
// Transfer maker -> taker.
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
settleInfo.makerToken,
|
settleInfo.makerToken,
|
||||||
settleInfo.maker,
|
settleInfo.maker,
|
||||||
settleInfo.taker,
|
settleInfo.taker,
|
||||||
makerTokenFilledAmount,
|
makerTokenFilledAmount
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,6 @@ contract SimpleFunctionRegistryFeature is
|
|||||||
// Register getters.
|
// Register getters.
|
||||||
_extend(this.getRollbackLength.selector, _implementation);
|
_extend(this.getRollbackLength.selector, _implementation);
|
||||||
_extend(this.getRollbackEntryAtIndex.selector, _implementation);
|
_extend(this.getRollbackEntryAtIndex.selector, _implementation);
|
||||||
_extend(this.getFunctionImplementation.selector, _implementation);
|
|
||||||
return LibBootstrap.BOOTSTRAP_SUCCESS;
|
return LibBootstrap.BOOTSTRAP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,18 +151,6 @@ contract SimpleFunctionRegistryFeature is
|
|||||||
return LibSimpleFunctionRegistryStorage.getStorage().implHistory[selector][idx];
|
return LibSimpleFunctionRegistryStorage.getStorage().implHistory[selector][idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Get the implementation contract of a registered function.
|
|
||||||
/// @param selector The function selector.
|
|
||||||
/// @return impl The implementation contract address.
|
|
||||||
function getFunctionImplementation(bytes4 selector)
|
|
||||||
external
|
|
||||||
override
|
|
||||||
view
|
|
||||||
returns (address impl)
|
|
||||||
{
|
|
||||||
return LibProxyStorage.getStorage().impls[selector];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Register or replace a function.
|
/// @dev Register or replace a function.
|
||||||
/// @param selector The function selector.
|
/// @param selector The function selector.
|
||||||
/// @param impl The implementation contract for the function.
|
/// @param impl The implementation contract for the function.
|
||||||
|
@ -25,6 +25,7 @@ import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
|||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
import "../errors/LibTransformERC20RichErrors.sol";
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
import "../fixins/FixinCommon.sol";
|
import "../fixins/FixinCommon.sol";
|
||||||
|
import "../fixins/FixinTokenSpender.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../external/IFlashWallet.sol";
|
import "../external/IFlashWallet.sol";
|
||||||
import "../external/FlashWallet.sol";
|
import "../external/FlashWallet.sol";
|
||||||
@ -35,14 +36,14 @@ import "./libs/LibSignedCallData.sol";
|
|||||||
import "./ITransformERC20Feature.sol";
|
import "./ITransformERC20Feature.sol";
|
||||||
import "./IFeature.sol";
|
import "./IFeature.sol";
|
||||||
import "./ISignatureValidatorFeature.sol";
|
import "./ISignatureValidatorFeature.sol";
|
||||||
import "./libs/LibTokenSpender.sol";
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature to composably transform between ERC20 tokens.
|
/// @dev Feature to composably transform between ERC20 tokens.
|
||||||
contract TransformERC20Feature is
|
contract TransformERC20Feature is
|
||||||
IFeature,
|
IFeature,
|
||||||
ITransformERC20Feature,
|
ITransformERC20Feature,
|
||||||
FixinCommon
|
FixinCommon,
|
||||||
|
FixinTokenSpender
|
||||||
{
|
{
|
||||||
using LibSafeMathV06 for uint256;
|
using LibSafeMathV06 for uint256;
|
||||||
using LibRichErrorsV06 for bytes;
|
using LibRichErrorsV06 for bytes;
|
||||||
@ -60,6 +61,11 @@ contract TransformERC20Feature is
|
|||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 1);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 1);
|
||||||
|
|
||||||
|
constructor(bytes32 greedyTokensBloomFilter)
|
||||||
|
public
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
|
{}
|
||||||
|
|
||||||
/// @dev Initialize and register this feature.
|
/// @dev Initialize and register this feature.
|
||||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||||
/// @param transformerDeployer The trusted deployer for transformers.
|
/// @param transformerDeployer The trusted deployer for transformers.
|
||||||
@ -211,7 +217,7 @@ contract TransformERC20Feature is
|
|||||||
// If the input token amount is -1, transform the taker's entire
|
// If the input token amount is -1, transform the taker's entire
|
||||||
// spendable balance.
|
// spendable balance.
|
||||||
if (args.inputTokenAmount == uint256(-1)) {
|
if (args.inputTokenAmount == uint256(-1)) {
|
||||||
args.inputTokenAmount = LibTokenSpender.getSpendableERC20BalanceOf(
|
args.inputTokenAmount = _getSpendableERC20BalanceOf(
|
||||||
args.inputToken,
|
args.inputToken,
|
||||||
args.taker
|
args.taker
|
||||||
);
|
);
|
||||||
@ -317,12 +323,11 @@ contract TransformERC20Feature is
|
|||||||
// Transfer input tokens.
|
// Transfer input tokens.
|
||||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
||||||
// Token is not ETH, so pull ERC20 tokens.
|
// Token is not ETH, so pull ERC20 tokens.
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
inputToken,
|
inputToken,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount
|
||||||
true
|
|
||||||
);
|
);
|
||||||
} else if (msg.value < amount) {
|
} else if (msg.value < amount) {
|
||||||
// Token is ETH, so the caller must attach enough ETH to the call.
|
// Token is ETH, so the caller must attach enough ETH to the call.
|
||||||
|
@ -38,6 +38,8 @@ contract UniswapFeature is
|
|||||||
string public constant override FEATURE_NAME = "UniswapFeature";
|
string public constant override FEATURE_NAME = "UniswapFeature";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
||||||
|
/// @dev A bloom filter for tokens that consume all gas when `transferFrom()` fails.
|
||||||
|
bytes32 public immutable GREEDY_TOKENS_BLOOM_FILTER;
|
||||||
/// @dev WETH contract.
|
/// @dev WETH contract.
|
||||||
IEtherTokenV06 private immutable WETH;
|
IEtherTokenV06 private immutable WETH;
|
||||||
/// @dev AllowanceTarget instance.
|
/// @dev AllowanceTarget instance.
|
||||||
@ -66,6 +68,8 @@ contract UniswapFeature is
|
|||||||
uint256 constant private UNISWAP_PAIR_SWAP_CALL_SELECTOR_32 = 0x022c0d9f00000000000000000000000000000000000000000000000000000000;
|
uint256 constant private UNISWAP_PAIR_SWAP_CALL_SELECTOR_32 = 0x022c0d9f00000000000000000000000000000000000000000000000000000000;
|
||||||
// bytes4(keccak256("transferFrom(address,address,uint256)"))
|
// bytes4(keccak256("transferFrom(address,address,uint256)"))
|
||||||
uint256 constant private TRANSFER_FROM_CALL_SELECTOR_32 = 0x23b872dd00000000000000000000000000000000000000000000000000000000;
|
uint256 constant private TRANSFER_FROM_CALL_SELECTOR_32 = 0x23b872dd00000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("allowance(address,address)"))
|
||||||
|
uint256 constant private ALLOWANCE_CALL_SELECTOR_32 = 0xdd62ed3e00000000000000000000000000000000000000000000000000000000;
|
||||||
// bytes4(keccak256("withdraw(uint256)"))
|
// bytes4(keccak256("withdraw(uint256)"))
|
||||||
uint256 constant private WETH_WITHDRAW_CALL_SELECTOR_32 = 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000;
|
uint256 constant private WETH_WITHDRAW_CALL_SELECTOR_32 = 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000;
|
||||||
// bytes4(keccak256("deposit()"))
|
// bytes4(keccak256("deposit()"))
|
||||||
@ -76,9 +80,15 @@ contract UniswapFeature is
|
|||||||
/// @dev Construct this contract.
|
/// @dev Construct this contract.
|
||||||
/// @param weth The WETH contract.
|
/// @param weth The WETH contract.
|
||||||
/// @param allowanceTarget The AllowanceTarget contract.
|
/// @param allowanceTarget The AllowanceTarget contract.
|
||||||
constructor(IEtherTokenV06 weth, IAllowanceTarget allowanceTarget) public {
|
/// @param greedyTokensBloomFilter The bloom filter for greedy tokens.
|
||||||
|
constructor(
|
||||||
|
IEtherTokenV06 weth,
|
||||||
|
IAllowanceTarget allowanceTarget,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
|
) public {
|
||||||
WETH = weth;
|
WETH = weth;
|
||||||
ALLOWANCE_TARGET = allowanceTarget;
|
ALLOWANCE_TARGET = allowanceTarget;
|
||||||
|
GREEDY_TOKENS_BLOOM_FILTER = greedyTokensBloomFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Initialize and register this feature.
|
/// @dev Initialize and register this feature.
|
||||||
@ -114,6 +124,7 @@ contract UniswapFeature is
|
|||||||
// Load immutables onto the stack.
|
// Load immutables onto the stack.
|
||||||
IEtherTokenV06 weth = WETH;
|
IEtherTokenV06 weth = WETH;
|
||||||
IAllowanceTarget allowanceTarget = ALLOWANCE_TARGET;
|
IAllowanceTarget allowanceTarget = ALLOWANCE_TARGET;
|
||||||
|
bytes32 greedyTokensBloomFilter = GREEDY_TOKENS_BLOOM_FILTER;
|
||||||
|
|
||||||
// Store some vars in memory to get around stack limits.
|
// Store some vars in memory to get around stack limits.
|
||||||
assembly {
|
assembly {
|
||||||
@ -125,6 +136,8 @@ contract UniswapFeature is
|
|||||||
mstore(0xA40, weth)
|
mstore(0xA40, weth)
|
||||||
// mload(0xA60) == ALLOWANCE_TARGET
|
// mload(0xA60) == ALLOWANCE_TARGET
|
||||||
mstore(0xA60, allowanceTarget)
|
mstore(0xA60, allowanceTarget)
|
||||||
|
// mload(0xA80) == GREEDY_TOKENS_BLOOM_FILTER
|
||||||
|
mstore(0xA80, greedyTokensBloomFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,52 +168,10 @@ contract UniswapFeature is
|
|||||||
|
|
||||||
if iszero(i) {
|
if iszero(i) {
|
||||||
switch eq(sellToken, ETH_TOKEN_ADDRESS_32)
|
switch eq(sellToken, ETH_TOKEN_ADDRESS_32)
|
||||||
case 0 {
|
case 0 { // Not selling ETH. Selling an ERC20 instead.
|
||||||
// For the first pair we need to transfer sellTokens into the
|
// For the first pair we need to transfer sellTokens into the
|
||||||
// pair contract.
|
// pair contract.
|
||||||
mstore(0xB00, TRANSFER_FROM_CALL_SELECTOR_32)
|
moveTakerTokensTo(sellToken, pair, sellAmount)
|
||||||
mstore(0xB04, caller())
|
|
||||||
mstore(0xB24, pair)
|
|
||||||
mstore(0xB44, sellAmount)
|
|
||||||
|
|
||||||
// Copy only the first 32 bytes of return data. We
|
|
||||||
// only care about reading a boolean in the success
|
|
||||||
// case, and we discard the return data in the
|
|
||||||
// failure case.
|
|
||||||
let success := call(gas(), sellToken, 0, 0xB00, 0x64, 0xC00, 0x20)
|
|
||||||
|
|
||||||
let rdsize := returndatasize()
|
|
||||||
|
|
||||||
// Check for ERC20 success. ERC20 tokens should
|
|
||||||
// return a boolean, but some return nothing or
|
|
||||||
// extra data. We accept 0-length return data as
|
|
||||||
// success, or at least 32 bytes that starts with
|
|
||||||
// a 32-byte boolean true.
|
|
||||||
success := and(
|
|
||||||
success, // call itself succeeded
|
|
||||||
or(
|
|
||||||
iszero(rdsize), // no return data, or
|
|
||||||
and(
|
|
||||||
iszero(lt(rdsize, 32)), // at least 32 bytes
|
|
||||||
eq(mload(0xC00), 1) // starts with uint256(1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if iszero(success) {
|
|
||||||
// Try to fall back to the allowance target.
|
|
||||||
mstore(0xB00, ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32)
|
|
||||||
mstore(0xB04, sellToken)
|
|
||||||
mstore(0xB24, 0x40)
|
|
||||||
mstore(0xB44, 0x64)
|
|
||||||
mstore(0xB64, TRANSFER_FROM_CALL_SELECTOR_32)
|
|
||||||
mstore(0xB68, caller())
|
|
||||||
mstore(0xB88, pair)
|
|
||||||
mstore(0xBA8, sellAmount)
|
|
||||||
if iszero(call(gas(), mload(0xA60), 0, 0xB00, 0xC8, 0x00, 0x0)) {
|
|
||||||
bubbleRevert()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
default {
|
default {
|
||||||
// If selling ETH, we need to wrap it to WETH and transfer to the
|
// If selling ETH, we need to wrap it to WETH and transfer to the
|
||||||
@ -389,6 +360,108 @@ contract UniswapFeature is
|
|||||||
returndatacopy(0, 0, returndatasize())
|
returndatacopy(0, 0, returndatasize())
|
||||||
revert(0, returndatasize())
|
revert(0, returndatasize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move `amount` tokens from the taker/caller to `to`.
|
||||||
|
function moveTakerTokensTo(token, to, amount) {
|
||||||
|
|
||||||
|
// If the token is possibly greedy, we check the allowance rather
|
||||||
|
// than relying on letting the transferFrom() call fail and
|
||||||
|
// falling through to legacy allowance target because the token
|
||||||
|
// will eat all our gas.
|
||||||
|
if isTokenPossiblyGreedy(token) {
|
||||||
|
// Check if we have enough direct allowance by calling
|
||||||
|
// `token.allowance()``
|
||||||
|
mstore(0xB00, ALLOWANCE_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, caller())
|
||||||
|
mstore(0xB24, address())
|
||||||
|
let success := call(gas(), token, 0, 0xB00, 0x44, 0xC00, 0x20)
|
||||||
|
if iszero(success) {
|
||||||
|
// Call to allowance() failed.
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
// Call succeeded.
|
||||||
|
// Result is stored in 0xC00-0xC20.
|
||||||
|
if lt(mload(0xC00), amount) {
|
||||||
|
// We don't have enough direct allowance, so try
|
||||||
|
// going through the legacy allowance taregt.
|
||||||
|
moveTakerTokensToWithLegacyAllowanceTarget(token, to, amount)
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we will optimistically try to perform a `transferFrom()`
|
||||||
|
// directly then if it fails we will go through the legacy allowance target.
|
||||||
|
mstore(0xB00, TRANSFER_FROM_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, caller())
|
||||||
|
mstore(0xB24, to)
|
||||||
|
mstore(0xB44, amount)
|
||||||
|
|
||||||
|
let success := call(
|
||||||
|
// Cap the gas limit to prvent all gas being consumed
|
||||||
|
// if the token reverts.
|
||||||
|
gas(),
|
||||||
|
token,
|
||||||
|
0,
|
||||||
|
0xB00,
|
||||||
|
0x64,
|
||||||
|
0xC00,
|
||||||
|
// Copy only the first 32 bytes of return data. We
|
||||||
|
// only care about reading a boolean in the success
|
||||||
|
// case, and we discard the return data in the
|
||||||
|
// failure case.
|
||||||
|
0x20
|
||||||
|
)
|
||||||
|
|
||||||
|
let rdsize := returndatasize()
|
||||||
|
|
||||||
|
// Check for ERC20 success. ERC20 tokens should
|
||||||
|
// return a boolean, but some return nothing or
|
||||||
|
// extra data. We accept 0-length return data as
|
||||||
|
// success, or at least 32 bytes that starts with
|
||||||
|
// a 32-byte boolean true.
|
||||||
|
success := and(
|
||||||
|
success, // call itself succeeded
|
||||||
|
or(
|
||||||
|
iszero(rdsize), // no return data, or
|
||||||
|
and(
|
||||||
|
iszero(lt(rdsize, 32)), // at least 32 bytes
|
||||||
|
eq(mload(0xC00), 1) // starts with uint256(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if iszero(success) {
|
||||||
|
// Try to fall back to the allowance target.
|
||||||
|
moveTakerTokensToWithLegacyAllowanceTarget(token, to, amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move tokens by going through the legacy allowance target contract.
|
||||||
|
function moveTakerTokensToWithLegacyAllowanceTarget(token, to, amount) {
|
||||||
|
mstore(0xB00, ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, token)
|
||||||
|
mstore(0xB24, 0x40)
|
||||||
|
mstore(0xB44, 0x64)
|
||||||
|
mstore(0xB64, TRANSFER_FROM_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB68, caller())
|
||||||
|
mstore(0xB88, to)
|
||||||
|
mstore(0xBA8, amount)
|
||||||
|
if iszero(call(gas(), mload(0xA60), 0, 0xB00, 0xC8, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
// If this fall back failed, the swap will most likely fail
|
||||||
|
// so there's no need to validate the result.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a token possibly belongs to the GREEDY_TOKENS_BLOOM_FILTER
|
||||||
|
// bloom filter.
|
||||||
|
function isTokenPossiblyGreedy(token) -> isPossiblyGreedy {
|
||||||
|
// The hash is given by:
|
||||||
|
// (1 << (keccak256(token) % 256)) | (1 << (token % 256))
|
||||||
|
mstore(0, token)
|
||||||
|
let h := or(shl(mod(keccak256(0, 32), 256), 1), shl(mod(token, 256), 1))
|
||||||
|
isPossiblyGreedy := eq(and(h, mload(0xA80)), h)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revert if we bought too little.
|
// Revert if we bought too little.
|
||||||
|
@ -21,7 +21,6 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
import "../external/FeeCollector.sol";
|
import "../external/FeeCollector.sol";
|
||||||
import "../features/libs/LibTokenSpender.sol";
|
|
||||||
import "../vendor/v3/IStaking.sol";
|
import "../vendor/v3/IStaking.sol";
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,48 +19,64 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
import "../../errors/LibSpenderRichErrors.sol";
|
import "../features/ITokenSpenderFeature.sol";
|
||||||
import "../ITokenSpenderFeature.sol";
|
import "../errors/LibSpenderRichErrors.sol";
|
||||||
|
import "../external/FeeCollector.sol";
|
||||||
|
import "../vendor/v3/IStaking.sol";
|
||||||
|
import "../vendor/v3/IStaking.sol";
|
||||||
|
|
||||||
library LibTokenSpender {
|
|
||||||
|
/// @dev Helpers for moving tokens around.
|
||||||
|
abstract contract FixinTokenSpender {
|
||||||
using LibRichErrorsV06 for bytes;
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
// Mask of the lower 20 bytes of a bytes32.
|
// Mask of the lower 20 bytes of a bytes32.
|
||||||
uint256 constant private ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
|
uint256 constant private ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
|
||||||
|
/// @dev A bloom filter for tokens that consume all gas when `transferFrom()` fails.
|
||||||
|
bytes32 public immutable GREEDY_TOKENS_BLOOM_FILTER;
|
||||||
|
|
||||||
|
/// @param greedyTokensBloomFilter The bloom filter for all greedy tokens.
|
||||||
|
constructor(bytes32 greedyTokensBloomFilter)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
GREEDY_TOKENS_BLOOM_FILTER = greedyTokensBloomFilter;
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Transfers ERC20 tokens from `owner` to `to`.
|
/// @dev Transfers ERC20 tokens from `owner` to `to`.
|
||||||
/// @param token The token to spend.
|
/// @param token The token to spend.
|
||||||
/// @param owner The owner of the tokens.
|
/// @param owner The owner of the tokens.
|
||||||
/// @param to The recipient of the tokens.
|
/// @param to The recipient of the tokens.
|
||||||
/// @param amount The amount of `token` to transfer.
|
/// @param amount The amount of `token` to transfer.
|
||||||
/// @param checkAllowance Whether or not to check the owner's allowance
|
function _transferERC20Tokens(
|
||||||
/// prior to attempting the transfer.
|
|
||||||
function spendERC20Tokens(
|
|
||||||
IERC20TokenV06 token,
|
IERC20TokenV06 token,
|
||||||
address owner,
|
address owner,
|
||||||
address to,
|
address to,
|
||||||
uint256 amount,
|
uint256 amount
|
||||||
bool checkAllowance
|
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
{
|
{
|
||||||
bool success;
|
bool success;
|
||||||
bytes memory revertData;
|
bytes memory revertData;
|
||||||
|
|
||||||
require(address(token) != address(this), "LibTokenSpender/CANNOT_INVOKE_SELF");
|
require(address(token) != address(this), "FixinTokenSpender/CANNOT_INVOKE_SELF");
|
||||||
|
|
||||||
if (checkAllowance) {
|
// If the token eats all gas when failing, we do not want to perform
|
||||||
// If the owner doesn't have a sufficient allowance set on `address(this)`,
|
// optimistic fall through to the old AllowanceTarget contract if the
|
||||||
// try the old AllowanceTarget.
|
// direct transferFrom() fails.
|
||||||
|
if (_isTokenPossiblyGreedy(token)) {
|
||||||
|
// If the token does not have a direct allowance on us then we use
|
||||||
|
// the allowance target.
|
||||||
if (token.allowance(owner, address(this)) < amount) {
|
if (token.allowance(owner, address(this)) < amount) {
|
||||||
return ITokenSpenderFeature(address(this))._spendERC20Tokens(
|
_transferFromLegacyAllowanceTarget(
|
||||||
token,
|
token,
|
||||||
owner,
|
owner,
|
||||||
to,
|
to,
|
||||||
amount
|
amount,
|
||||||
|
""
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +89,15 @@ library LibTokenSpender {
|
|||||||
mstore(add(ptr, 0x24), and(to, ADDRESS_MASK))
|
mstore(add(ptr, 0x24), and(to, ADDRESS_MASK))
|
||||||
mstore(add(ptr, 0x44), amount)
|
mstore(add(ptr, 0x44), amount)
|
||||||
|
|
||||||
success := call(gas(), and(token, ADDRESS_MASK), 0, ptr, 0x64, 0, 0)
|
success := call(
|
||||||
|
gas(),
|
||||||
|
and(token, ADDRESS_MASK),
|
||||||
|
0,
|
||||||
|
ptr,
|
||||||
|
0x64,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
let rdsize := returndatasize()
|
let rdsize := returndatasize()
|
||||||
|
|
||||||
@ -104,25 +128,13 @@ library LibTokenSpender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// Try the old AllowanceTarget.
|
_transferFromLegacyAllowanceTarget(
|
||||||
try ITokenSpenderFeature(address(this))._spendERC20Tokens(
|
|
||||||
token,
|
token,
|
||||||
owner,
|
owner,
|
||||||
to,
|
to,
|
||||||
amount
|
|
||||||
) {
|
|
||||||
} catch {
|
|
||||||
// Bubble up the first error message. (In general, the fallback to the
|
|
||||||
// allowance target is opportunistic. We ignore the specific error
|
|
||||||
// message if it fails.)
|
|
||||||
LibSpenderRichErrors.SpenderERC20TransferFromFailedError(
|
|
||||||
address(token),
|
|
||||||
owner,
|
|
||||||
to,
|
|
||||||
amount,
|
amount,
|
||||||
revertData
|
revertData
|
||||||
).rrevert();
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +143,7 @@ library LibTokenSpender {
|
|||||||
/// @param token The token to spend.
|
/// @param token The token to spend.
|
||||||
/// @param owner The owner of the tokens.
|
/// @param owner The owner of the tokens.
|
||||||
/// @return amount The amount of tokens that can be pulled.
|
/// @return amount The amount of tokens that can be pulled.
|
||||||
function getSpendableERC20BalanceOf(
|
function _getSpendableERC20BalanceOf(
|
||||||
IERC20TokenV06 token,
|
IERC20TokenV06 token,
|
||||||
address owner
|
address owner
|
||||||
)
|
)
|
||||||
@ -144,4 +156,53 @@ library LibTokenSpender {
|
|||||||
token.balanceOf(owner)
|
token.balanceOf(owner)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Check if a token possibly belongs to the `GREEDY_TOKENS_BLOOM_FILTER`
|
||||||
|
/// bloom filter.
|
||||||
|
function _isTokenPossiblyGreedy(IERC20TokenV06 token)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (bool isPossiblyGreedy)
|
||||||
|
{
|
||||||
|
// The hash is given by:
|
||||||
|
// (1 << (keccak256(token) % 256)) | (1 << (token % 256))
|
||||||
|
bytes32 h;
|
||||||
|
assembly {
|
||||||
|
mstore(0, token)
|
||||||
|
h := or(shl(mod(keccak256(0, 32), 256), 1), shl(mod(token, 256), 1))
|
||||||
|
}
|
||||||
|
return (h & GREEDY_TOKENS_BLOOM_FILTER) == h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Transfer tokens using the legacy allowance target instead of
|
||||||
|
/// allowances directly set on the exchange proxy.
|
||||||
|
function _transferFromLegacyAllowanceTarget(
|
||||||
|
IERC20TokenV06 token,
|
||||||
|
address owner,
|
||||||
|
address to,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory initialRevertData
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
// Try the old AllowanceTarget.
|
||||||
|
try ITokenSpenderFeature(address(this))._spendERC20Tokens(
|
||||||
|
token,
|
||||||
|
owner,
|
||||||
|
to,
|
||||||
|
amount
|
||||||
|
) {
|
||||||
|
} catch (bytes memory revertData) {
|
||||||
|
// Bubble up the first error message. (In general, the fallback to the
|
||||||
|
// allowance target is opportunistic. We ignore the specific error
|
||||||
|
// message if it fails.)
|
||||||
|
LibSpenderRichErrors.SpenderERC20TransferFromFailedError(
|
||||||
|
address(token),
|
||||||
|
owner,
|
||||||
|
to,
|
||||||
|
amount,
|
||||||
|
initialRevertData.length != 0 ? initialRevertData : revertData
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -20,13 +20,19 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "../src/fixins/FixinTokenSpender.sol";
|
||||||
|
|
||||||
import "../src/features/libs/LibTokenSpender.sol";
|
contract TestFixinTokenSpender is
|
||||||
|
FixinTokenSpender
|
||||||
contract TestLibTokenSpender {
|
{
|
||||||
uint256 constant private TRIGGER_FALLBACK_SUCCESS_AMOUNT = 1340;
|
uint256 constant private TRIGGER_FALLBACK_SUCCESS_AMOUNT = 1340;
|
||||||
|
|
||||||
function spendERC20Tokens(
|
constructor(bytes32 greedyTokensBloomFilter)
|
||||||
|
public
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
|
{}
|
||||||
|
|
||||||
|
function transferERC20Tokens(
|
||||||
IERC20TokenV06 token,
|
IERC20TokenV06 token,
|
||||||
address owner,
|
address owner,
|
||||||
address to,
|
address to,
|
||||||
@ -34,12 +40,11 @@ contract TestLibTokenSpender {
|
|||||||
)
|
)
|
||||||
external
|
external
|
||||||
{
|
{
|
||||||
LibTokenSpender.spendERC20Tokens(
|
_transferERC20Tokens(
|
||||||
token,
|
token,
|
||||||
owner,
|
owner,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +78,14 @@ contract TestLibTokenSpender {
|
|||||||
view
|
view
|
||||||
returns (uint256)
|
returns (uint256)
|
||||||
{
|
{
|
||||||
return LibTokenSpender.getSpendableERC20BalanceOf(token, owner);
|
return _getSpendableERC20BalanceOf(token, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTokenPossiblyGreedy(IERC20TokenV06 token)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return _isTokenPossiblyGreedy(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,7 +22,6 @@ pragma experimental ABIEncoderV2;
|
|||||||
import "../src/ZeroEx.sol";
|
import "../src/ZeroEx.sol";
|
||||||
import "../src/features/IBootstrapFeature.sol";
|
import "../src/features/IBootstrapFeature.sol";
|
||||||
import "../src/migrations/InitialMigration.sol";
|
import "../src/migrations/InitialMigration.sol";
|
||||||
import "../src/features/SimpleFunctionRegistryFeature.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TestInitialMigration is
|
contract TestInitialMigration is
|
||||||
@ -46,7 +45,7 @@ contract TestInitialMigration is
|
|||||||
success = InitialMigration.bootstrap(owner, features);
|
success = InitialMigration.bootstrap(owner, features);
|
||||||
// Snoop the bootstrap feature contract.
|
// Snoop the bootstrap feature contract.
|
||||||
bootstrapFeature =
|
bootstrapFeature =
|
||||||
SimpleFunctionRegistryFeature(address(uint160(address(this))))
|
ZeroEx(address(uint160(address(this))))
|
||||||
.getFunctionImplementation(IBootstrapFeature.bootstrap.selector);
|
.getFunctionImplementation(IBootstrapFeature.bootstrap.selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ contract TestMetaTransactionsTransformERC20Feature is
|
|||||||
bytes callDataSignature
|
bytes callDataSignature
|
||||||
);
|
);
|
||||||
|
|
||||||
|
constructor() public TransformERC20Feature(0) {}
|
||||||
|
|
||||||
function _transformERC20(TransformERC20Args memory args)
|
function _transformERC20(TransformERC20Args memory args)
|
||||||
public
|
public
|
||||||
override
|
override
|
||||||
|
@ -42,6 +42,14 @@ contract TestMintableERC20Token {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function approveAs(address owner, address spender, uint256 amount)
|
||||||
|
external
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
allowance[owner][spender] = amount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function mint(address owner, uint256 amount)
|
function mint(address owner, uint256 amount)
|
||||||
external
|
external
|
||||||
virtual
|
virtual
|
||||||
|
@ -10,10 +10,17 @@ contract TestNativeOrdersFeature is
|
|||||||
address zeroExAddress,
|
address zeroExAddress,
|
||||||
IEtherTokenV06 weth,
|
IEtherTokenV06 weth,
|
||||||
IStaking staking,
|
IStaking staking,
|
||||||
uint32 protocolFeeMultiplier
|
uint32 protocolFeeMultiplier,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
NativeOrdersFeature(zeroExAddress, weth, staking, protocolFeeMultiplier)
|
NativeOrdersFeature(
|
||||||
|
zeroExAddress,
|
||||||
|
weth,
|
||||||
|
staking,
|
||||||
|
protocolFeeMultiplier,
|
||||||
|
greedyTokensBloomFilter
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// solhint-disable no-empty-blocks
|
// solhint-disable no-empty-blocks
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,12 @@ contract TestTokenSpenderERC20Token is
|
|||||||
uint256 constant private EXTRA_RETURN_TRUE_AMOUNT = 1341;
|
uint256 constant private EXTRA_RETURN_TRUE_AMOUNT = 1341;
|
||||||
uint256 constant private EXTRA_RETURN_FALSE_AMOUNT = 1342;
|
uint256 constant private EXTRA_RETURN_FALSE_AMOUNT = 1342;
|
||||||
|
|
||||||
|
bool private _isGreedyRevert;
|
||||||
|
|
||||||
|
function setGreedyRevert(bool isGreedy) external {
|
||||||
|
_isGreedyRevert = isGreedy;
|
||||||
|
}
|
||||||
|
|
||||||
function transferFrom(address from, address to, uint256 amount)
|
function transferFrom(address from, address to, uint256 amount)
|
||||||
public
|
public
|
||||||
override
|
override
|
||||||
@ -54,9 +60,11 @@ contract TestTokenSpenderERC20Token is
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (amount == REVERT_RETURN_AMOUNT) {
|
if (amount == REVERT_RETURN_AMOUNT) {
|
||||||
|
assert(!_isGreedyRevert);
|
||||||
revert("TestTokenSpenderERC20Token/Revert");
|
revert("TestTokenSpenderERC20Token/Revert");
|
||||||
}
|
}
|
||||||
if (amount == TRIGGER_FALLBACK_SUCCESS_AMOUNT) {
|
if (amount == TRIGGER_FALLBACK_SUCCESS_AMOUNT) {
|
||||||
|
assert(!_isGreedyRevert);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (amount == EXTRA_RETURN_TRUE_AMOUNT
|
if (amount == EXTRA_RETURN_TRUE_AMOUNT
|
||||||
|
@ -28,4 +28,6 @@ contract TestTransformERC20 is
|
|||||||
modifier onlySelf() override {
|
modifier onlySelf() override {
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor() public TransformERC20Feature(0) {}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature",
|
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpender|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLibTokenSpender|TestLiquidityProvider|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
|
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
41
contracts/zero-ex/src/bloom_filter_utils.ts
Normal file
41
contracts/zero-ex/src/bloom_filter_utils.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the bloom filter for a list of tokens.
|
||||||
|
* Used to filter greedy tokens in the exchange proxy.
|
||||||
|
*/
|
||||||
|
export function getTokenListBloomFilter(tokens: string[]): string {
|
||||||
|
let filter = hexUtils.leftPad(0);
|
||||||
|
for (const token of tokens) {
|
||||||
|
// (1 << (keccak256(token) % 256)) | (1 << (token % 256))
|
||||||
|
const a = hexUtils.toHex(new BigNumber(2).pow(new BigNumber(hexUtils.hash(hexUtils.leftPad(token))).mod(256)));
|
||||||
|
const b = hexUtils.toHex(new BigNumber(2).pow(new BigNumber(token).mod(256)));
|
||||||
|
filter = bitwiseOrWords(filter, bitwiseOrWords(a, b));
|
||||||
|
}
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitwise OR two hex words.
|
||||||
|
function bitwiseOrWords(a: string, b: string): string {
|
||||||
|
const aBits = hexWordToBitArray(a);
|
||||||
|
const bBits = hexWordToBitArray(b);
|
||||||
|
const resultBits = aBits.slice();
|
||||||
|
for (let i = 0; i < 256; ++i) {
|
||||||
|
// tslint:disable-next-line: no-bitwise
|
||||||
|
resultBits[i] |= bBits[i];
|
||||||
|
}
|
||||||
|
return bitArrayToHexWord(resultBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexWordToBitArray(hexWord: string): number[] {
|
||||||
|
// Covnert to a binary string.
|
||||||
|
const bin = new BigNumber(hexWord).toString(2);
|
||||||
|
// Convert to integers.
|
||||||
|
const bits = bin.split('').map(s => parseInt(s, 10));
|
||||||
|
// Left the binary string pad with zeroes.
|
||||||
|
return new Array(256 - bits.length).fill(0).concat(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bitArrayToHexWord(bits: number[]): string {
|
||||||
|
return hexUtils.leftPad(new BigNumber(`0b${bits.map(b => b.toString()).join('')}`));
|
||||||
|
}
|
21
contracts/zero-ex/src/constants.ts
Normal file
21
contracts/zero-ex/src/constants.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
|
||||||
|
// List of tokens that consume all gas when they fail.
|
||||||
|
export const GREEDY_TOKENS = [
|
||||||
|
// USDT
|
||||||
|
'0xdac17f958d2ee523a2206206994597c13d831ec7',
|
||||||
|
// LINK
|
||||||
|
'0x514910771af9ca656af840dff83e8264ecf986ca',
|
||||||
|
// ORAI
|
||||||
|
'0x4c11249814f11b9346808179cf06e71ac328c1b5',
|
||||||
|
// BNT
|
||||||
|
'0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c',
|
||||||
|
// LIT
|
||||||
|
'0x763fa6806e1acf68130d2d0f0df754c93cc546b2',
|
||||||
|
// KNC
|
||||||
|
'0xdd974d5c2e2928dea5f71b9825b8b646686bd200',
|
||||||
|
// DIP
|
||||||
|
'0xc719d010b63e5bbf2c0551872cd5316ed26acd83',
|
||||||
|
// MINI
|
||||||
|
'0x4d953cf077c0c95ba090226e59a18fcf97db44ec',
|
||||||
|
];
|
@ -36,6 +36,8 @@ export * from './signature_utils';
|
|||||||
export * from './orders';
|
export * from './orders';
|
||||||
export * from './eip712_utils';
|
export * from './eip712_utils';
|
||||||
export * from './revert_errors';
|
export * from './revert_errors';
|
||||||
|
export * from './bloom_filter_utils';
|
||||||
|
export { GREEDY_TOKENS } from './constants';
|
||||||
export {
|
export {
|
||||||
AffiliateFeeTransformerContract,
|
AffiliateFeeTransformerContract,
|
||||||
BridgeAdapterContract,
|
BridgeAdapterContract,
|
||||||
|
@ -5,6 +5,7 @@ import { TxData } from 'ethereum-types';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts } from './artifacts';
|
import { artifacts } from './artifacts';
|
||||||
|
import { ZERO_BYTES32 } from './constants';
|
||||||
import {
|
import {
|
||||||
FullMigrationContract,
|
FullMigrationContract,
|
||||||
InitialMigrationContract,
|
InitialMigrationContract,
|
||||||
@ -133,6 +134,7 @@ export interface FullFeaturesDeployConfig {
|
|||||||
wethAddress: string;
|
wethAddress: string;
|
||||||
stakingAddress: string;
|
stakingAddress: string;
|
||||||
protocolFeeMultiplier: number;
|
protocolFeeMultiplier: number;
|
||||||
|
greedyTokensBloomFilter: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,6 +149,7 @@ const DEFAULT_FULL_FEATURES_DEPLOY_CONFIG = {
|
|||||||
wethAddress: NULL_ADDRESS,
|
wethAddress: NULL_ADDRESS,
|
||||||
stakingAddress: NULL_ADDRESS,
|
stakingAddress: NULL_ADDRESS,
|
||||||
protocolFeeMultiplier: 70e3,
|
protocolFeeMultiplier: 70e3,
|
||||||
|
greedyTokensBloomFilter: ZERO_BYTES32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FULL_FEATURES_ARTIFACTS = {
|
const DEFAULT_FULL_FEATURES_ARTIFACTS = {
|
||||||
@ -189,6 +192,7 @@ export async function deployFullFeaturesAsync(
|
|||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
|
_config.greedyTokensBloomFilter,
|
||||||
)).address,
|
)).address,
|
||||||
signatureValidator:
|
signatureValidator:
|
||||||
features.signatureValidator ||
|
features.signatureValidator ||
|
||||||
@ -206,6 +210,7 @@ export async function deployFullFeaturesAsync(
|
|||||||
txDefaults,
|
txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
_config.zeroExAddress,
|
_config.zeroExAddress,
|
||||||
|
_config.greedyTokensBloomFilter,
|
||||||
)).address,
|
)).address,
|
||||||
nativeOrders:
|
nativeOrders:
|
||||||
features.nativeOrders ||
|
features.nativeOrders ||
|
||||||
@ -218,6 +223,7 @@ export async function deployFullFeaturesAsync(
|
|||||||
_config.wethAddress,
|
_config.wethAddress,
|
||||||
_config.stakingAddress,
|
_config.stakingAddress,
|
||||||
_config.protocolFeeMultiplier,
|
_config.protocolFeeMultiplier,
|
||||||
|
_config.greedyTokensBloomFilter,
|
||||||
)).address,
|
)).address,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
|
|||||||
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json';
|
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json';
|
||||||
import * as FixinProtocolFees from '../test/generated-artifacts/FixinProtocolFees.json';
|
import * as FixinProtocolFees from '../test/generated-artifacts/FixinProtocolFees.json';
|
||||||
import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentrancyGuard.json';
|
import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentrancyGuard.json';
|
||||||
|
import * as FixinTokenSpender from '../test/generated-artifacts/FixinTokenSpender.json';
|
||||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
||||||
@ -64,7 +65,6 @@ import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifact
|
|||||||
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
|
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
|
||||||
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
|
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
|
||||||
import * as LibStorage from '../test/generated-artifacts/LibStorage.json';
|
import * as LibStorage from '../test/generated-artifacts/LibStorage.json';
|
||||||
import * as LibTokenSpender from '../test/generated-artifacts/LibTokenSpender.json';
|
|
||||||
import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json';
|
import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json';
|
||||||
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
||||||
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
||||||
@ -98,11 +98,11 @@ import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/Tes
|
|||||||
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
|
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
|
||||||
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
||||||
import * as TestFixinProtocolFees from '../test/generated-artifacts/TestFixinProtocolFees.json';
|
import * as TestFixinProtocolFees from '../test/generated-artifacts/TestFixinProtocolFees.json';
|
||||||
|
import * as TestFixinTokenSpender from '../test/generated-artifacts/TestFixinTokenSpender.json';
|
||||||
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
||||||
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
||||||
import * as TestLibNativeOrder from '../test/generated-artifacts/TestLibNativeOrder.json';
|
import * as TestLibNativeOrder from '../test/generated-artifacts/TestLibNativeOrder.json';
|
||||||
import * as TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
|
import * as TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
|
||||||
import * as TestLibTokenSpender from '../test/generated-artifacts/TestLibTokenSpender.json';
|
|
||||||
import * as TestLiquidityProvider from '../test/generated-artifacts/TestLiquidityProvider.json';
|
import * as TestLiquidityProvider from '../test/generated-artifacts/TestLiquidityProvider.json';
|
||||||
import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json';
|
import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json';
|
||||||
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
||||||
@ -128,9 +128,11 @@ import * as TransformerDeployer from '../test/generated-artifacts/TransformerDep
|
|||||||
import * as UniswapFeature from '../test/generated-artifacts/UniswapFeature.json';
|
import * as UniswapFeature from '../test/generated-artifacts/UniswapFeature.json';
|
||||||
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
||||||
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
||||||
|
import * as ZeroExOptimized from '../test/generated-artifacts/ZeroExOptimized.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
IZeroEx: IZeroEx as ContractArtifact,
|
IZeroEx: IZeroEx as ContractArtifact,
|
||||||
ZeroEx: ZeroEx as ContractArtifact,
|
ZeroEx: ZeroEx as ContractArtifact,
|
||||||
|
ZeroExOptimized: ZeroExOptimized as ContractArtifact,
|
||||||
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||||
LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact,
|
LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact,
|
||||||
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
|
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
|
||||||
@ -174,11 +176,11 @@ export const artifacts = {
|
|||||||
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
||||||
LibSignature: LibSignature as ContractArtifact,
|
LibSignature: LibSignature as ContractArtifact,
|
||||||
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
||||||
LibTokenSpender: LibTokenSpender as ContractArtifact,
|
|
||||||
FixinCommon: FixinCommon as ContractArtifact,
|
FixinCommon: FixinCommon as ContractArtifact,
|
||||||
FixinEIP712: FixinEIP712 as ContractArtifact,
|
FixinEIP712: FixinEIP712 as ContractArtifact,
|
||||||
FixinProtocolFees: FixinProtocolFees as ContractArtifact,
|
FixinProtocolFees: FixinProtocolFees as ContractArtifact,
|
||||||
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
|
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
|
||||||
|
FixinTokenSpender: FixinTokenSpender as ContractArtifact,
|
||||||
FullMigration: FullMigration as ContractArtifact,
|
FullMigration: FullMigration as ContractArtifact,
|
||||||
InitialMigration: InitialMigration as ContractArtifact,
|
InitialMigration: InitialMigration as ContractArtifact,
|
||||||
LibBootstrap: LibBootstrap as ContractArtifact,
|
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||||
@ -229,11 +231,11 @@ export const artifacts = {
|
|||||||
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
||||||
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||||
TestFixinProtocolFees: TestFixinProtocolFees as ContractArtifact,
|
TestFixinProtocolFees: TestFixinProtocolFees as ContractArtifact,
|
||||||
|
TestFixinTokenSpender: TestFixinTokenSpender as ContractArtifact,
|
||||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||||
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||||
TestLibNativeOrder: TestLibNativeOrder as ContractArtifact,
|
TestLibNativeOrder: TestLibNativeOrder as ContractArtifact,
|
||||||
TestLibSignature: TestLibSignature as ContractArtifact,
|
TestLibSignature: TestLibSignature as ContractArtifact,
|
||||||
TestLibTokenSpender: TestLibTokenSpender as ContractArtifact,
|
|
||||||
TestLiquidityProvider: TestLiquidityProvider as ContractArtifact,
|
TestLiquidityProvider: TestLiquidityProvider as ContractArtifact,
|
||||||
TestMetaTransactionsTransformERC20Feature: TestMetaTransactionsTransformERC20Feature as ContractArtifact,
|
TestMetaTransactionsTransformERC20Feature: TestMetaTransactionsTransformERC20Feature as ContractArtifact,
|
||||||
TestMigrator: TestMigrator as ContractArtifact,
|
TestMigrator: TestMigrator as ContractArtifact,
|
||||||
|
@ -2,6 +2,7 @@ import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contra
|
|||||||
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
|
import { BigNumber, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
|
import { ZERO_BYTES32 } from '../../src/constants';
|
||||||
import { IOwnableFeatureContract, IZeroExContract, LiquidityProviderFeatureContract } from '../../src/wrappers';
|
import { IOwnableFeatureContract, IZeroExContract, LiquidityProviderFeatureContract } from '../../src/wrappers';
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { abis } from '../utils/abis';
|
import { abis } from '../utils/abis';
|
||||||
@ -64,6 +65,7 @@ blockchainTests('LiquidityProvider feature', env => {
|
|||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
sandbox.address,
|
sandbox.address,
|
||||||
|
ZERO_BYTES32,
|
||||||
);
|
);
|
||||||
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis)
|
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis)
|
||||||
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
||||||
|
@ -10,7 +10,7 @@ import { fullMigrateAsync } from '../utils/migration';
|
|||||||
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
|
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
|
||||||
import { TestMintableERC20TokenContract } from '../wrappers';
|
import { TestMintableERC20TokenContract } from '../wrappers';
|
||||||
|
|
||||||
blockchainTests.resets('LimitOrdersFeature', env => {
|
blockchainTests.resets('NativeOrdersFeature', env => {
|
||||||
const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT } = constants;
|
const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||||
const GAS_PRICE = new BigNumber('123e9');
|
const GAS_PRICE = new BigNumber('123e9');
|
||||||
const PROTOCOL_FEE_MULTIPLIER = 1337e3;
|
const PROTOCOL_FEE_MULTIPLIER = 1337e3;
|
@ -66,7 +66,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
|
|||||||
|
|
||||||
it('`rollback()` to zero impl succeeds for unregistered function', async () => {
|
it('`rollback()` to zero impl succeeds for unregistered function', async () => {
|
||||||
await registry.rollback(testFnSelector, NULL_ADDRESS).awaitTransactionSuccessAsync();
|
await registry.rollback(testFnSelector, NULL_ADDRESS).awaitTransactionSuccessAsync();
|
||||||
const impl = await registry.getFunctionImplementation(testFnSelector).callAsync();
|
const impl = await zeroEx.getFunctionImplementation(testFnSelector).callAsync();
|
||||||
expect(impl).to.eq(NULL_ADDRESS);
|
expect(impl).to.eq(NULL_ADDRESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
TestMintTokenERC20TransformerContract,
|
TestMintTokenERC20TransformerContract,
|
||||||
TestMintTokenERC20TransformerEvents,
|
TestMintTokenERC20TransformerEvents,
|
||||||
TestMintTokenERC20TransformerMintTransformEventArgs,
|
TestMintTokenERC20TransformerMintTransformEventArgs,
|
||||||
|
TestTransformERC20Contract,
|
||||||
TransformERC20FeatureEvents,
|
TransformERC20FeatureEvents,
|
||||||
} from '../wrappers';
|
} from '../wrappers';
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
{
|
{
|
||||||
transformERC20: (await TransformERC20FeatureContract.deployFrom0xArtifactAsync(
|
transformERC20: (await TestTransformERC20Contract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestTransformERC20,
|
artifacts.TestTransformERC20,
|
||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
|
@ -7,34 +7,49 @@ import {
|
|||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
|
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
|
import { getTokenListBloomFilter } from '../src/bloom_filter_utils';
|
||||||
|
|
||||||
import { artifacts } from './artifacts';
|
import { artifacts } from './artifacts';
|
||||||
import {
|
import {
|
||||||
TestLibTokenSpenderContract,
|
TestFixinTokenSpenderContract,
|
||||||
TestLibTokenSpenderEvents,
|
TestFixinTokenSpenderEvents,
|
||||||
TestTokenSpenderERC20TokenContract,
|
TestTokenSpenderERC20TokenContract,
|
||||||
TestTokenSpenderERC20TokenEvents,
|
TestTokenSpenderERC20TokenEvents,
|
||||||
} from './wrappers';
|
} from './wrappers';
|
||||||
|
|
||||||
blockchainTests.resets('LibTokenSpender library', env => {
|
blockchainTests.resets('FixinTokenSpender', env => {
|
||||||
let tokenSpender: TestLibTokenSpenderContract;
|
let tokenSpender: TestFixinTokenSpenderContract;
|
||||||
let token: TestTokenSpenderERC20TokenContract;
|
let token: TestTokenSpenderERC20TokenContract;
|
||||||
|
let greedyToken: TestTokenSpenderERC20TokenContract;
|
||||||
|
let greedyTokensBloomFilter: string;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
tokenSpender = await TestLibTokenSpenderContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.TestLibTokenSpender,
|
|
||||||
env.provider,
|
|
||||||
env.txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
|
token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestTokenSpenderERC20Token,
|
artifacts.TestTokenSpenderERC20Token,
|
||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
);
|
);
|
||||||
|
greedyToken = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestTokenSpenderERC20Token,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
await greedyToken.setGreedyRevert(true).awaitTransactionSuccessAsync();
|
||||||
|
|
||||||
|
greedyTokensBloomFilter = getTokenListBloomFilter([greedyToken.address]);
|
||||||
|
|
||||||
|
tokenSpender = await TestFixinTokenSpenderContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestFixinTokenSpender,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
greedyTokensBloomFilter,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('spendERC20Tokens()', () => {
|
describe('transferERC20Tokens()', () => {
|
||||||
const EMPTY_RETURN_AMOUNT = 1337;
|
const EMPTY_RETURN_AMOUNT = 1337;
|
||||||
const FALSE_RETURN_AMOUNT = 1338;
|
const FALSE_RETURN_AMOUNT = 1338;
|
||||||
const REVERT_RETURN_AMOUNT = 1339;
|
const REVERT_RETURN_AMOUNT = 1339;
|
||||||
@ -42,12 +57,12 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
const EXTRA_RETURN_TRUE_AMOUNT = 1341;
|
const EXTRA_RETURN_TRUE_AMOUNT = 1341;
|
||||||
const EXTRA_RETURN_FALSE_AMOUNT = 1342;
|
const EXTRA_RETURN_FALSE_AMOUNT = 1342;
|
||||||
|
|
||||||
it('spendERC20Tokens() successfully calls compliant ERC20 token', async () => {
|
it('transferERC20Tokens() successfully calls compliant ERC20 token', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(123456);
|
const tokenAmount = new BigNumber(123456);
|
||||||
const receipt = await tokenSpender
|
const receipt = await tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -63,12 +78,12 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() successfully calls non-compliant ERC20 token', async () => {
|
it('transferERC20Tokens() successfully calls non-compliant ERC20 token', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(EMPTY_RETURN_AMOUNT);
|
const tokenAmount = new BigNumber(EMPTY_RETURN_AMOUNT);
|
||||||
const receipt = await tokenSpender
|
const receipt = await tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -84,12 +99,12 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() reverts if ERC20 token reverts', async () => {
|
it('transferERC20Tokens() reverts if ERC20 token reverts', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(REVERT_RETURN_AMOUNT);
|
const tokenAmount = new BigNumber(REVERT_RETURN_AMOUNT);
|
||||||
const tx = tokenSpender
|
const tx = tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
const expectedError = new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
const expectedError = new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
||||||
token.address,
|
token.address,
|
||||||
@ -101,12 +116,12 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
return expect(tx).to.revertWith(expectedError);
|
return expect(tx).to.revertWith(expectedError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() reverts if ERC20 token returns false', async () => {
|
it('transferERC20Tokens() reverts if ERC20 token returns false', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(FALSE_RETURN_AMOUNT);
|
const tokenAmount = new BigNumber(FALSE_RETURN_AMOUNT);
|
||||||
const tx = tokenSpender
|
const tx = tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
||||||
@ -119,12 +134,12 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() falls back successfully to TokenSpender._spendERC20Tokens()', async () => {
|
it('transferERC20Tokens() falls back successfully to TokenSpender._spendERC20Tokens()', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(TRIGGER_FALLBACK_SUCCESS_AMOUNT);
|
const tokenAmount = new BigNumber(TRIGGER_FALLBACK_SUCCESS_AMOUNT);
|
||||||
const receipt = await tokenSpender
|
const receipt = await tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -136,17 +151,17 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
amount: tokenAmount,
|
amount: tokenAmount,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
TestLibTokenSpenderEvents.FallbackCalled,
|
TestFixinTokenSpenderEvents.FallbackCalled,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() allows extra data after true', async () => {
|
it('transferERC20Tokens() allows extra data after true', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(EXTRA_RETURN_TRUE_AMOUNT);
|
const tokenAmount = new BigNumber(EXTRA_RETURN_TRUE_AMOUNT);
|
||||||
|
|
||||||
const receipt = await tokenSpender
|
const receipt = await tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -162,13 +177,13 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("spendERC20Tokens() reverts when there's extra data after false", async () => {
|
it("transferERC20Tokens() reverts when there's extra data after false", async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(EXTRA_RETURN_FALSE_AMOUNT);
|
const tokenAmount = new BigNumber(EXTRA_RETURN_FALSE_AMOUNT);
|
||||||
|
|
||||||
const tx = tokenSpender
|
const tx = tokenSpender
|
||||||
.spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
|
||||||
@ -181,15 +196,82 @@ blockchainTests.resets('LibTokenSpender library', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('spendERC20Tokens() cannot call self', async () => {
|
it('transferERC20Tokens() cannot call self', async () => {
|
||||||
const tokenFrom = randomAddress();
|
const tokenFrom = randomAddress();
|
||||||
const tokenTo = randomAddress();
|
const tokenTo = randomAddress();
|
||||||
const tokenAmount = new BigNumber(123456);
|
const tokenAmount = new BigNumber(123456);
|
||||||
|
|
||||||
const tx = tokenSpender
|
const tx = tokenSpender
|
||||||
.spendERC20Tokens(tokenSpender.address, tokenFrom, tokenTo, tokenAmount)
|
.transferERC20Tokens(tokenSpender.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
return expect(tx).to.revertWith('LibTokenSpender/CANNOT_INVOKE_SELF');
|
return expect(tx).to.revertWith('FixinTokenSpender/CANNOT_INVOKE_SELF');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls transferFrom() directly for a greedy token with allowance', async () => {
|
||||||
|
const tokenFrom = randomAddress();
|
||||||
|
const tokenTo = randomAddress();
|
||||||
|
const tokenAmount = new BigNumber(123456);
|
||||||
|
await greedyToken.approveAs(tokenFrom, tokenSpender.address, tokenAmount).awaitTransactionSuccessAsync();
|
||||||
|
|
||||||
|
const receipt = await tokenSpender
|
||||||
|
.transferERC20Tokens(greedyToken.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
// Because there is an allowance set, we will call transferFrom()
|
||||||
|
// directly, which succeds and emits an event.
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
sender: tokenSpender.address,
|
||||||
|
from: tokenFrom,
|
||||||
|
to: tokenTo,
|
||||||
|
amount: tokenAmount,
|
||||||
|
},
|
||||||
|
], // No events.
|
||||||
|
TestTokenSpenderERC20TokenEvents.TransferFromCalled,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls only the fallback for a greedy token with no allowance', async () => {
|
||||||
|
const tokenFrom = randomAddress();
|
||||||
|
const tokenTo = randomAddress();
|
||||||
|
const tokenAmount = new BigNumber(TRIGGER_FALLBACK_SUCCESS_AMOUNT);
|
||||||
|
|
||||||
|
const receipt = await tokenSpender
|
||||||
|
.transferERC20Tokens(greedyToken.address, tokenFrom, tokenTo, tokenAmount)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
// Because this is a greedy token and there is no allowance set, transferFrom()
|
||||||
|
// will not be called before hitting the fallback, which only emits an event
|
||||||
|
// in the test contract.
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[], // No events.
|
||||||
|
TestTokenSpenderERC20TokenEvents.TransferFromCalled,
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
token: greedyToken.address,
|
||||||
|
owner: tokenFrom,
|
||||||
|
to: tokenTo,
|
||||||
|
amount: tokenAmount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TestFixinTokenSpenderEvents.FallbackCalled,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isTokenPossiblyGreedy()', () => {
|
||||||
|
it('returns true for greedy token', async () => {
|
||||||
|
const isGreedy = await tokenSpender.isTokenPossiblyGreedy(greedyToken.address).callAsync();
|
||||||
|
expect(isGreedy).to.eq(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for non-greedy token', async () => {
|
||||||
|
const isGreedy = await tokenSpender.isTokenPossiblyGreedy(token.address).callAsync();
|
||||||
|
expect(isGreedy).to.eq(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
|||||||
INativeOrdersFeatureContract,
|
INativeOrdersFeatureContract,
|
||||||
IOwnableFeatureContract,
|
IOwnableFeatureContract,
|
||||||
ISignatureValidatorFeatureContract,
|
ISignatureValidatorFeatureContract,
|
||||||
ISimpleFunctionRegistryFeatureContract,
|
|
||||||
ITokenSpenderFeatureContract,
|
ITokenSpenderFeatureContract,
|
||||||
ITransformERC20FeatureContract,
|
ITransformERC20FeatureContract,
|
||||||
TestFullMigrationContract,
|
TestFullMigrationContract,
|
||||||
@ -27,7 +26,6 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
let zeroEx: ZeroExContract;
|
let zeroEx: ZeroExContract;
|
||||||
let features: FullFeatures;
|
let features: FullFeatures;
|
||||||
let migrator: TestFullMigrationContract;
|
let migrator: TestFullMigrationContract;
|
||||||
let registry: ISimpleFunctionRegistryFeatureContract;
|
|
||||||
const transformerDeployer = randomAddress();
|
const transformerDeployer = randomAddress();
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@ -50,7 +48,6 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
await migrator
|
await migrator
|
||||||
.migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
|
.migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ZeroEx has the correct owner', async () => {
|
it('ZeroEx has the correct owner', async () => {
|
||||||
@ -191,7 +188,7 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
for (const fn of featureInfo.fns) {
|
for (const fn of featureInfo.fns) {
|
||||||
it(`${fn} is registered`, async () => {
|
it(`${fn} is registered`, async () => {
|
||||||
const selector = contract.getSelector(fn);
|
const selector = contract.getSelector(fn);
|
||||||
const impl = await registry.getFunctionImplementation(selector).callAsync();
|
const impl = await zeroEx.getFunctionImplementation(selector).callAsync();
|
||||||
expect(impl).to.not.eq(NULL_ADDRESS);
|
expect(impl).to.not.eq(NULL_ADDRESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ export * from '../test/generated-wrappers/fixin_common';
|
|||||||
export * from '../test/generated-wrappers/fixin_e_i_p712';
|
export * from '../test/generated-wrappers/fixin_e_i_p712';
|
||||||
export * from '../test/generated-wrappers/fixin_protocol_fees';
|
export * from '../test/generated-wrappers/fixin_protocol_fees';
|
||||||
export * from '../test/generated-wrappers/fixin_reentrancy_guard';
|
export * from '../test/generated-wrappers/fixin_reentrancy_guard';
|
||||||
|
export * from '../test/generated-wrappers/fixin_token_spender';
|
||||||
export * from '../test/generated-wrappers/flash_wallet';
|
export * from '../test/generated-wrappers/flash_wallet';
|
||||||
export * from '../test/generated-wrappers/full_migration';
|
export * from '../test/generated-wrappers/full_migration';
|
||||||
export * from '../test/generated-wrappers/i_allowance_target';
|
export * from '../test/generated-wrappers/i_allowance_target';
|
||||||
@ -62,7 +63,6 @@ export * from '../test/generated-wrappers/lib_simple_function_registry_rich_erro
|
|||||||
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
|
export * from '../test/generated-wrappers/lib_simple_function_registry_storage';
|
||||||
export * from '../test/generated-wrappers/lib_spender_rich_errors';
|
export * from '../test/generated-wrappers/lib_spender_rich_errors';
|
||||||
export * from '../test/generated-wrappers/lib_storage';
|
export * from '../test/generated-wrappers/lib_storage';
|
||||||
export * from '../test/generated-wrappers/lib_token_spender';
|
|
||||||
export * from '../test/generated-wrappers/lib_token_spender_storage';
|
export * from '../test/generated-wrappers/lib_token_spender_storage';
|
||||||
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
|
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
|
||||||
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
|
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
|
||||||
@ -96,11 +96,11 @@ export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
|
|||||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
|
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
|
||||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
||||||
export * from '../test/generated-wrappers/test_fixin_protocol_fees';
|
export * from '../test/generated-wrappers/test_fixin_protocol_fees';
|
||||||
|
export * from '../test/generated-wrappers/test_fixin_token_spender';
|
||||||
export * from '../test/generated-wrappers/test_full_migration';
|
export * from '../test/generated-wrappers/test_full_migration';
|
||||||
export * from '../test/generated-wrappers/test_initial_migration';
|
export * from '../test/generated-wrappers/test_initial_migration';
|
||||||
export * from '../test/generated-wrappers/test_lib_native_order';
|
export * from '../test/generated-wrappers/test_lib_native_order';
|
||||||
export * from '../test/generated-wrappers/test_lib_signature';
|
export * from '../test/generated-wrappers/test_lib_signature';
|
||||||
export * from '../test/generated-wrappers/test_lib_token_spender';
|
|
||||||
export * from '../test/generated-wrappers/test_liquidity_provider';
|
export * from '../test/generated-wrappers/test_liquidity_provider';
|
||||||
export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20_feature';
|
export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20_feature';
|
||||||
export * from '../test/generated-wrappers/test_migrator';
|
export * from '../test/generated-wrappers/test_migrator';
|
||||||
@ -126,3 +126,4 @@ export * from '../test/generated-wrappers/transformer_deployer';
|
|||||||
export * from '../test/generated-wrappers/uniswap_feature';
|
export * from '../test/generated-wrappers/uniswap_feature';
|
||||||
export * from '../test/generated-wrappers/weth_transformer';
|
export * from '../test/generated-wrappers/weth_transformer';
|
||||||
export * from '../test/generated-wrappers/zero_ex';
|
export * from '../test/generated-wrappers/zero_ex';
|
||||||
|
export * from '../test/generated-wrappers/zero_ex_optimized';
|
||||||
|
@ -83,7 +83,7 @@ blockchainTests.resets('ZeroEx contract', env => {
|
|||||||
// registry.getSelector('extendSelf'),
|
// registry.getSelector('extendSelf'),
|
||||||
];
|
];
|
||||||
const selectors = [...ownableSelectors, ...registrySelectors];
|
const selectors = [...ownableSelectors, ...registrySelectors];
|
||||||
const impls = await Promise.all(selectors.map(s => registry.getFunctionImplementation(s).callAsync()));
|
const impls = await Promise.all(selectors.map(s => zeroEx.getFunctionImplementation(s).callAsync()));
|
||||||
for (let i = 0; i < impls.length; ++i) {
|
for (let i = 0; i < impls.length; ++i) {
|
||||||
const selector = selectors[i];
|
const selector = selectors[i];
|
||||||
const impl = impls[i];
|
const impl = impls[i];
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"test/generated-artifacts/FixinEIP712.json",
|
"test/generated-artifacts/FixinEIP712.json",
|
||||||
"test/generated-artifacts/FixinProtocolFees.json",
|
"test/generated-artifacts/FixinProtocolFees.json",
|
||||||
"test/generated-artifacts/FixinReentrancyGuard.json",
|
"test/generated-artifacts/FixinReentrancyGuard.json",
|
||||||
|
"test/generated-artifacts/FixinTokenSpender.json",
|
||||||
"test/generated-artifacts/FlashWallet.json",
|
"test/generated-artifacts/FlashWallet.json",
|
||||||
"test/generated-artifacts/FullMigration.json",
|
"test/generated-artifacts/FullMigration.json",
|
||||||
"test/generated-artifacts/IAllowanceTarget.json",
|
"test/generated-artifacts/IAllowanceTarget.json",
|
||||||
@ -89,7 +90,6 @@
|
|||||||
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
|
"test/generated-artifacts/LibSimpleFunctionRegistryStorage.json",
|
||||||
"test/generated-artifacts/LibSpenderRichErrors.json",
|
"test/generated-artifacts/LibSpenderRichErrors.json",
|
||||||
"test/generated-artifacts/LibStorage.json",
|
"test/generated-artifacts/LibStorage.json",
|
||||||
"test/generated-artifacts/LibTokenSpender.json",
|
|
||||||
"test/generated-artifacts/LibTokenSpenderStorage.json",
|
"test/generated-artifacts/LibTokenSpenderStorage.json",
|
||||||
"test/generated-artifacts/LibTransformERC20RichErrors.json",
|
"test/generated-artifacts/LibTransformERC20RichErrors.json",
|
||||||
"test/generated-artifacts/LibTransformERC20Storage.json",
|
"test/generated-artifacts/LibTransformERC20Storage.json",
|
||||||
@ -123,11 +123,11 @@
|
|||||||
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
|
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
|
||||||
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
||||||
"test/generated-artifacts/TestFixinProtocolFees.json",
|
"test/generated-artifacts/TestFixinProtocolFees.json",
|
||||||
|
"test/generated-artifacts/TestFixinTokenSpender.json",
|
||||||
"test/generated-artifacts/TestFullMigration.json",
|
"test/generated-artifacts/TestFullMigration.json",
|
||||||
"test/generated-artifacts/TestInitialMigration.json",
|
"test/generated-artifacts/TestInitialMigration.json",
|
||||||
"test/generated-artifacts/TestLibNativeOrder.json",
|
"test/generated-artifacts/TestLibNativeOrder.json",
|
||||||
"test/generated-artifacts/TestLibSignature.json",
|
"test/generated-artifacts/TestLibSignature.json",
|
||||||
"test/generated-artifacts/TestLibTokenSpender.json",
|
|
||||||
"test/generated-artifacts/TestLiquidityProvider.json",
|
"test/generated-artifacts/TestLiquidityProvider.json",
|
||||||
"test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json",
|
"test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json",
|
||||||
"test/generated-artifacts/TestMigrator.json",
|
"test/generated-artifacts/TestMigrator.json",
|
||||||
@ -152,7 +152,8 @@
|
|||||||
"test/generated-artifacts/TransformerDeployer.json",
|
"test/generated-artifacts/TransformerDeployer.json",
|
||||||
"test/generated-artifacts/UniswapFeature.json",
|
"test/generated-artifacts/UniswapFeature.json",
|
||||||
"test/generated-artifacts/WethTransformer.json",
|
"test/generated-artifacts/WethTransformer.json",
|
||||||
"test/generated-artifacts/ZeroEx.json"
|
"test/generated-artifacts/ZeroEx.json",
|
||||||
|
"test/generated-artifacts/ZeroExOptimized.json"
|
||||||
],
|
],
|
||||||
"exclude": ["./deploy/solc/solc_bin"]
|
"exclude": ["./deploy/solc/solc_bin"]
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,6 @@ RFQ orders are a stripped down version of standard limit orders, supporting fewe
|
|||||||
|
|
||||||
Some notable differences from regular limit orders are:
|
Some notable differences from regular limit orders are:
|
||||||
|
|
||||||
* RFQ orders can only be filled once. Even a partial fill will mark the order as ``FILLED``.
|
|
||||||
* The only fill restrictions that can be placed on an RFQ order is on the ``tx.origin`` of the transaction.
|
* The only fill restrictions that can be placed on an RFQ order is on the ``tx.origin`` of the transaction.
|
||||||
* There are no taker token fees.
|
* There are no taker token fees.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user