Merge pull request #2390 from 0xProject/feat/forwarder/v2-backwards-compatibility
Make Forwarder backwards compatible with v2
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -13,6 +13,8 @@ export {
|
||||
StaticCallProxyContract,
|
||||
TestStaticCallTargetContract,
|
||||
UniswapBridgeContract,
|
||||
KyberBridgeContract,
|
||||
ChaiBridgeContract,
|
||||
} from './wrappers';
|
||||
|
||||
export { ERC20Wrapper } from './erc20_wrapper';
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"contractsDir": "./contracts",
|
||||
"useDockerisedSolc": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -32,12 +32,14 @@ contract Forwarder is
|
||||
{
|
||||
constructor (
|
||||
address _exchange,
|
||||
address _exchangeV2,
|
||||
address _weth
|
||||
)
|
||||
public
|
||||
Ownable()
|
||||
LibConstants(
|
||||
_exchange,
|
||||
_exchangeV2,
|
||||
_weth
|
||||
)
|
||||
MixinForwarderCore()
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ contract TestForwarder is
|
||||
constructor ()
|
||||
public
|
||||
LibConstants(
|
||||
address(0),
|
||||
address(0),
|
||||
address(0)
|
||||
)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { artifacts } from './artifacts';
|
||||
export { ForwarderContract } from './wrappers';
|
||||
export { ForwarderContract, IExchangeV2Contract } from './wrappers';
|
||||
export { ExchangeForwarderRevertErrors } from '@0x/utils';
|
||||
export {
|
||||
ContractArtifact,
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/forwarder';
|
||||
export * from '../generated-wrappers/i_exchange_v2';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
201
contracts/integrations/test/forwarder/forwarder_mainnet_test.ts
Normal file
201
contracts/integrations/test/forwarder/forwarder_mainnet_test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
22
contracts/integrations/test/forwarder/types.ts
Normal file
22
contracts/integrations/test/forwarder/types.ts
Normal 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;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.24;
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": true,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.11',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
solc: {
|
||||
version: '0.5.9',
|
||||
settings: {
|
||||
evmVersion: 'constantinople',
|
||||
evmVersion: 'istanbul',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 1000000,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ContractAddresses {
|
||||
uniswapBridge: string;
|
||||
eth2DaiBridge: string;
|
||||
kyberBridge: string;
|
||||
chaiBridge: string;
|
||||
dydxBridge: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user