Merge pull request #2390 from 0xProject/feat/forwarder/v2-backwards-compatibility

Make Forwarder backwards compatible with v2
This commit is contained in:
Amir Bandeali
2020-01-07 13:04:27 -08:00
committed by GitHub
66 changed files with 1480 additions and 458 deletions

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -13,6 +13,8 @@ export {
StaticCallProxyContract,
TestStaticCallTargetContract,
UniswapBridgeContract,
KyberBridgeContract,
ChaiBridgeContract,
} from './wrappers';
export { ERC20Wrapper } from './erc20_wrapper';

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -3,7 +3,7 @@
"contractsDir": "./contracts",
"useDockerisedSolc": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 200,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -14,7 +14,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// solhint-disable
pragma solidity ^0.4.18;
pragma solidity ^0.5.9;
contract WETH9 {
@@ -30,27 +30,27 @@ contract WETH9 {
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
function() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return this.balance;
return address(this).balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
Approval(msg.sender, guy, wad);
emit Approval(msg.sender, guy, wad);
return true;
}
@@ -72,7 +72,7 @@ contract WETH9 {
balanceOf[src] -= wad;
balanceOf[dst] += wad;
Transfer(src, dst, wad);
emit Transfer(src, dst, wad);
return true;
}

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -32,12 +32,14 @@ contract Forwarder is
{
constructor (
address _exchange,
address _exchangeV2,
address _weth
)
public
Ownable()
LibConstants(
_exchange,
_exchangeV2,
_weth
)
MixinForwarderCore()

View File

@@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
@@ -35,6 +36,7 @@ contract MixinAssets is
IAssets
{
using LibBytes for bytes;
using LibSafeMath for uint256;
/// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets
/// that were accidentally sent to this contract.
@@ -82,6 +84,10 @@ contract MixinAssets is
)
internal
{
if (amount == 0) {
return;
}
bytes4 proxyId = assetData.readBytes4(0);
if (
@@ -91,7 +97,9 @@ contract MixinAssets is
_transferERC20Token(assetData, amount);
} else if (proxyId == IAssetData(address(0)).ERC721Token.selector) {
_transferERC721Token(assetData, amount);
} else {
} else if (proxyId == IAssetData(address(0)).MultiAsset.selector) {
_transferMultiAsset(assetData, amount);
} else if (proxyId != IAssetData(address(0)).StaticCall.selector) {
LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError(
proxyId
));
@@ -137,4 +145,26 @@ contract MixinAssets is
tokenId
);
}
/// @dev Decodes MultiAsset assetData and recursively transfers assets to sender.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to transfer to sender.
function _transferMultiAsset(
bytes memory assetData,
uint256 amount
)
internal
{
// solhint-disable indent
(uint256[] memory nestedAmounts, bytes[] memory nestedAssetData) = abi.decode(
assetData.slice(4, assetData.length),
(uint256[], bytes[])
);
// solhint-enable indent
uint256 numNestedAssets = nestedAssetData.length;
for (uint256 i = 0; i != numNestedAssets; i++) {
_transferAssetToSender(nestedAssetData[i], amount.safeMul(nestedAmounts[i]));
}
}
}

View File

@@ -30,6 +30,7 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./interfaces/IExchangeV2.sol";
import "./MixinAssets.sol";
@@ -54,23 +55,19 @@ contract MixinExchangeWrapper is
internal
returns (LibFillResults.FillResults memory fillResults)
{
// ABI encode calldata for `fillOrder`
bytes memory fillOrderCalldata = abi.encodeWithSelector(
IExchange(address(0)).fillOrder.selector,
if (_isV2Order(order)) {
return _fillV2OrderNoThrow(
order,
takerAssetFillAmount,
signature
);
}
return _fillV3OrderNoThrow(
order,
takerAssetFillAmount,
signature
);
address exchange = address(EXCHANGE);
(bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata);
if (didSucceed) {
assert(returnData.length == 160);
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
}
// fillResults values will be 0 by default if call was unsuccessful
return fillResults;
}
/// @dev Executes a single call of fillOrder according to the wethSellAmount and
@@ -172,7 +169,7 @@ contract MixinExchangeWrapper is
// The remaining amount of WETH to sell
uint256 remainingTakerAssetFillAmount = wethSellAmount
.safeSub(totalWethSpentAmount)
.safeSub(protocolFee);
.safeSub(_isV2Order(orders[i]) ? 0 : protocolFee);
// If the maker asset is ERC20Bridge, take a snapshot of the Forwarder contract's balance.
bytes4 makerAssetProxyId = orders[i].makerAssetData.readBytes4(0);
@@ -370,6 +367,91 @@ contract MixinExchangeWrapper is
}
}
/// @dev Fills the input ExchangeV2 order. The `makerFeeAssetData` must be
// equal to EXCHANGE_V2_ORDER_ID (0x770501f8).
/// Returns false if the transaction would otherwise revert.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function _fillV2OrderNoThrow(
LibOrder.Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature
)
internal
returns (LibFillResults.FillResults memory fillResults)
{
// Strip v3 specific fields from order
IExchangeV2.Order memory v2Order = IExchangeV2.Order({
makerAddress: order.makerAddress,
takerAddress: order.takerAddress,
feeRecipientAddress: order.feeRecipientAddress,
senderAddress: order.senderAddress,
makerAssetAmount: order.makerAssetAmount,
takerAssetAmount: order.takerAssetAmount,
// NOTE: We assume fees are 0 for all v2 orders. Orders with non-zero fees will fail to be filled.
makerFee: 0,
takerFee: 0,
expirationTimeSeconds: order.expirationTimeSeconds,
salt: order.salt,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData
});
// ABI encode calldata for `fillOrder`
bytes memory fillOrderCalldata = abi.encodeWithSelector(
IExchangeV2(address(0)).fillOrder.selector,
v2Order,
takerAssetFillAmount,
signature
);
address exchange = address(EXCHANGE_V2);
(bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata);
if (didSucceed) {
assert(returnData.length == 128);
// NOTE: makerFeePaid, takerFeePaid, and protocolFeePaid will always be 0 for v2 orders
(fillResults.makerAssetFilledAmount, fillResults.takerAssetFilledAmount) = abi.decode(returnData, (uint256, uint256));
}
// fillResults values will be 0 by default if call was unsuccessful
return fillResults;
}
/// @dev Fills the input ExchangeV3 order.
/// Returns false if the transaction would otherwise revert.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function _fillV3OrderNoThrow(
LibOrder.Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature
)
internal
returns (LibFillResults.FillResults memory fillResults)
{
// ABI encode calldata for `fillOrder`
bytes memory fillOrderCalldata = abi.encodeWithSelector(
IExchange(address(0)).fillOrder.selector,
order,
takerAssetFillAmount,
signature
);
address exchange = address(EXCHANGE);
(bool didSucceed, bytes memory returnData) = exchange.call(fillOrderCalldata);
if (didSucceed) {
assert(returnData.length == 160);
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
}
// fillResults values will be 0 by default if call was unsuccessful
return fillResults;
}
/// @dev Checks whether one asset is effectively equal to another asset.
/// This is the case if they have the same ERC20Proxy/ERC20BridgeProxy asset data, or if
/// one is the ERC20Bridge equivalent of the other.
@@ -398,7 +480,18 @@ contract MixinExchangeWrapper is
address token2 = assetData2.readAddress(16);
return (token1 == token2);
} else {
return false;
return assetData1.equals(assetData2);
}
}
/// @dev Checks whether an order is a v2 order.
/// @param order Order struct containing order specifications.
/// @return True if the order's `makerFeeAssetData` is set to the v2 order id.
function _isV2Order(LibOrder.Order memory order)
internal
pure
returns (bool)
{
return order.makerFeeAssetData.length > 3 && order.makerFeeAssetData.readBytes4(0) == EXCHANGE_V2_ORDER_ID;
}
}

View File

@@ -85,7 +85,6 @@ contract MixinForwarderCore is
ethFeeAmounts,
feeRecipients
);
// Spends up to wethRemaining to fill orders, transfers purchased assets to msg.sender,
// and pays WETH order fees.
(

View File

@@ -109,7 +109,6 @@ contract MixinWeth is
if (wethRemaining > 0) {
// Convert remaining WETH to ETH
ETHER_TOKEN.withdraw(wethRemaining);
// Transfer remaining ETH to sender
msg.sender.transfer(wethRemaining);
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
contract IExchangeV2 {
// solhint-disable max-line-length
struct Order {
address makerAddress; // Address that created the order.
address takerAddress; // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order.
address feeRecipientAddress; // Address that will recieve fees when order is filled.
address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods.
uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0.
uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0.
uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted.
uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted.
uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires.
uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash.
bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The last byte references the id of this proxy.
bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The last byte references the id of this proxy.
}
// solhint-enable max-line-length
struct FillResults {
uint256 makerAssetFilledAmount; // Total amount of makerAsset(s) filled.
uint256 takerAssetFilledAmount; // Total amount of takerAsset(s) filled.
uint256 makerFeePaid; // Total amount of ZRX paid by maker(s) to feeRecipient(s).
uint256 takerFeePaid; // Total amount of ZRX paid by taker to feeRecipients(s).
}
struct OrderInfo {
uint8 orderStatus; // Status that describes order's validity and fillability.
bytes32 orderHash; // EIP712 typed data hash of the order (see LibOrder.getTypedDataHash).
uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled.
}
/// @dev Fills the input order.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function fillOrder(
Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature
)
public
returns (FillResults memory fillResults);
/// @dev Gets information about an order: status, hash, and amount filled.
/// @param order Order to gather information on.
/// @return OrderInfo Information about the order and its state.
/// See LibOrder.OrderInfo for a complete description.
function getOrderInfo(Order memory order)
public
returns (OrderInfo memory orderInfo);
}

View File

@@ -18,29 +18,49 @@
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "../interfaces/IExchangeV2.sol";
contract LibConstants {
using LibBytes for bytes;
uint256 constant internal MAX_UINT = uint256(-1);
uint256 constant internal MAX_UINT = 2**256 - 1;
// The v2 order id is the first 4 bytes of the ExchangeV2 order schema hash.
// bytes4(keccak256(abi.encodePacked(
// "Order(",
// "address makerAddress,",
// "address takerAddress,",
// "address feeRecipientAddress,",
// "address senderAddress,",
// "uint256 makerAssetAmount,",
// "uint256 takerAssetAmount,",
// "uint256 makerFee,",
// "uint256 takerFee,",
// "uint256 expirationTimeSeconds,",
// "uint256 salt,",
// "bytes makerAssetData,",
// "bytes takerAssetData",
// ")"
// )));
bytes4 constant public EXCHANGE_V2_ORDER_ID = 0x770501f8;
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
IExchangeV2 internal EXCHANGE_V2;
IEtherToken internal ETHER_TOKEN;
// solhint-enable var-name-mixedcase
constructor (
address _exchange,
address _exchangeV2,
address _weth
)
public
{
EXCHANGE = IExchange(_exchange);
EXCHANGE_V2 = IExchangeV2(_exchangeV2);
ETHER_TOKEN = IEtherToken(_weth);
}
}

View File

@@ -31,6 +31,7 @@ contract TestForwarder is
constructor ()
public
LibConstants(
address(0),
address(0),
address(0)
)

View File

@@ -38,8 +38,8 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"publicInterfaceContracts": "Forwarder",
"abis": "./test/generated-artifacts/@(Forwarder|IAssets|IForwarder|IForwarderCore|LibConstants|LibForwarderRichErrors|MixinAssets|MixinExchangeWrapper|MixinForwarderCore|MixinWeth|TestForwarder).json",
"publicInterfaceContracts": "Forwarder,IExchangeV2",
"abis": "./test/generated-artifacts/@(Forwarder|IAssets|IExchangeV2|IForwarder|IForwarderCore|LibConstants|LibForwarderRichErrors|MixinAssets|MixinExchangeWrapper|MixinForwarderCore|MixinWeth|TestForwarder).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@@ -6,4 +6,5 @@
import { ContractArtifact } from 'ethereum-types';
import * as Forwarder from '../generated-artifacts/Forwarder.json';
export const artifacts = { Forwarder: Forwarder as ContractArtifact };
import * as IExchangeV2 from '../generated-artifacts/IExchangeV2.json';
export const artifacts = { Forwarder: Forwarder as ContractArtifact, IExchangeV2: IExchangeV2 as ContractArtifact };

View File

@@ -1,5 +1,5 @@
export { artifacts } from './artifacts';
export { ForwarderContract } from './wrappers';
export { ForwarderContract, IExchangeV2Contract } from './wrappers';
export { ExchangeForwarderRevertErrors } from '@0x/utils';
export {
ContractArtifact,

View File

@@ -4,3 +4,4 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/forwarder';
export * from '../generated-wrappers/i_exchange_v2';

View File

@@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types';
import * as Forwarder from '../test/generated-artifacts/Forwarder.json';
import * as IAssets from '../test/generated-artifacts/IAssets.json';
import * as IExchangeV2 from '../test/generated-artifacts/IExchangeV2.json';
import * as IForwarder from '../test/generated-artifacts/IForwarder.json';
import * as IForwarderCore from '../test/generated-artifacts/IForwarderCore.json';
import * as LibConstants from '../test/generated-artifacts/LibConstants.json';
@@ -23,6 +24,7 @@ export const artifacts = {
MixinForwarderCore: MixinForwarderCore as ContractArtifact,
MixinWeth: MixinWeth as ContractArtifact,
IAssets: IAssets as ContractArtifact,
IExchangeV2: IExchangeV2 as ContractArtifact,
IForwarder: IForwarder as ContractArtifact,
IForwarderCore: IForwarderCore as ContractArtifact,
LibConstants: LibConstants as ContractArtifact,

View File

@@ -20,11 +20,12 @@ import {
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { BigNumber, ExchangeForwarderRevertErrors, hexUtils } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import { artifacts } from './artifacts';
import { TestForwarderContract } from './wrappers';
blockchainTests('Supported asset type unit tests', env => {
blockchainTests.resets('Supported asset type unit tests', env => {
let forwarder: TestForwarderContract;
let assetDataEncoder: IAssetDataContract;
let bridgeAddress: string;
@@ -38,6 +39,8 @@ blockchainTests('Supported asset type unit tests', env => {
let erc20AssetData: string;
let erc721AssetData: string;
let erc20BridgeAssetData: string;
let staticCallAssetData: string;
let multiAssetData: string;
before(async () => {
[receiver] = await env.getAccountAddressesAsync();
@@ -70,7 +73,7 @@ blockchainTests('Supported asset type unit tests', env => {
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
nftId = getRandomInteger(constants.ZERO_AMOUNT, constants.MAX_UINT256);
nftId = getRandomInteger(0, constants.MAX_UINT256);
erc721AssetData = assetDataEncoder.ERC721Token(erc721Token.address, nftId).getABIEncodedTransactionData();
bridgeAddress = randomAddress();
@@ -78,6 +81,14 @@ blockchainTests('Supported asset type unit tests', env => {
erc20BridgeAssetData = assetDataEncoder
.ERC20Bridge(erc20Token.address, bridgeAddress, bridgeData)
.getABIEncodedTransactionData();
staticCallAssetData = assetDataEncoder
.StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL)
.getABIEncodedTransactionData();
multiAssetData = assetDataEncoder
.MultiAsset([new BigNumber(1)], [erc20AssetData])
.getABIEncodedTransactionData();
});
describe('_areUnderlyingAssetsEqual', () => {
@@ -115,8 +126,59 @@ blockchainTests('Supported asset type unit tests', env => {
.callAsync();
expect(result).to.be.false();
});
it('returns false if assetData1 == assetData2 are ERC721', async () => {
it('returns true if assetData1 == assetData2 are ERC721', async () => {
const result = await forwarder.areUnderlyingAssetsEqual(erc721AssetData, erc721AssetData).callAsync();
expect(result).to.be.true();
});
it('returns false if assetData1 != assetData2 are ERC721', async () => {
const differentErc721AssetData = assetDataEncoder
.ERC721Token(randomAddress(), getRandomInteger(0, constants.MAX_UINT256))
.getABIEncodedTransactionData();
const result = await forwarder
.areUnderlyingAssetsEqual(erc721AssetData, differentErc721AssetData)
.callAsync();
expect(result).to.be.false();
});
it('returns true if assetData1 == assetData2 are StaticCall', async () => {
const result = await forwarder
.areUnderlyingAssetsEqual(staticCallAssetData, staticCallAssetData)
.callAsync();
expect(result).to.be.true();
});
it('returns false if assetData1 != assetData2 are StaticCall', async () => {
const differentStaticCallAssetData = assetDataEncoder
.StaticCall(randomAddress(), hexUtils.random(), constants.KECCAK256_NULL)
.getABIEncodedTransactionData();
const result = await forwarder
.areUnderlyingAssetsEqual(staticCallAssetData, differentStaticCallAssetData)
.callAsync();
expect(result).to.be.false();
});
it('returns false if assetData1 is ERC20 and assetData2 is MultiAsset', async () => {
const result = await forwarder.areUnderlyingAssetsEqual(erc20AssetData, multiAssetData).callAsync();
expect(result).to.be.false();
});
it('returns true if assetData1 == assetData2 are MultiAsset (single nested asset)', async () => {
const result = await forwarder.areUnderlyingAssetsEqual(multiAssetData, multiAssetData).callAsync();
expect(result).to.be.true();
});
it('returns true if assetData1 == assetData2 are MultiAsset (multiple nested assets)', async () => {
const assetData = assetDataEncoder
.MultiAsset(
[getRandomInteger(0, constants.MAX_UINT256), new BigNumber(1)],
[erc20AssetData, erc721AssetData],
)
.getABIEncodedTransactionData();
const result = await forwarder.areUnderlyingAssetsEqual(assetData, assetData).callAsync();
expect(result).to.be.true();
});
it('returns false if assetData1 != assetData2 are MultiAsset', async () => {
const differentMultiAssetData = assetDataEncoder
.MultiAsset([getRandomInteger(0, constants.MAX_UINT256)], [erc721AssetData])
.getABIEncodedTransactionData();
const result = await forwarder
.areUnderlyingAssetsEqual(multiAssetData, differentMultiAssetData)
.callAsync();
expect(result).to.be.false();
});
});
@@ -158,6 +220,61 @@ blockchainTests('Supported asset type unit tests', env => {
const expectedError = new ExchangeForwarderRevertErrors.Erc721AmountMustEqualOneError(invalidAmount);
return expect(tx).to.revertWith(expectedError);
});
it('transfers a single ERC20 token wrapped as MultiAsset', async () => {
const nestedAmount = new BigNumber(1337);
const erc20MultiAssetData = assetDataEncoder
.MultiAsset([nestedAmount], [erc20AssetData])
.getABIEncodedTransactionData();
const multiAssetAmount = new BigNumber(2);
const txReceipt = await forwarder
.transferAssetToSender(erc20MultiAssetData, multiAssetAmount)
.awaitTransactionSuccessAsync({ from: receiver });
verifyEventsFromLogs<ERC20TokenTransferEventArgs>(
txReceipt.logs,
[{ _from: forwarder.address, _to: receiver, _value: multiAssetAmount.times(nestedAmount) }],
ERC20TokenEvents.Transfer,
);
});
it('transfers ERC20, ERC721, and StaticCall assets wrapped as MultiAsset', async () => {
const nestedAmounts = [new BigNumber(1337), TRANSFER_AMOUNT, TRANSFER_AMOUNT];
const assortedMultiAssetData = assetDataEncoder
.MultiAsset(nestedAmounts, [erc20AssetData, erc721AssetData, staticCallAssetData])
.getABIEncodedTransactionData();
const txReceipt = await forwarder
.transferAssetToSender(assortedMultiAssetData, TRANSFER_AMOUNT)
.awaitTransactionSuccessAsync({ from: receiver });
expect(txReceipt.logs.length).to.equal(2);
// tslint:disable:no-unnecessary-type-assertion
const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args;
const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args;
// tslint:enable:no-unnecessary-type-assertion
expect(erc20TransferEvent).to.deep.equal({
_from: forwarder.address,
_to: receiver,
_value: nestedAmounts[0],
});
expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId });
});
it('performs nested MultiAsset transfers', async () => {
const nestedAmounts = [TRANSFER_AMOUNT, TRANSFER_AMOUNT, TRANSFER_AMOUNT];
const assortedMultiAssetData = assetDataEncoder
.MultiAsset(nestedAmounts, [multiAssetData, erc721AssetData, staticCallAssetData])
.getABIEncodedTransactionData();
const txReceipt = await forwarder
.transferAssetToSender(assortedMultiAssetData, TRANSFER_AMOUNT)
.awaitTransactionSuccessAsync({ from: receiver });
expect(txReceipt.logs.length).to.equal(2);
// tslint:disable:no-unnecessary-type-assertion
const erc20TransferEvent = (txReceipt.logs[0] as LogWithDecodedArgs<ERC20TokenTransferEventArgs>).args;
const erc721TransferEvent = (txReceipt.logs[1] as LogWithDecodedArgs<ERC721TokenTransferEventArgs>).args;
// tslint:enable:no-unnecessary-type-assertion
expect(erc20TransferEvent).to.deep.equal({
_from: forwarder.address,
_to: receiver,
_value: TRANSFER_AMOUNT,
});
expect(erc721TransferEvent).to.deep.equal({ _from: forwarder.address, _to: receiver, _tokenId: nftId });
});
it('transfers an ERC20 token given ERC20Bridge assetData', async () => {
const txReceipt = await forwarder
.transferAssetToSender(erc20BridgeAssetData, TRANSFER_AMOUNT)
@@ -168,6 +285,12 @@ blockchainTests('Supported asset type unit tests', env => {
ERC20TokenEvents.Transfer,
);
});
it('noops (emits no events) for StaticCall assetData', async () => {
const txReceipt = await forwarder
.transferAssetToSender(staticCallAssetData, TRANSFER_AMOUNT)
.awaitTransactionSuccessAsync({ from: receiver });
expect(txReceipt.logs.length).to.equal(0);
});
it('reverts if assetData is unsupported', async () => {
const randomBytes = hexUtils.random();
const tx = forwarder

View File

@@ -5,6 +5,7 @@
*/
export * from '../test/generated-wrappers/forwarder';
export * from '../test/generated-wrappers/i_assets';
export * from '../test/generated-wrappers/i_exchange_v2';
export * from '../test/generated-wrappers/i_forwarder';
export * from '../test/generated-wrappers/i_forwarder_core';
export * from '../test/generated-wrappers/lib_constants';

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,8 +4,10 @@
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/Forwarder.json",
"generated-artifacts/IExchangeV2.json",
"test/generated-artifacts/Forwarder.json",
"test/generated-artifacts/IAssets.json",
"test/generated-artifacts/IExchangeV2.json",
"test/generated-artifacts/IForwarder.json",
"test/generated-artifacts/IForwarderCore.json",
"test/generated-artifacts/LibConstants.json",

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -1,6 +1,6 @@
import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
import { artifacts, ForwarderContract } from '@0x/contracts-exchange-forwarder';
import { BlockchainTestsEnvironment } from '@0x/contracts-test-utils';
import { BlockchainTestsEnvironment, constants } from '@0x/contracts-test-utils';
import { DeploymentManager } from '../framework/deployment_manager';
@@ -17,6 +17,7 @@ export async function deployForwarderAsync(
deployment.txDefaults,
{ ...exchangeArtifacts, ...artifacts },
deployment.exchange.address,
constants.NULL_ADDRESS, // ExchangeV2 not tested on Ganache
deployment.tokens.weth.address,
);
}

View File

@@ -0,0 +1,201 @@
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { ForwarderContract, IExchangeV2Contract } from '@0x/contracts-exchange-forwarder';
import {
blockchainTests,
constants,
expect,
getLatestBlockTimestampAsync,
orderHashUtils,
signingUtils,
} from '@0x/contracts-test-utils';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { Order, SignatureType, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as ethUtil from 'ethereumjs-util';
import { contractAddresses } from '../mainnet_fork_utils';
import { SignedV2Order } from './types';
blockchainTests.fork.resets('Forwarder mainnet tests', env => {
const forwarder = new ForwarderContract(contractAddresses.forwarder, env.provider, env.txDefaults);
const exchangeV2 = new IExchangeV2Contract(contractAddresses.exchangeV2, env.provider, env.txDefaults);
const wethAssetData = assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken);
const v2OrderId = '0x770501f8';
let makerAddress: string;
let takerAddress: string;
let makerAssetData: string;
let makerToken: DummyERC20TokenContract;
let makerPrivateKey: Buffer;
before(async () => {
[makerAddress, takerAddress] = await env.web3Wrapper.getAvailableAddressesAsync();
makerToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
erc20Artifacts.DummyERC20Token,
env.provider,
env.txDefaults,
erc20Artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
constants.DUMMY_TOKEN_DECIMALS,
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
);
await makerToken.setBalance(makerAddress, constants.INITIAL_ERC20_BALANCE).awaitTransactionSuccessAsync();
await makerToken
.approve(contractAddresses.erc20Proxy, constants.INITIAL_ERC20_ALLOWANCE)
.awaitTransactionSuccessAsync({ from: makerAddress });
makerAssetData = assetDataUtils.encodeERC20AssetData(makerToken.address);
makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[0];
});
async function createOrderAsync(orderParams: Partial<Order> = {}): Promise<SignedOrder | SignedV2Order> {
const currentBlockTimestamp = await getLatestBlockTimestampAsync();
const fifteenMinutesInSeconds = 15 * 60;
const order = {
chainId: 1,
exchangeAddress: contractAddresses.exchange,
makerAddress,
takerAddress: constants.NULL_ADDRESS,
senderAddress: constants.NULL_ADDRESS,
feeRecipientAddress: constants.NULL_ADDRESS,
expirationTimeSeconds: new BigNumber(currentBlockTimestamp).plus(fifteenMinutesInSeconds),
salt: generatePseudoRandomSalt(),
makerAssetData,
takerAssetData: wethAssetData,
makerFeeAssetData: makerAssetData,
takerFeeAssetData: wethAssetData,
makerAssetAmount: constants.INITIAL_ERC20_BALANCE.dividedToIntegerBy(2),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(0.001, 18),
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
...orderParams,
};
const orderHashHex =
order.makerFeeAssetData === v2OrderId
? (await exchangeV2.getOrderInfo(order).callAsync()).orderHash
: orderHashUtils.getOrderHashHex(order);
const signature = `0x${signingUtils
.signMessage(ethUtil.toBuffer(orderHashHex), makerPrivateKey, SignatureType.EthSign)
.toString('hex')}`;
return {
...order,
signature,
};
}
describe('marketSellOrdersWithEth', () => {
it('should fill a single v2 order with no fees', async () => {
const order = await createOrderAsync({ makerFeeAssetData: v2OrderId });
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketSellOrdersWithEth([order], [order.signature], [], [])
.callAsync({
from: takerAddress,
value: order.takerAssetAmount,
});
expect(wethSpentAmount).to.bignumber.eq(order.takerAssetAmount);
expect(makerAssetAcquiredAmount).to.bignumber.eq(order.makerAssetAmount);
});
it('should fill multiple v2 orders', async () => {
const orders = [
await createOrderAsync({ makerFeeAssetData: v2OrderId }),
await createOrderAsync({ makerFeeAssetData: v2OrderId }),
];
const ethSellAmount = BigNumber.sum(
orders[0].takerAssetAmount,
orders[1].takerAssetAmount.dividedToIntegerBy(2),
);
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketSellOrdersWithEth(orders, orders.map(o => o.signature), [], [])
.callAsync({
from: takerAddress,
value: ethSellAmount,
});
expect(wethSpentAmount).to.bignumber.eq(ethSellAmount);
expect(makerAssetAcquiredAmount).to.bignumber.eq(
BigNumber.sum(orders[0].makerAssetAmount, orders[1].makerAssetAmount.dividedToIntegerBy(2)),
);
});
it.skip('should fill multiple v2/v3 orders', async () => {
const v2Order = await createOrderAsync({ makerFeeAssetData: v2OrderId });
const v3Order = await createOrderAsync();
const protocolFee = new BigNumber(150000).times(constants.DEFAULT_GAS_PRICE);
const ethSellAmount = BigNumber.sum(
v2Order.takerAssetAmount,
v3Order.takerAssetAmount.dividedToIntegerBy(2),
);
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketSellOrdersWithEth([v2Order, v3Order], [v2Order.signature, v3Order.signature], [], [])
.callAsync({
from: takerAddress,
value: ethSellAmount.plus(protocolFee),
gasPrice: constants.DEFAULT_GAS_PRICE,
});
expect(wethSpentAmount).to.bignumber.eq(ethSellAmount.plus(protocolFee));
expect(makerAssetAcquiredAmount).to.bignumber.eq(
BigNumber.sum(v2Order.makerAssetAmount, v3Order.makerAssetAmount.dividedToIntegerBy(2)),
);
});
});
describe('marketBuyOrdersWithEth', () => {
it('should fill a single v2 order', async () => {
const order = await createOrderAsync({ makerFeeAssetData: v2OrderId });
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketBuyOrdersWithEth([order], order.makerAssetAmount, [order.signature], [], [])
.callAsync({ from: takerAddress, value: order.takerAssetAmount });
expect(wethSpentAmount).to.bignumber.eq(order.takerAssetAmount);
expect(makerAssetAcquiredAmount).to.bignumber.eq(order.makerAssetAmount);
});
it('should fill multiple v2 orders', async () => {
const orders = [
await createOrderAsync({ makerFeeAssetData: v2OrderId }),
await createOrderAsync({ makerFeeAssetData: v2OrderId }),
];
const ethSellAmount = BigNumber.sum(
orders[0].takerAssetAmount,
orders[1].takerAssetAmount.dividedToIntegerBy(2),
);
const makerAssetBuyAmount = BigNumber.sum(
orders[0].makerAssetAmount,
orders[1].makerAssetAmount.dividedToIntegerBy(2),
);
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketBuyOrdersWithEth(orders, makerAssetBuyAmount, orders.map(o => o.signature), [], [])
.callAsync({
from: takerAddress,
value: ethSellAmount,
});
expect(wethSpentAmount).to.bignumber.eq(ethSellAmount);
expect(makerAssetAcquiredAmount).to.bignumber.eq(makerAssetBuyAmount);
});
it.skip('should fill multiple v2/v3 orders', async () => {
const v2Order = await createOrderAsync({ makerFeeAssetData: v2OrderId });
const v3Order = await createOrderAsync();
const protocolFee = new BigNumber(150000).times(constants.DEFAULT_GAS_PRICE);
const ethSellAmount = BigNumber.sum(
v2Order.takerAssetAmount,
v3Order.takerAssetAmount.dividedToIntegerBy(2),
);
const makerAssetBuyAmount = BigNumber.sum(
v2Order.makerAssetAmount,
v3Order.makerAssetAmount.dividedToIntegerBy(2),
);
const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder
.marketBuyOrdersWithEth(
[v2Order, v3Order],
makerAssetBuyAmount,
[v2Order.signature, v3Order.signature],
[],
[],
)
.callAsync({
from: takerAddress,
value: ethSellAmount.plus(protocolFee),
gasPrice: constants.DEFAULT_GAS_PRICE,
});
expect(wethSpentAmount).to.bignumber.eq(ethSellAmount.plus(protocolFee));
expect(makerAssetAcquiredAmount).to.bignumber.eq(makerAssetBuyAmount);
});
});
});

View File

@@ -1,3 +1,4 @@
import { artifacts as assetProxyArtifacts, TestStaticCallTargetContract } from '@0x/contracts-asset-proxy';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
@@ -37,6 +38,8 @@ blockchainTests('Forwarder integration tests', env => {
let nftId: BigNumber;
let wethAssetData: string;
let makerAssetData: string;
let staticCallSuccessAssetData: string;
let staticCallFailureAssetData: string;
let maker: Maker;
let taker: Taker;
@@ -51,12 +54,33 @@ blockchainTests('Forwarder integration tests', env => {
});
forwarder = await deployForwarderAsync(deployment, env);
const staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.TestStaticCallTarget,
env.provider,
env.txDefaults,
assetProxyArtifacts,
);
[makerToken, makerFeeToken, anotherErc20Token] = deployment.tokens.erc20;
[erc721Token] = deployment.tokens.erc721;
wethAssetData = deployment.assetDataEncoder
.ERC20Token(deployment.tokens.weth.address)
.getABIEncodedTransactionData();
makerAssetData = deployment.assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData();
staticCallSuccessAssetData = deployment.assetDataEncoder
.StaticCall(
staticCallTarget.address,
staticCallTarget.assertEvenNumber(new BigNumber(2)).getABIEncodedTransactionData(),
constants.KECCAK256_NULL,
)
.getABIEncodedTransactionData();
staticCallFailureAssetData = deployment.assetDataEncoder
.StaticCall(
staticCallTarget.address,
staticCallTarget.assertEvenNumber(new BigNumber(1)).getABIEncodedTransactionData(),
constants.KECCAK256_NULL,
)
.getABIEncodedTransactionData();
taker = new Taker({ name: 'Taker', deployment });
orderFeeRecipient = new FeeRecipient({
@@ -125,6 +149,7 @@ blockchainTests('Forwarder integration tests', env => {
env.txDefaults,
{},
exchange.address,
constants.NULL_ADDRESS,
deployment.tokens.weth.address,
);
await expect(deployForwarder).to.revertWith(
@@ -265,14 +290,14 @@ blockchainTests('Forwarder integration tests', env => {
makerAssetAmount: constants.ZERO_AMOUNT,
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5);
await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over an order with an invalid taker asset amount', async () => {
const unfillableOrder = await maker.signOrderAsync({
takerAssetAmount: constants.ZERO_AMOUNT,
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5);
await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over an expired order', async () => {
const currentTimestamp = await getLatestBlockTimestampAsync();
@@ -280,19 +305,65 @@ blockchainTests('Forwarder integration tests', env => {
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([expiredOrder, fillableOrder], 1.5);
await testFactory.marketSellTestAsync([expiredOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over a fully filled order', async () => {
const fullyFilledOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([fullyFilledOrder], 1);
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([fullyFilledOrder, fillableOrder], 1.5);
await testFactory.marketSellTestAsync([fullyFilledOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over a cancelled order', async () => {
const cancelledOrder = await maker.signOrderAsync();
await maker.cancelOrderAsync(cancelledOrder);
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([cancelledOrder, fillableOrder], 1.5);
await testFactory.marketSellTestAsync([cancelledOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
for (const orderAssetData of ['makerAssetData', 'makerFeeAssetData']) {
it(`should fill an order with StaticCall ${orderAssetData} if the StaticCall succeeds`, async () => {
const staticCallOrder = await maker.signOrderAsync({
[orderAssetData]: staticCallSuccessAssetData,
});
const nonStaticCallOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([staticCallOrder, nonStaticCallOrder], 1.5);
});
it(`should not fill an order with StaticCall ${orderAssetData} if the StaticCall fails`, async () => {
const staticCallOrder = await maker.signOrderAsync({
[orderAssetData]: staticCallFailureAssetData,
});
const nonStaticCallOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([staticCallOrder, nonStaticCallOrder], 1.5, { noopOrders: [0] });
});
}
it('should fill an order with multiAsset makerAssetData', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2)], [makerAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3);
});
it('should fill an order with multiAsset makerAssetData (nested StaticCall succeeds)', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2), new BigNumber(3)], [makerAssetData, staticCallSuccessAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3);
});
it('should skip over an order with multiAsset makerAssetData where the nested StaticCall fails', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2), new BigNumber(3)], [makerAssetData, staticCallFailureAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketSellTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3, { noopOrders: [0] });
});
});
blockchainTests.resets('marketSellOrdersWithEth with extra fees', () => {
@@ -494,14 +565,14 @@ blockchainTests('Forwarder integration tests', env => {
makerAssetAmount: constants.ZERO_AMOUNT,
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5);
await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over an order with an invalid taker asset amount', async () => {
const unfillableOrder = await maker.signOrderAsync({
takerAssetAmount: constants.ZERO_AMOUNT,
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5);
await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over an expired order', async () => {
const currentTimestamp = await getLatestBlockTimestampAsync();
@@ -509,19 +580,19 @@ blockchainTests('Forwarder integration tests', env => {
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([expiredOrder, fillableOrder], 1.5);
await testFactory.marketBuyTestAsync([expiredOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over a fully filled order', async () => {
const fullyFilledOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([fullyFilledOrder], 1);
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([fullyFilledOrder, fillableOrder], 1.5);
await testFactory.marketBuyTestAsync([fullyFilledOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('should skip over a cancelled order', async () => {
const cancelledOrder = await maker.signOrderAsync();
await maker.cancelOrderAsync(cancelledOrder);
const fillableOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([cancelledOrder, fillableOrder], 1.5);
await testFactory.marketBuyTestAsync([cancelledOrder, fillableOrder], 1.5, { noopOrders: [0] });
});
it('Should buy slightly greater makerAsset when exchange rate is rounded', async () => {
// The 0x Protocol contracts round the exchange rate in favor of the Maker.
@@ -621,6 +692,52 @@ blockchainTests('Forwarder integration tests', env => {
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
for (const orderAssetData of ['makerAssetData', 'makerFeeAssetData', 'takerFeeAssetData']) {
it(`should fill an order with StaticCall ${orderAssetData} if the StaticCall succeeds`, async () => {
const staticCallOrder = await maker.signOrderAsync({
[orderAssetData]: staticCallSuccessAssetData,
});
const nonStaticCallOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([staticCallOrder, nonStaticCallOrder], 1.5);
});
it(`should not fill an order with StaticCall ${orderAssetData} if the StaticCall fails`, async () => {
const staticCallOrder = await maker.signOrderAsync({
[orderAssetData]: staticCallFailureAssetData,
});
const nonStaticCallOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([staticCallOrder, nonStaticCallOrder], 1.5, { noopOrders: [0] });
});
}
it('should fill an order with multiAsset makerAssetData', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2)], [makerAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3);
});
it('should fill an order with multiAsset makerAssetData (nested StaticCall succeeds)', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2), new BigNumber(3)], [makerAssetData, staticCallSuccessAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3);
});
it('should skip over an order with multiAsset makerAssetData where the nested StaticCall fails', async () => {
const multiAssetData = deployment.assetDataEncoder
.MultiAsset([new BigNumber(2), new BigNumber(3)], [makerAssetData, staticCallFailureAssetData])
.getABIEncodedTransactionData();
const multiAssetOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
});
const nonMultiAssetOrder = await maker.signOrderAsync();
await testFactory.marketBuyTestAsync([multiAssetOrder, nonMultiAssetOrder], 1.3, { noopOrders: [0] });
});
});
blockchainTests.resets('marketBuyOrdersWithEth with extra fees', () => {
it('should buy the asset and send fee to feeRecipient', async () => {

View File

@@ -22,6 +22,7 @@ interface MarketSellOptions {
forwarderFeeRecipientAddresses: string[];
revertError: RevertError;
bridgeExcessBuyAmount: BigNumber;
noopOrders: number[]; // Indices of orders expected to noop on _fillOrderNoThrow (e.g. cancelled orders)
}
interface MarketBuyOptions extends MarketSellOptions {
@@ -71,7 +72,7 @@ export class ForwarderTestFactory {
orders.map(order => this._deployment.exchange.getOrderInfo(order).callAsync()),
);
const expectedOrderStatuses = orderInfoBefore.map((orderInfo, i) =>
fractionalNumberOfOrdersToFill >= i + 1 && orderInfo.orderStatus === OrderStatus.Fillable
fractionalNumberOfOrdersToFill >= i + 1 && !(options.noopOrders || []).includes(i)
? OrderStatus.FullyFilled
: orderInfo.orderStatus,
);
@@ -112,7 +113,7 @@ export class ForwarderTestFactory {
orders.map(order => this._deployment.exchange.getOrderInfo(order).callAsync()),
);
const expectedOrderStatuses = orderInfoBefore.map((orderInfo, i) =>
fractionalNumberOfOrdersToFill >= i + 1 && orderInfo.orderStatus === OrderStatus.Fillable
fractionalNumberOfOrdersToFill >= i + 1 && !(options.noopOrders || []).includes(i)
? OrderStatus.FullyFilled
: orderInfo.orderStatus,
);
@@ -198,8 +199,8 @@ export class ForwarderTestFactory {
for (const [i, order] of orders.entries()) {
if (remainingOrdersToFill === 0) {
break;
} else if (ordersInfoBefore[i].orderStatus !== OrderStatus.Fillable) {
// If the order is not fillable, skip over it but still count it towards fractionalNumberOfOrdersToFill
} else if ((options.noopOrders || []).includes(i)) {
// If the order won't be filled, skip over it but still count it towards fractionalNumberOfOrdersToFill
remainingOrdersToFill = Math.max(remainingOrdersToFill - 1, 0);
continue;
}

View File

@@ -0,0 +1,22 @@
import { BigNumber } from '@0x/utils';
export interface V2Order {
makerAddress: string;
takerAddress: string;
senderAddress: string;
feeRecipientAddress: string;
makerAssetAmount: BigNumber;
takerAssetAmount: BigNumber;
makerFee: BigNumber;
takerFee: BigNumber;
salt: BigNumber;
expirationTimeSeconds: BigNumber;
makerAssetData: string;
takerAssetData: string;
makerFeeAssetData: string;
takerFeeAssetData: string;
}
export interface SignedV2Order extends V2Order {
signature: string;
}

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -16,7 +16,7 @@
*/
pragma solidity 0.4.24;
pragma solidity ^0.5.9;
// solhint-disable no-empty-blocks

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": true,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.11',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -19,6 +19,7 @@ export let providerConfigs: Web3Config = {
total_accounts: constants.NUM_TEST_ACCOUNTS,
shouldUseInProcessGanache: true,
shouldAllowUnlimitedContractSize: true,
hardfork: 'istanbul',
};
export const provider: Web3ProviderEngine = web3Factory.getRpcProvider(providerConfigs);

View File

@@ -4,7 +4,7 @@
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,

View File

@@ -18,10 +18,6 @@
pragma solidity ^0.5.9;
import "./interfaces/IOwnable.sol";
import "./LibOwnableRichErrors.sol";
import "./LibRichErrors.sol";
contract DeploymentConstants {
/// @dev Mainnet address of the WETH contract.

View File

@@ -84,7 +84,7 @@ module.exports = {
solc: {
version: '0.5.9',
settings: {
evmVersion: 'constantinople',
evmVersion: 'istanbul',
optimizer: {
enabled: true,
runs: 1000000,

View File

@@ -82,7 +82,7 @@
"async-child-process": "^1.1.1",
"bundlewatch": "^0.2.1",
"coveralls": "^3.0.0",
"ganache-cli": "6.4.1",
"ganache-cli": "6.8.0-istanbul.0",
"lcov-result-merger": "^3.0.0",
"lerna": "^3.0.0-beta.25",
"npm-run-all": "^4.1.2",

View File

@@ -12,15 +12,23 @@
},
{
"note": "Added DydxBridge Contract to contract-addresses",
"pr": 2401
"pr": 2390
},
{
"note": "Update `UniswapBridge` mainnet address.",
"pr": 2412
"pr": 2390
},
{
"note": "Update `KyberBridge` mainnet address.",
"pr": 2412
"pr": 2390
},
{
"note": "Update `Forwarder` mainnet address.",
"pr": 2390
},
{
"note": "Added ChaiBridge Contract to contract-addresses",
"pr": 2390
}
],
"timestamp": 1578272714

View File

@@ -4,7 +4,7 @@
"exchange": "0x61935cbdd02287b511119ddb11aeb42f1593b7ef",
"erc20Proxy": "0x95e6f48254609a6ee006f7d493c8e5fb97094cef",
"erc721Proxy": "0xefc70a1b18c432bdc64b596838b4d138f6bc6cad",
"forwarder": "0xa3ac9844514b96bb502627ca9dceb57c4be289e3",
"forwarder": "0x4ef40d1bf0983899892946830abf99eca2dbc5ce",
"orderValidator": "0x0000000000000000000000000000000000000000",
"zrxToken": "0xe41d2489571d322189246dafa5ebde1f4699f498",
"etherToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
@@ -21,11 +21,12 @@
"stakingProxy": "0xa26e80e7dea86279c6d778d702cc413e6cffa777",
"devUtils": "0xc7612135356ba8f75dbf517b55d88a91977492dc",
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
"uniswapBridge": "0xb0dc61047847732a013ce27341228228a38655a0",
"eth2DaiBridge": "0x0ac2d6f5f5afc669d3ca38f830dad2b4f238ad3f",
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
"erc20BridgeSampler": "0x1b402fdb5ee87f989c11e3963557e89cc313b6c0",
"kyberBridge": "0x7253a80c1d3a3175283bad9ed04b2cecad0fe0d3",
"dydxBridge": "0x96ddba19b69d6ea2549f6a12d005595167414744"
"kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62",
"eth2DaiBridge": "0xe97ea901d034ba2e018155264f77c417ce7717f9",
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",
"dydxBridge": "0x55dc8f21d20d4c6ed3c82916a438a413ca68e335"
},
"3": {
"erc20Proxy": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa",
@@ -53,6 +54,7 @@
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
"kyberBridge": "0x0000000000000000000000000000000000000000",
"chaiBridge": "0x0000000000000000000000000000000000000000",
"dydxBridge": "0x0000000000000000000000000000000000000000"
},
"4": {
@@ -81,6 +83,7 @@
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
"kyberBridge": "0x0000000000000000000000000000000000000000",
"chaiBridge": "0x0000000000000000000000000000000000000000",
"dydxBridge": "0x0000000000000000000000000000000000000000"
},
"42": {
@@ -109,6 +112,7 @@
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
"erc20BridgeSampler": "0x551f0e213dcb71f676558d8b0ab559d1cdd103f2",
"kyberBridge": "0x0000000000000000000000000000000000000000",
"chaiBridge": "0x0000000000000000000000000000000000000000",
"dydxBridge": "0x0000000000000000000000000000000000000000"
},
"1337": {
@@ -137,6 +141,7 @@
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
"kyberBridge": "0x0000000000000000000000000000000000000000",
"chaiBridge": "0x0000000000000000000000000000000000000000",
"dydxBridge": "0x0000000000000000000000000000000000000000"
}
}

View File

@@ -26,6 +26,7 @@ export interface ContractAddresses {
uniswapBridge: string;
eth2DaiBridge: string;
kyberBridge: string;
chaiBridge: string;
dydxBridge: string;
}

View File

@@ -25,6 +25,7 @@ export interface Web3Config {
blockTime?: number;
locked?: boolean;
unlocked_accounts?: string[];
hardfork?: string;
}
export const web3Factory = {
@@ -81,6 +82,7 @@ export const web3Factory = {
blockTime: config.blockTime,
locked: config.locked,
unlocked_accounts: config.unlocked_accounts,
hardfork: config.hardfork,
} as any), // TODO remove any once types are merged in DefinitelyTyped
);
} else {

View File

@@ -64,6 +64,7 @@
"@0x/base-contract": "^6.0.3",
"@0x/contract-addresses": "^4.2.0",
"@0x/contract-artifacts": "^3.3.0",
"@0x/contract-wrappers": "^13.3.0",
"@0x/contracts-asset-proxy": "^3.1.0",
"@0x/contracts-coordinator": "^3.0.3",
"@0x/contracts-dev-utils": "^1.0.3",

View File

@@ -1,5 +1,6 @@
import { ContractAddresses } from '@0x/contract-addresses';
import * as artifacts from '@0x/contract-artifacts';
import { ForwarderContract } from '@0x/contract-wrappers';
import {
ERC1155ProxyContract,
ERC20BridgeProxyContract,
@@ -14,7 +15,6 @@ import { ERC1155MintableContract } from '@0x/contracts-erc1155';
import { DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeContract } from '@0x/contracts-exchange';
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
import { StakingProxyContract, TestStakingContract, ZrxVaultContract } from '@0x/contracts-staking';
import { Web3ProviderEngine } from '@0x/subproviders';
import { BigNumber, providerUtils } from '@0x/utils';
@@ -270,6 +270,7 @@ export async function runMigrationsAsync(
eth2DaiBridge: constants.NULL_ADDRESS,
kyberBridge: constants.NULL_ADDRESS,
erc20BridgeSampler: constants.NULL_ADDRESS,
chaiBridge: constants.NULL_ADDRESS,
dydxBridge: constants.NULL_ADDRESS,
};
return contractAddresses;

View File

@@ -1,8 +1,11 @@
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import {
artifacts as assetProxyArtifacts,
ChaiBridgeContract,
DydxBridgeContract,
ERC20BridgeProxyContract,
Eth2DaiBridgeContract,
KyberBridgeContract,
UniswapBridgeContract,
} from '@0x/contracts-asset-proxy';
import { artifacts as coordinatorArtifacts, CoordinatorContract } from '@0x/contracts-coordinator';
@@ -103,6 +106,27 @@ export async function runMigrationsAsync(supportedProvider: SupportedProvider, t
assetProxyArtifacts,
);
await KyberBridgeContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.KyberBridge,
provider,
txDefaults,
assetProxyArtifacts,
);
await ChaiBridgeContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.ChaiBridge,
provider,
txDefaults,
assetProxyArtifacts,
);
await DydxBridgeContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.DydxBridge,
provider,
txDefaults,
assetProxyArtifacts,
);
const authorizableInterface = new IAuthorizableContract(constants.NULL_ADDRESS, provider, txDefaults);
const ownableInterface = new IOwnableContract(constants.NULL_ADDRESS, provider, txDefaults);
@@ -238,15 +262,15 @@ export async function runMigrationsAsync(supportedProvider: SupportedProvider, t
chainId,
);
const forwarder = await ForwarderContract.deployFrom0xArtifactAsync(
await ForwarderContract.deployFrom0xArtifactAsync(
forwarderArtifacts.Forwarder,
provider,
txDefaults,
forwarderArtifacts,
exchange.address,
deployedAddresses.exchangeV2,
deployedAddresses.etherToken,
);
await forwarder.approveMakerAssetProxy(deployedAddresses.etherToken).awaitTransactionSuccessAsync();
}
(async () => {

View File

@@ -47,7 +47,7 @@
"ethereum-types": "^3.0.0",
"ethereumjs-tx": "^1.3.5",
"ethereumjs-util": "^5.1.1",
"ganache-core": "^2.6.0",
"ganache-core": "^2.9.0-istanbul.0",
"hdkey": "^0.7.1",
"json-rpc-error": "2.0.0",
"lodash": "^4.17.11",

View File

@@ -47,7 +47,7 @@
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
"dirty-chai": "^2.0.1",
"ganache-core": "^2.6.0",
"ganache-core": "^2.9.0-istanbul.0",
"make-promises-safe": "^1.1.0",
"mocha": "^6.2.0",
"npm-run-all": "^4.1.2",

View File

@@ -35,7 +35,7 @@ describe('Web3Wrapper tests', () => {
describe('#getNodeVersionAsync', () => {
it('gets the node version', async () => {
const nodeVersion = await web3Wrapper.getNodeVersionAsync();
const NODE_VERSION = 'EthereumJS TestRPC/v2.6.0/ethereum-js';
const NODE_VERSION = 'EthereumJS TestRPC/v2.9.0-istanbul.0/ethereum-js';
expect(nodeVersion).to.be.equal(NODE_VERSION);
});
});

1010
yarn.lock

File diff suppressed because it is too large Load Diff