Compare commits
102 Commits
@0x/sol-co
...
@0x/assert
Author | SHA1 | Date | |
---|---|---|---|
|
2113fb490d | ||
|
0afedbd252 | ||
|
3dec38450a | ||
|
d7a00b05e3 | ||
|
ff2cc8c887 | ||
|
0571a96cea | ||
|
b7b457b076 | ||
|
d2c12005b2 | ||
|
25f26d7e5f | ||
|
9d5724e1a0 | ||
|
784d23ec87 | ||
|
709689a7ee | ||
|
35d5d3d995 | ||
|
630a8d8a4e | ||
|
54eb1d9055 | ||
|
cb63caea61 | ||
|
8e046bb022 | ||
|
189b53b8c4 | ||
|
9b7277d464 | ||
|
551a65c069 | ||
|
4c21a697f4 | ||
|
a8506c07ae | ||
|
b0feb85b5c | ||
|
f371eba8ad | ||
|
265fa52ace | ||
|
c1f5322d38 | ||
|
4415e00b38 | ||
|
d4e46c5a9c | ||
|
bf5b9949fe | ||
|
3c11a2b1da | ||
|
1248868169 | ||
|
930b95a548 | ||
|
27c9f68c7c | ||
|
358d4d86a7 | ||
|
d55eea2239 | ||
|
4507954ea5 | ||
|
8e0a83f8d8 | ||
|
b6ec09e6cf | ||
|
ed4e90623d | ||
|
38cdb48748 | ||
|
71bfe9b745 | ||
|
9e7645a167 | ||
|
6dccc37143 | ||
|
3310310d8c | ||
|
abb499aad8 | ||
|
1afc09b08a | ||
|
0e86d72f05 | ||
|
c9857a2764 | ||
|
701ba3902c | ||
|
bb3ec970a9 | ||
|
1d023e6db5 | ||
|
1bd906ecb3 | ||
|
7cbffdb86b | ||
|
b979196ffd | ||
|
2949db5f49 | ||
|
47c3ed9705 | ||
|
51ca3109eb | ||
|
2bcb79dc44 | ||
|
ecec985649 | ||
|
994908549d | ||
|
e00f059a4a | ||
|
6808e0d531 | ||
|
410c95308a | ||
|
bec1f23616 | ||
|
34596b7f83 | ||
|
5ca7169ee5 | ||
|
3300aaa1b9 | ||
|
54afc8a4a1 | ||
|
f19f4310f4 | ||
|
444125a7e1 | ||
|
56cbb69401 | ||
|
70870ffcd2 | ||
|
a556d91673 | ||
|
8ecbde8e1e | ||
|
a24b293818 | ||
|
cab5ebf94b | ||
|
a54b5baef2 | ||
|
c324fe204e | ||
|
37d972ed9e | ||
|
e4a3b1cb05 | ||
|
49538f272e | ||
|
1283232144 | ||
|
2f9891f0aa | ||
|
865a2b1fb0 | ||
|
1fde62eeb6 | ||
|
6754cd48e2 | ||
|
ccb477687a | ||
|
be0e6c8925 | ||
|
1c2cb947c0 | ||
|
4663eec950 | ||
|
fff3c1eb36 | ||
|
4b7434d1e8 | ||
|
8cc35a60e6 | ||
|
130653a1aa | ||
|
1dcbebd130 | ||
|
faf306ad23 | ||
|
d11cdcd5d2 | ||
|
0e59bd0bf3 | ||
|
c0c6154ec1 | ||
|
cb5384c2fb | ||
|
038c836fe5 | ||
|
731a823cc2 |
@@ -23,7 +23,7 @@ jobs:
|
||||
# command: npm set prefix=/home/circleci/npm && echo 'export PATH=$HOME/circleci/npm/bin:$PATH' >> /home/circleci/.bashrc
|
||||
- run:
|
||||
name: install-yarn
|
||||
command: npm install --global yarn@1.17.0
|
||||
command: npm install --force --global yarn@1.17.0
|
||||
- run:
|
||||
name: yarn
|
||||
command: yarn --frozen-lockfile --ignore-engines install || yarn --frozen-lockfile --ignore-engines install
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler
|
||||
# TODO(dorothy-zbornak): Re-enable after updating this package for
|
||||
# 3.0. At that time, also remove exclusion from monorepo
|
||||
# package.json's test script.
|
||||
@@ -197,13 +197,10 @@ jobs:
|
||||
- image: 0xorg/mesh:0xV3
|
||||
environment:
|
||||
ETHEREUM_RPC_URL: 'http://localhost:8545'
|
||||
ETHEREUM_NETWORK_ID: '50'
|
||||
ETHEREUM_CHAIN_ID: '1337'
|
||||
USE_BOOTSTRAP_LIST: 'true'
|
||||
VERBOSITY: 3
|
||||
PRIVATE_KEY_PATH: ''
|
||||
BLOCK_POLLING_INTERVAL: '5s'
|
||||
P2P_LISTEN_PORT: '60557'
|
||||
VERBOSITY: 5
|
||||
BLOCK_POLLING_INTERVAL: '50ms'
|
||||
ETHEREUM_RPC_MAX_REQUESTS_PER_24_HR_UTC: '1778000'
|
||||
command: |
|
||||
sh -c "waitForGanache () { until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done }; waitForGanache && ./mesh"
|
||||
- image: 0xorg/launch-kit-backend:v3
|
||||
|
28
.gitignore
vendored
28
.gitignore
vendored
@@ -160,33 +160,7 @@ contracts/exchange-forwarder/generated-wrappers/
|
||||
contracts/exchange-forwarder/test/generated-wrappers/
|
||||
contracts/dev-utils/generated-wrappers/
|
||||
contracts/dev-utils/test/generated-wrappers/
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dev_utils/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_token/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/asset_proxy_owner/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/coordinator/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/coordinator_registry/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc20_token/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc721_token/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dutch_auction/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_mintable/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_bridge_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_token/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/forwarder/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_asset_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_validator/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_wallet/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/multi_asset_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_validator/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/staking_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/static_call_proxy/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/weth9/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_token/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_vault/__init__.py
|
||||
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/*/__init__.py
|
||||
|
||||
# solc-bin in sol-compiler
|
||||
packages/sol-compiler/solc_bin/
|
||||
|
@@ -14,8 +14,8 @@ packages/abi-gen/ @feuGeneA
|
||||
packages/base-contract/ @xianny
|
||||
packages/connect/ @fragosti
|
||||
packages/abi-gen-templates/ @feuGeneA @xianny
|
||||
packages/contract-addresses/ @albrow
|
||||
packages/contract-artifacts/ @albrow
|
||||
packages/contract-addresses/ @abandeali1
|
||||
packages/contract-artifacts/ @abandeali1
|
||||
packages/dev-utils/ @LogvinovLeon @fabioberger
|
||||
packages/devnet/ @albrow
|
||||
packages/ethereum-types/ @LogvinovLeon
|
||||
|
@@ -1,4 +1,31 @@
|
||||
[
|
||||
{
|
||||
"version": "3.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Integration tests for DydxBridge with ERC20BridgeProxy.",
|
||||
"pr": 2401
|
||||
},
|
||||
{
|
||||
"note": "Fix `UniswapBridge` token -> token transfer call.",
|
||||
"pr": 2412
|
||||
},
|
||||
{
|
||||
"note": "Fix `KyberBridge` incorrect `minConversionRate` calculation.",
|
||||
"pr": 2412
|
||||
}
|
||||
],
|
||||
"timestamp": 1578272714
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
@@ -64,6 +91,10 @@
|
||||
{
|
||||
"note": "Implement `KyberBridge`.",
|
||||
"pr": 2352
|
||||
},
|
||||
{
|
||||
"note": "Implement `DydxBridge`.",
|
||||
"pr": 2365
|
||||
}
|
||||
],
|
||||
"timestamp": 1575290197
|
||||
|
@@ -5,6 +5,16 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.1.0 - _January 6, 2020_
|
||||
|
||||
* Integration tests for DydxBridge with ERC20BridgeProxy. (#2401)
|
||||
* Fix `UniswapBridge` token -> token transfer call. (#2412)
|
||||
* Fix `KyberBridge` incorrect `minConversionRate` calculation. (#2412)
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
@@ -26,6 +36,7 @@ CHANGELOG
|
||||
## v2.3.0-beta.4 - _December 2, 2019_
|
||||
|
||||
* Implement `KyberBridge`. (#2352)
|
||||
* Implement `DydxBridge`. (#2365)
|
||||
|
||||
## v2.3.0-beta.3 - _November 20, 2019_
|
||||
|
||||
|
241
contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
Normal file
241
contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "../interfaces/IERC20Bridge.sol";
|
||||
import "../interfaces/IDydxBridge.sol";
|
||||
import "../interfaces/IDydx.sol";
|
||||
|
||||
|
||||
contract DydxBridge is
|
||||
IERC20Bridge,
|
||||
IDydxBridge,
|
||||
DeploymentConstants
|
||||
{
|
||||
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Callback for `IERC20Bridge`. Deposits or withdraws tokens from a dydx account.
|
||||
/// Notes:
|
||||
/// 1. This bridge must be set as an operator of the input dydx account.
|
||||
/// 2. This function may only be called in the context of the 0x Exchange.
|
||||
/// 3. The maker or taker of the 0x order must be the dydx account owner.
|
||||
/// 4. Deposits into dydx are made from the `from` address.
|
||||
/// 5. Withdrawals from dydx are made to the `to` address.
|
||||
/// 6. Calling this function must always withdraw at least `amount`,
|
||||
/// otherwise the `ERC20Bridge` will revert.
|
||||
/// @param from The sender of the tokens and owner of the dydx account.
|
||||
/// @param to The recipient of the tokens.
|
||||
/// @param amount Minimum amount of `toTokenAddress` tokens to deposit or withdraw.
|
||||
/// @param encodedBridgeData An abi-encoded `BridgeData` struct.
|
||||
/// @return success The magic bytes if successful.
|
||||
function bridgeTransferFrom(
|
||||
address,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata encodedBridgeData
|
||||
)
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
// Ensure that only the `ERC20BridgeProxy` can call this function.
|
||||
require(
|
||||
msg.sender == _getERC20BridgeProxyAddress(),
|
||||
"DydxBridge/ONLY_CALLABLE_BY_ERC20_BRIDGE_PROXY"
|
||||
);
|
||||
|
||||
// Decode bridge data.
|
||||
(BridgeData memory bridgeData) = abi.decode(encodedBridgeData, (BridgeData));
|
||||
|
||||
// The dydx accounts are owned by the `from` address.
|
||||
IDydx.AccountInfo[] memory accounts = _createAccounts(from, bridgeData);
|
||||
|
||||
// Create dydx actions to run on the dydx accounts.
|
||||
IDydx.ActionArgs[] memory actions = _createActions(
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
bridgeData
|
||||
);
|
||||
|
||||
// Run operation. This will revert on failure.
|
||||
IDydx(_getDydxAddress()).operate(accounts, actions);
|
||||
return BRIDGE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Creates an array of accounts for dydx to operate on.
|
||||
/// All accounts must belong to the same owner.
|
||||
/// @param accountOwner Owner of the dydx account.
|
||||
/// @param bridgeData A `BridgeData` struct.
|
||||
function _createAccounts(
|
||||
address accountOwner,
|
||||
BridgeData memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (IDydx.AccountInfo[] memory accounts)
|
||||
{
|
||||
uint256[] memory accountNumbers = bridgeData.accountNumbers;
|
||||
uint256 nAccounts = accountNumbers.length;
|
||||
accounts = new IDydx.AccountInfo[](nAccounts);
|
||||
for (uint256 i = 0; i < nAccounts; ++i) {
|
||||
accounts[i] = IDydx.AccountInfo({
|
||||
owner: accountOwner,
|
||||
number: accountNumbers[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Creates an array of actions to carry out on dydx.
|
||||
/// @param depositFrom Deposit value from this address (owner of the dydx account).
|
||||
/// @param withdrawTo Withdraw value to this address.
|
||||
/// @param amount The amount of value available to operate on.
|
||||
/// @param bridgeData A `BridgeData` struct.
|
||||
function _createActions(
|
||||
address depositFrom,
|
||||
address withdrawTo,
|
||||
uint256 amount,
|
||||
BridgeData memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (IDydx.ActionArgs[] memory actions)
|
||||
{
|
||||
BridgeAction[] memory bridgeActions = bridgeData.actions;
|
||||
uint256 nBridgeActions = bridgeActions.length;
|
||||
actions = new IDydx.ActionArgs[](nBridgeActions);
|
||||
for (uint256 i = 0; i < nBridgeActions; ++i) {
|
||||
// Cache current bridge action.
|
||||
BridgeAction memory bridgeAction = bridgeActions[i];
|
||||
|
||||
// Scale amount, if conversion rate is set.
|
||||
uint256 scaledAmount;
|
||||
if (bridgeAction.conversionRateDenominator > 0) {
|
||||
scaledAmount = LibMath.safeGetPartialAmountFloor(
|
||||
bridgeAction.conversionRateNumerator,
|
||||
bridgeAction.conversionRateDenominator,
|
||||
amount
|
||||
);
|
||||
} else {
|
||||
scaledAmount = amount;
|
||||
}
|
||||
|
||||
// Construct dydx action.
|
||||
if (bridgeAction.actionType == BridgeActionType.Deposit) {
|
||||
// Deposit tokens from the account owner into their dydx account.
|
||||
actions[i] = _createDepositAction(
|
||||
depositFrom,
|
||||
scaledAmount,
|
||||
bridgeAction
|
||||
);
|
||||
} else if (bridgeAction.actionType == BridgeActionType.Withdraw) {
|
||||
// Withdraw tokens from dydx to the `otherAccount`.
|
||||
actions[i] = _createWithdrawAction(
|
||||
withdrawTo,
|
||||
scaledAmount,
|
||||
bridgeAction
|
||||
);
|
||||
} else {
|
||||
// If all values in the `Action` enum are handled then this
|
||||
// revert is unreachable: Solidity will revert when casting
|
||||
// from `uint8` to `Action`.
|
||||
revert("DydxBridge/UNRECOGNIZED_BRIDGE_ACTION");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns a dydx `DepositAction`.
|
||||
/// @param depositFrom Deposit tokens from this address who is also the account owner.
|
||||
/// @param amount of tokens to deposit.
|
||||
/// @param bridgeAction A `BridgeAction` struct.
|
||||
/// @return depositAction The encoded dydx action.
|
||||
function _createDepositAction(
|
||||
address depositFrom,
|
||||
uint256 amount,
|
||||
BridgeAction memory bridgeAction
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IDydx.ActionArgs memory depositAction
|
||||
)
|
||||
{
|
||||
// Create dydx amount.
|
||||
IDydx.AssetAmount memory dydxAmount = IDydx.AssetAmount({
|
||||
sign: true, // true if positive.
|
||||
denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account.
|
||||
ref: IDydx.AssetReference.Delta, // Delta => a relative amount.
|
||||
value: amount // amount to deposit.
|
||||
});
|
||||
|
||||
// Create dydx deposit action.
|
||||
depositAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Deposit, // deposit tokens.
|
||||
amount: dydxAmount, // amount to deposit.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to deposit.
|
||||
otherAddress: depositFrom, // deposit from the account owner.
|
||||
// unused parameters
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns a dydx `WithdrawAction`.
|
||||
/// @param withdrawTo Withdraw tokens to this address.
|
||||
/// @param amount of tokens to withdraw.
|
||||
/// @param bridgeAction A `BridgeAction` struct.
|
||||
/// @return withdrawAction The encoded dydx action.
|
||||
function _createWithdrawAction(
|
||||
address withdrawTo,
|
||||
uint256 amount,
|
||||
BridgeAction memory bridgeAction
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IDydx.ActionArgs memory withdrawAction
|
||||
)
|
||||
{
|
||||
// Create dydx amount.
|
||||
IDydx.AssetAmount memory amountToWithdraw = IDydx.AssetAmount({
|
||||
sign: false, // false if negative.
|
||||
denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account.
|
||||
ref: IDydx.AssetReference.Delta, // Delta => a relative amount.
|
||||
value: amount // amount to withdraw.
|
||||
});
|
||||
|
||||
// Create withdraw action.
|
||||
withdrawAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Withdraw, // withdraw tokens.
|
||||
amount: amountToWithdraw, // amount to withdraw.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to withdraw.
|
||||
otherAddress: withdrawTo, // withdraw tokens to this address.
|
||||
// unused parameters
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "../interfaces/IERC20Bridge.sol";
|
||||
import "../interfaces/IKyberNetworkProxy.sol";
|
||||
|
||||
@@ -34,6 +35,8 @@ contract KyberBridge is
|
||||
IWallet,
|
||||
DeploymentConstants
|
||||
{
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
// @dev Structure used internally to get around stack limits.
|
||||
struct TradeState {
|
||||
IKyberNetworkProxy kyber;
|
||||
@@ -41,6 +44,7 @@ contract KyberBridge is
|
||||
address fromTokenAddress;
|
||||
uint256 fromTokenBalance;
|
||||
uint256 payableAmount;
|
||||
uint256 conversionRate;
|
||||
}
|
||||
|
||||
/// @dev Kyber ETH pseudo-address.
|
||||
@@ -81,11 +85,23 @@ contract KyberBridge is
|
||||
state.weth = IEtherToken(_getWethAddress());
|
||||
// Decode the bridge data to get the `fromTokenAddress`.
|
||||
(state.fromTokenAddress) = abi.decode(bridgeData, (address));
|
||||
// Query the balance of "from" tokens.
|
||||
state.fromTokenBalance = IERC20Token(state.fromTokenAddress).balanceOf(address(this));
|
||||
if (state.fromTokenBalance == 0) {
|
||||
// Return failure if no input tokens.
|
||||
return BRIDGE_FAILED;
|
||||
}
|
||||
// Compute the conversion rate, expressed in 18 decimals.
|
||||
// The sequential notation is to get around stack limits.
|
||||
state.conversionRate = KYBER_RATE_BASE;
|
||||
state.conversionRate = state.conversionRate.safeMul(amount);
|
||||
state.conversionRate = state.conversionRate.safeMul(
|
||||
10 ** uint256(LibERC20Token.decimals(state.fromTokenAddress))
|
||||
);
|
||||
state.conversionRate = state.conversionRate.safeDiv(state.fromTokenBalance);
|
||||
state.conversionRate = state.conversionRate.safeDiv(
|
||||
10 ** uint256(LibERC20Token.decimals(toTokenAddress))
|
||||
);
|
||||
if (state.fromTokenAddress == toTokenAddress) {
|
||||
// Just transfer the tokens if they're the same.
|
||||
LibERC20Token.transfer(state.fromTokenAddress, to, state.fromTokenBalance);
|
||||
@@ -118,7 +134,7 @@ contract KyberBridge is
|
||||
uint256(-1),
|
||||
// Compute the minimum conversion rate, which is expressed in units with
|
||||
// 18 decimal places.
|
||||
(KYBER_RATE_BASE * amount) / state.fromTokenBalance,
|
||||
state.conversionRate,
|
||||
// No affiliate address.
|
||||
address(0)
|
||||
);
|
||||
|
@@ -134,8 +134,8 @@ contract UniswapBridge is
|
||||
state.fromTokenBalance,
|
||||
// Minimum buy amount.
|
||||
amount,
|
||||
// No minimum intermediate ETH buy amount.
|
||||
0,
|
||||
// Must buy at least 1 intermediate ETH.
|
||||
1,
|
||||
// Expires after this block.
|
||||
block.timestamp,
|
||||
// Recipient is `to`.
|
||||
|
89
contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
Normal file
89
contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
interface IDydx {
|
||||
|
||||
/// @dev Represents the unique key that specifies an account
|
||||
struct AccountInfo {
|
||||
address owner; // The address that owns the account
|
||||
uint256 number; // A nonce that allows a single address to control many accounts
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
Deposit, // supply tokens
|
||||
Withdraw, // borrow tokens
|
||||
Transfer, // transfer balance between accounts
|
||||
Buy, // buy an amount of some token (externally)
|
||||
Sell, // sell an amount of some token (externally)
|
||||
Trade, // trade tokens against another account
|
||||
Liquidate, // liquidate an undercollateralized or expiring account
|
||||
Vaporize, // use excess tokens to zero-out a completely negative account
|
||||
Call // send arbitrary data to an address
|
||||
}
|
||||
|
||||
/// @dev Arguments that are passed to Solo in an ordered list as part of a single operation.
|
||||
/// Each ActionArgs has an actionType which specifies which action struct that this data will be
|
||||
/// parsed into before being processed.
|
||||
struct ActionArgs {
|
||||
ActionType actionType;
|
||||
uint256 accountId;
|
||||
AssetAmount amount;
|
||||
uint256 primaryMarketId;
|
||||
uint256 secondaryMarketId;
|
||||
address otherAddress;
|
||||
uint256 otherAccountId;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
enum AssetDenomination {
|
||||
Wei, // the amount is denominated in wei
|
||||
Par // the amount is denominated in par
|
||||
}
|
||||
|
||||
enum AssetReference {
|
||||
Delta, // the amount is given as a delta from the current value
|
||||
Target // the amount is given as an exact number to end up at
|
||||
}
|
||||
|
||||
struct AssetAmount {
|
||||
bool sign; // true if positive
|
||||
AssetDenomination denomination;
|
||||
AssetReference ref;
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
/// @dev The main entry-point to Solo that allows users and contracts to manage accounts.
|
||||
/// Take one or more actions on one or more accounts. The msg.sender must be the owner or
|
||||
/// operator of all accounts except for those being liquidated, vaporized, or traded with.
|
||||
/// One call to operate() is considered a singular "operation". Account collateralization is
|
||||
/// ensured only after the completion of the entire operation.
|
||||
/// @param accounts A list of all accounts that will be used in this operation. Cannot contain
|
||||
/// duplicates. In each action, the relevant account will be referred-to by its
|
||||
/// index in the list.
|
||||
/// @param actions An ordered list of all actions that will be taken in this operation. The
|
||||
/// actions will be processed in order.
|
||||
function operate(
|
||||
AccountInfo[] calldata accounts,
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external;
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
interface IDydxBridge {
|
||||
|
||||
/// @dev This is the subset of `IDydx.ActionType` that are supported by the bridge.
|
||||
enum BridgeActionType {
|
||||
Deposit, // Deposit tokens into dydx account.
|
||||
Withdraw // Withdraw tokens from dydx account.
|
||||
}
|
||||
|
||||
struct BridgeAction {
|
||||
BridgeActionType actionType; // Action to run on dydx account.
|
||||
uint256 accountId; // Index in `BridgeData.accountNumbers` for this action.
|
||||
uint256 marketId; // Market to operate on.
|
||||
uint256 conversionRateNumerator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
uint256 conversionRateDenominator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
}
|
||||
|
||||
struct BridgeData {
|
||||
uint256[] accountNumbers; // Account number used to identify the owner's specific account.
|
||||
BridgeAction[] actions; // Actions to carry out on the owner's accounts.
|
||||
}
|
||||
}
|
191
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
191
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||
import "../src/bridges/DydxBridge.sol";
|
||||
|
||||
|
||||
contract TestDydxBridgeToken {
|
||||
|
||||
uint256 private constant INIT_HOLDER_BALANCE = 10 * 10**18; // 10 tokens
|
||||
mapping (address => uint256) private _balances;
|
||||
|
||||
/// @dev Sets initial balance of token holders.
|
||||
constructor(address[] memory holders)
|
||||
public
|
||||
{
|
||||
for (uint256 i = 0; i != holders.length; ++i) {
|
||||
_balances[holders[i]] = INIT_HOLDER_BALANCE;
|
||||
}
|
||||
_balances[msg.sender] = INIT_HOLDER_BALANCE;
|
||||
}
|
||||
|
||||
/// @dev Basic transferFrom implementation.
|
||||
function transferFrom(address from, address to, uint256 amount)
|
||||
external
|
||||
returns (bool)
|
||||
{
|
||||
if (_balances[from] < amount || _balances[to] + amount < _balances[to]) {
|
||||
return false;
|
||||
}
|
||||
_balances[from] -= amount;
|
||||
_balances[to] += amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev Returns balance of `holder`.
|
||||
function balanceOf(address holder)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return _balances[holder];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// solhint-disable space-after-comma
|
||||
contract TestDydxBridge is
|
||||
IDydx,
|
||||
DydxBridge
|
||||
{
|
||||
|
||||
address private constant ALWAYS_REVERT_ADDRESS = address(1);
|
||||
address private _testTokenAddress;
|
||||
bool private _shouldRevertOnOperate;
|
||||
|
||||
event OperateAccount(
|
||||
address owner,
|
||||
uint256 number
|
||||
);
|
||||
|
||||
event OperateAction(
|
||||
ActionType actionType,
|
||||
uint256 accountId,
|
||||
bool amountSign,
|
||||
AssetDenomination amountDenomination,
|
||||
AssetReference amountRef,
|
||||
uint256 amountValue,
|
||||
uint256 primaryMarketId,
|
||||
uint256 secondaryMarketId,
|
||||
address otherAddress,
|
||||
uint256 otherAccountId,
|
||||
bytes data
|
||||
);
|
||||
|
||||
constructor(address[] memory holders)
|
||||
public
|
||||
{
|
||||
// Deploy a test token. This represents the asset being deposited/withdrawn from dydx.
|
||||
_testTokenAddress = address(new TestDydxBridgeToken(holders));
|
||||
}
|
||||
|
||||
/// @dev Simulates `operate` in dydx contract.
|
||||
/// Emits events so that arguments can be validated client-side.
|
||||
function operate(
|
||||
AccountInfo[] calldata accounts,
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external
|
||||
{
|
||||
if (_shouldRevertOnOperate) {
|
||||
revert("TestDydxBridge/SHOULD_REVERT_ON_OPERATE");
|
||||
}
|
||||
|
||||
for (uint i = 0; i < accounts.length; ++i) {
|
||||
emit OperateAccount(
|
||||
accounts[i].owner,
|
||||
accounts[i].number
|
||||
);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < actions.length; ++i) {
|
||||
emit OperateAction(
|
||||
actions[i].actionType,
|
||||
actions[i].accountId,
|
||||
actions[i].amount.sign,
|
||||
actions[i].amount.denomination,
|
||||
actions[i].amount.ref,
|
||||
actions[i].amount.value,
|
||||
actions[i].primaryMarketId,
|
||||
actions[i].secondaryMarketId,
|
||||
actions[i].otherAddress,
|
||||
actions[i].otherAccountId,
|
||||
actions[i].data
|
||||
);
|
||||
|
||||
if (actions[i].actionType == IDydx.ActionType.Withdraw) {
|
||||
require(
|
||||
IERC20Token(_testTokenAddress).transferFrom(
|
||||
address(this),
|
||||
actions[i].otherAddress,
|
||||
actions[i].amount.value
|
||||
),
|
||||
"TestDydxBridge/WITHDRAW_FAILED"
|
||||
);
|
||||
} else if (actions[i].actionType == IDydx.ActionType.Deposit) {
|
||||
require(
|
||||
IERC20Token(_testTokenAddress).transferFrom(
|
||||
actions[i].otherAddress,
|
||||
address(this),
|
||||
actions[i].amount.value
|
||||
),
|
||||
"TestDydxBridge/DEPOSIT_FAILED"
|
||||
);
|
||||
} else {
|
||||
revert("TestDydxBridge/UNSUPPORTED_ACTION");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev If `true` then subsequent calls to `operate` will revert.
|
||||
function setRevertOnOperate(bool shouldRevert)
|
||||
external
|
||||
{
|
||||
_shouldRevertOnOperate = shouldRevert;
|
||||
}
|
||||
|
||||
/// @dev Returns test token.
|
||||
function getTestToken()
|
||||
external
|
||||
returns (address)
|
||||
{
|
||||
return _testTokenAddress;
|
||||
}
|
||||
|
||||
/// @dev overrides `_getDydxAddress()` from `DeploymentConstants` to return this address.
|
||||
function _getDydxAddress()
|
||||
internal
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return address(this);
|
||||
}
|
||||
|
||||
/// @dev overrides `_getERC20BridgeProxyAddress()` from `DeploymentConstants` for testing.
|
||||
function _getERC20BridgeProxyAddress()
|
||||
internal
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return msg.sender == ALWAYS_REVERT_ADDRESS ? address(0) : msg.sender;
|
||||
}
|
||||
}
|
@@ -67,9 +67,11 @@ interface ITestContract {
|
||||
/// @dev A minimalist ERC20/WETH token.
|
||||
contract TestToken {
|
||||
|
||||
uint8 public decimals;
|
||||
ITestContract private _testContract;
|
||||
|
||||
constructor() public {
|
||||
constructor(uint8 decimals_) public {
|
||||
decimals = decimals_;
|
||||
_testContract = ITestContract(msg.sender);
|
||||
}
|
||||
|
||||
@@ -165,7 +167,7 @@ contract TestKyberBridge is
|
||||
uint256 private _nextFillAmount;
|
||||
|
||||
constructor() public {
|
||||
weth = IEtherToken(address(new TestToken()));
|
||||
weth = IEtherToken(address(new TestToken(18)));
|
||||
}
|
||||
|
||||
/// @dev Implementation of `IKyberNetworkProxy.trade()`
|
||||
@@ -195,11 +197,11 @@ contract TestKyberBridge is
|
||||
return _nextFillAmount;
|
||||
}
|
||||
|
||||
function createToken()
|
||||
function createToken(uint8 decimals)
|
||||
external
|
||||
returns (address tokenAddress)
|
||||
{
|
||||
return address(new TestToken());
|
||||
return address(new TestToken(decimals));
|
||||
}
|
||||
|
||||
function setNextFillAmount(uint256 amount)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-asset-proxy",
|
||||
"version": "3.0.1",
|
||||
"version": "3.1.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -38,8 +38,7 @@
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,ChaiBridge,TestStaticCallTarget",
|
||||
"abis": "./test/generated-artifacts/@(ChaiBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
|
||||
"abis": "./test/generated-artifacts/@(ChaiBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
@@ -52,15 +51,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -80,15 +79,16 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-erc1155": "^2.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-erc721": "^3.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-erc1155": "^2.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-erc721": "^3.0.3",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"ethereum-types": "^3.0.0",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
|
@@ -6,6 +6,7 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
|
||||
import * as DydxBridge from '../generated-artifacts/DydxBridge.json';
|
||||
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
|
||||
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
|
||||
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json';
|
||||
@@ -13,23 +14,62 @@ import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
|
||||
import * as Eth2DaiBridge from '../generated-artifacts/Eth2DaiBridge.json';
|
||||
import * as IAssetData from '../generated-artifacts/IAssetData.json';
|
||||
import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json';
|
||||
import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json';
|
||||
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
|
||||
import * as IChai from '../generated-artifacts/IChai.json';
|
||||
import * as IDydx from '../generated-artifacts/IDydx.json';
|
||||
import * as IDydxBridge from '../generated-artifacts/IDydxBridge.json';
|
||||
import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
|
||||
import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetworkProxy from '../generated-artifacts/IKyberNetworkProxy.json';
|
||||
import * as IUniswapExchange from '../generated-artifacts/IUniswapExchange.json';
|
||||
import * as IUniswapExchangeFactory from '../generated-artifacts/IUniswapExchangeFactory.json';
|
||||
import * as KyberBridge from '../generated-artifacts/KyberBridge.json';
|
||||
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
|
||||
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
|
||||
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
|
||||
import * as Ownable from '../generated-artifacts/Ownable.json';
|
||||
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
|
||||
import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json';
|
||||
import * as TestDydxBridge from '../generated-artifacts/TestDydxBridge.json';
|
||||
import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json';
|
||||
import * as TestEth2DaiBridge from '../generated-artifacts/TestEth2DaiBridge.json';
|
||||
import * as TestKyberBridge from '../generated-artifacts/TestKyberBridge.json';
|
||||
import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json';
|
||||
import * as TestUniswapBridge from '../generated-artifacts/TestUniswapBridge.json';
|
||||
import * as UniswapBridge from '../generated-artifacts/UniswapBridge.json';
|
||||
export const artifacts = {
|
||||
MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact,
|
||||
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
|
||||
Ownable: Ownable as ContractArtifact,
|
||||
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
|
||||
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
|
||||
ERC20Proxy: ERC20Proxy as ContractArtifact,
|
||||
ERC721Proxy: ERC721Proxy as ContractArtifact,
|
||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
|
||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||
DydxBridge: DydxBridge as ContractArtifact,
|
||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||
KyberBridge: KyberBridge as ContractArtifact,
|
||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||
IAssetData: IAssetData as ContractArtifact,
|
||||
IAssetProxy: IAssetProxy as ContractArtifact,
|
||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||
KyberBridge: KyberBridge as ContractArtifact,
|
||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||
IChai: IChai as ContractArtifact,
|
||||
IDydx: IDydx as ContractArtifact,
|
||||
IDydxBridge: IDydxBridge as ContractArtifact,
|
||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
||||
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
|
||||
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||
};
|
||||
|
40
contracts/asset-proxy/src/dydx_bridge_encoder.ts
Normal file
40
contracts/asset-proxy/src/dydx_bridge_encoder.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
export enum DydxBridgeActionType {
|
||||
Deposit,
|
||||
Withdraw,
|
||||
}
|
||||
|
||||
export interface DydxBrigeAction {
|
||||
actionType: DydxBridgeActionType;
|
||||
accountId: BigNumber;
|
||||
marketId: BigNumber;
|
||||
conversionRateNumerator: BigNumber;
|
||||
conversionRateDenominator: BigNumber;
|
||||
}
|
||||
|
||||
export interface DydxBridgeData {
|
||||
accountNumbers: BigNumber[];
|
||||
actions: DydxBrigeAction[];
|
||||
}
|
||||
|
||||
export const dydxBridgeDataEncoder = AbiEncoder.create([
|
||||
{
|
||||
name: 'bridgeData',
|
||||
type: 'tuple',
|
||||
components: [
|
||||
{ name: 'accountNumbers', type: 'uint256[]' },
|
||||
{
|
||||
name: 'actions',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'actionType', type: 'uint8' },
|
||||
{ name: 'accountId', type: 'uint256' },
|
||||
{ name: 'marketId', type: 'uint256' },
|
||||
{ name: 'conversionRateNumerator', type: 'uint256' },
|
||||
{ name: 'conversionRateDenominator', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
@@ -5,6 +5,8 @@ export {
|
||||
ERC20ProxyContract,
|
||||
ERC721ProxyContract,
|
||||
Eth2DaiBridgeContract,
|
||||
DydxBridgeContract,
|
||||
TestDydxBridgeContract,
|
||||
IAssetDataContract,
|
||||
IAssetProxyContract,
|
||||
MultiAssetProxyContract,
|
||||
@@ -62,3 +64,4 @@ export {
|
||||
TupleDataItem,
|
||||
StateMutability,
|
||||
} from 'ethereum-types';
|
||||
export * from './dydx_bridge_encoder';
|
||||
|
@@ -4,6 +4,7 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/chai_bridge';
|
||||
export * from '../generated-wrappers/dydx_bridge';
|
||||
export * from '../generated-wrappers/erc1155_proxy';
|
||||
export * from '../generated-wrappers/erc20_bridge_proxy';
|
||||
export * from '../generated-wrappers/erc20_proxy';
|
||||
@@ -11,8 +12,27 @@ export * from '../generated-wrappers/erc721_proxy';
|
||||
export * from '../generated-wrappers/eth2_dai_bridge';
|
||||
export * from '../generated-wrappers/i_asset_data';
|
||||
export * from '../generated-wrappers/i_asset_proxy';
|
||||
export * from '../generated-wrappers/i_asset_proxy_dispatcher';
|
||||
export * from '../generated-wrappers/i_authorizable';
|
||||
export * from '../generated-wrappers/i_chai';
|
||||
export * from '../generated-wrappers/i_dydx';
|
||||
export * from '../generated-wrappers/i_dydx_bridge';
|
||||
export * from '../generated-wrappers/i_erc20_bridge';
|
||||
export * from '../generated-wrappers/i_eth2_dai';
|
||||
export * from '../generated-wrappers/i_kyber_network_proxy';
|
||||
export * from '../generated-wrappers/i_uniswap_exchange';
|
||||
export * from '../generated-wrappers/i_uniswap_exchange_factory';
|
||||
export * from '../generated-wrappers/kyber_bridge';
|
||||
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
|
||||
export * from '../generated-wrappers/mixin_authorizable';
|
||||
export * from '../generated-wrappers/multi_asset_proxy';
|
||||
export * from '../generated-wrappers/ownable';
|
||||
export * from '../generated-wrappers/static_call_proxy';
|
||||
export * from '../generated-wrappers/test_chai_bridge';
|
||||
export * from '../generated-wrappers/test_dydx_bridge';
|
||||
export * from '../generated-wrappers/test_erc20_bridge';
|
||||
export * from '../generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../generated-wrappers/test_kyber_bridge';
|
||||
export * from '../generated-wrappers/test_static_call_target';
|
||||
export * from '../generated-wrappers/test_uniswap_bridge';
|
||||
export * from '../generated-wrappers/uniswap_bridge';
|
||||
|
@@ -6,6 +6,7 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
|
||||
import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json';
|
||||
import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
|
||||
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
||||
import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.json';
|
||||
@@ -16,6 +17,8 @@ import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json';
|
||||
import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json';
|
||||
import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json';
|
||||
import * as IChai from '../test/generated-artifacts/IChai.json';
|
||||
import * as IDydx from '../test/generated-artifacts/IDydx.json';
|
||||
import * as IDydxBridge from '../test/generated-artifacts/IDydxBridge.json';
|
||||
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
|
||||
@@ -28,6 +31,7 @@ import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.js
|
||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
||||
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
||||
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
|
||||
import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json';
|
||||
import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
|
||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
|
||||
@@ -45,6 +49,7 @@ export const artifacts = {
|
||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||
DydxBridge: DydxBridge as ContractArtifact,
|
||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||
KyberBridge: KyberBridge as ContractArtifact,
|
||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||
@@ -53,12 +58,15 @@ export const artifacts = {
|
||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||
IChai: IChai as ContractArtifact,
|
||||
IDydx: IDydx as ContractArtifact,
|
||||
IDydxBridge: IDydxBridge as ContractArtifact,
|
||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
||||
|
399
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
399
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
@@ -0,0 +1,399 @@
|
||||
import { LibMathRevertErrors } from '@0x/contracts-exchange-libs';
|
||||
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId, RevertReason } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DydxBridgeActionType, DydxBridgeData, dydxBridgeDataEncoder } from '../src/dydx_bridge_encoder';
|
||||
import { ERC20BridgeProxyContract, IAssetDataContract } from '../src/wrappers';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { TestDydxBridgeContract, TestDydxBridgeEvents } from './wrappers';
|
||||
|
||||
blockchainTests.resets('DydxBridge unit tests', env => {
|
||||
const defaultAccountNumber = new BigNumber(1);
|
||||
const marketId = new BigNumber(2);
|
||||
const defaultAmount = new BigNumber(4);
|
||||
const notAuthorized = '0x0000000000000000000000000000000000000001';
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
let testContract: TestDydxBridgeContract;
|
||||
let testProxyContract: ERC20BridgeProxyContract;
|
||||
let assetDataEncoder: IAssetDataContract;
|
||||
let owner: string;
|
||||
let authorized: string;
|
||||
let accountOwner: string;
|
||||
let receiver: string;
|
||||
|
||||
before(async () => {
|
||||
// Get accounts
|
||||
const accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
[owner, authorized, accountOwner, receiver] = accounts;
|
||||
|
||||
// Deploy dydx bridge
|
||||
testContract = await TestDydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestDydxBridge,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
[accountOwner, receiver],
|
||||
);
|
||||
|
||||
// Deploy test erc20 bridge proxy
|
||||
testProxyContract = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ERC20BridgeProxy,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
await testProxyContract.addAuthorizedAddress(authorized).awaitTransactionSuccessAsync({ from: owner });
|
||||
|
||||
// Setup asset data encoder
|
||||
assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
||||
});
|
||||
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
const callBridgeTransferFrom = async (
|
||||
from: string,
|
||||
to: string,
|
||||
amount: BigNumber,
|
||||
bridgeData: DydxBridgeData,
|
||||
sender: string,
|
||||
): Promise<string> => {
|
||||
const returnValue = await testContract
|
||||
.bridgeTransferFrom(
|
||||
constants.NULL_ADDRESS,
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
dydxBridgeDataEncoder.encode({ bridgeData }),
|
||||
)
|
||||
.callAsync({ from: sender });
|
||||
return returnValue;
|
||||
};
|
||||
const executeBridgeTransferFromAndVerifyEvents = async (
|
||||
from: string,
|
||||
to: string,
|
||||
amount: BigNumber,
|
||||
bridgeData: DydxBridgeData,
|
||||
sender: string,
|
||||
): Promise<void> => {
|
||||
// Execute transaction.
|
||||
const txReceipt = await testContract
|
||||
.bridgeTransferFrom(
|
||||
constants.NULL_ADDRESS,
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
dydxBridgeDataEncoder.encode({ bridgeData }),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: sender });
|
||||
|
||||
// Verify `OperateAccount` event.
|
||||
const expectedOperateAccountEvents = [];
|
||||
for (const accountNumber of bridgeData.accountNumbers) {
|
||||
expectedOperateAccountEvents.push({
|
||||
owner: accountOwner,
|
||||
number: accountNumber,
|
||||
});
|
||||
}
|
||||
verifyEventsFromLogs(txReceipt.logs, expectedOperateAccountEvents, TestDydxBridgeEvents.OperateAccount);
|
||||
|
||||
// Verify `OperateAction` event.
|
||||
const weiDenomination = 0;
|
||||
const deltaAmountRef = 0;
|
||||
const expectedOperateActionEvents = [];
|
||||
for (const action of bridgeData.actions) {
|
||||
expectedOperateActionEvents.push({
|
||||
actionType: action.actionType as number,
|
||||
accountId: action.accountId,
|
||||
amountSign: action.actionType === DydxBridgeActionType.Deposit ? true : false,
|
||||
amountDenomination: weiDenomination,
|
||||
amountRef: deltaAmountRef,
|
||||
amountValue: action.conversionRateDenominator.gt(0)
|
||||
? amount
|
||||
.times(action.conversionRateNumerator)
|
||||
.dividedToIntegerBy(action.conversionRateDenominator)
|
||||
: amount,
|
||||
primaryMarketId: marketId,
|
||||
secondaryMarketId: constants.ZERO_AMOUNT,
|
||||
otherAddress: action.actionType === DydxBridgeActionType.Deposit ? from : to,
|
||||
otherAccountId: constants.ZERO_AMOUNT,
|
||||
data: '0x',
|
||||
});
|
||||
}
|
||||
verifyEventsFromLogs(txReceipt.logs, expectedOperateActionEvents, TestDydxBridgeEvents.OperateAction);
|
||||
};
|
||||
it('succeeds when calling with zero amount', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
constants.ZERO_AMOUNT,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling with no accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling with no actions', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and a single account', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when calling `operate` with multiple actions under a single account', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when scaling the `amount` to deposit', async () => {
|
||||
const conversionRateNumerator = new BigNumber(1);
|
||||
const conversionRateDenominator = new BigNumber(2);
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [
|
||||
defaultWithdrawAction,
|
||||
{
|
||||
...defaultDepositAction,
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
},
|
||||
],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('succeeds when scaling the `amount` to withdraw', async () => {
|
||||
const conversionRateNumerator = new BigNumber(1);
|
||||
const conversionRateDenominator = new BigNumber(2);
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [
|
||||
defaultDepositAction,
|
||||
{
|
||||
...defaultWithdrawAction,
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
},
|
||||
],
|
||||
};
|
||||
await executeBridgeTransferFromAndVerifyEvents(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
});
|
||||
it('reverts if not called by the ERC20 Bridge Proxy', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
const callBridgeTransferFromPromise = callBridgeTransferFrom(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
notAuthorized,
|
||||
);
|
||||
const expectedError = RevertReason.DydxBridgeOnlyCallableByErc20BridgeProxy;
|
||||
return expect(callBridgeTransferFromPromise).to.revertWith(expectedError);
|
||||
});
|
||||
it('should return magic bytes if call succeeds', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
const returnValue = await callBridgeTransferFrom(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
expect(returnValue).to.equal(AssetProxyId.ERC20Bridge);
|
||||
});
|
||||
it('should revert when `Operate` reverts', async () => {
|
||||
// Set revert flag.
|
||||
await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync();
|
||||
|
||||
// Execute transfer.
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
const tx = callBridgeTransferFrom(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE';
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert when there is a rounding error', async () => {
|
||||
// Setup a rounding error
|
||||
const conversionRateNumerator = new BigNumber(5318);
|
||||
const conversionRateDenominator = new BigNumber(47958);
|
||||
const amount = new BigNumber(9000);
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [
|
||||
defaultDepositAction,
|
||||
{
|
||||
...defaultWithdrawAction,
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Execute transfer and assert error.
|
||||
const tx = callBridgeTransferFrom(accountOwner, receiver, amount, bridgeData, authorized);
|
||||
const expectedError = new LibMathRevertErrors.RoundingError(
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
amount,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC20BridgeProxy.transferFrom()', () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction],
|
||||
};
|
||||
let assetData: string;
|
||||
|
||||
before(async () => {
|
||||
const testTokenAddress = await testContract.getTestToken().callAsync();
|
||||
assetData = assetDataEncoder
|
||||
.ERC20Bridge(testTokenAddress, testContract.address, dydxBridgeDataEncoder.encode({ bridgeData }))
|
||||
.getABIEncodedTransactionData();
|
||||
});
|
||||
|
||||
it('should succeed if `bridgeTransferFrom` succeeds', async () => {
|
||||
await testProxyContract
|
||||
.transferFrom(assetData, accountOwner, receiver, defaultAmount)
|
||||
.awaitTransactionSuccessAsync({ from: authorized });
|
||||
});
|
||||
it('should revert if `bridgeTransferFrom` reverts', async () => {
|
||||
// Set revert flag.
|
||||
await testContract.setRevertOnOperate(true).awaitTransactionSuccessAsync();
|
||||
const tx = testProxyContract
|
||||
.transferFrom(assetData, accountOwner, receiver, defaultAmount)
|
||||
.awaitTransactionSuccessAsync({ from: authorized });
|
||||
const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE';
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
@@ -3,6 +3,7 @@ import {
|
||||
constants,
|
||||
expect,
|
||||
getRandomInteger,
|
||||
getRandomPortion,
|
||||
randomAddress,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
@@ -17,6 +18,12 @@ import { TestKyberBridgeContract, TestKyberBridgeEvents } from './wrappers';
|
||||
|
||||
blockchainTests.resets('KyberBridge unit tests', env => {
|
||||
const KYBER_ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
const FROM_TOKEN_DECIMALS = 6;
|
||||
const TO_TOKEN_DECIMALS = 18;
|
||||
const FROM_TOKEN_BASE = new BigNumber(10).pow(FROM_TOKEN_DECIMALS);
|
||||
const TO_TOKEN_BASE = new BigNumber(10).pow(TO_TOKEN_DECIMALS);
|
||||
const WETH_BASE = new BigNumber(10).pow(18);
|
||||
const KYBER_RATE_BASE = WETH_BASE;
|
||||
let testContract: TestKyberBridgeContract;
|
||||
|
||||
before(async () => {
|
||||
@@ -45,10 +52,10 @@ blockchainTests.resets('KyberBridge unit tests', env => {
|
||||
|
||||
before(async () => {
|
||||
wethAddress = await testContract.weth().callAsync();
|
||||
fromTokenAddress = await testContract.createToken().callAsync();
|
||||
await testContract.createToken().awaitTransactionSuccessAsync();
|
||||
toTokenAddress = await testContract.createToken().callAsync();
|
||||
await testContract.createToken().awaitTransactionSuccessAsync();
|
||||
fromTokenAddress = await testContract.createToken(FROM_TOKEN_DECIMALS).callAsync();
|
||||
await testContract.createToken(FROM_TOKEN_DECIMALS).awaitTransactionSuccessAsync();
|
||||
toTokenAddress = await testContract.createToken(TO_TOKEN_DECIMALS).callAsync();
|
||||
await testContract.createToken(TO_TOKEN_DECIMALS).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
const STATIC_KYBER_TRADE_ARGS = {
|
||||
@@ -75,13 +82,14 @@ blockchainTests.resets('KyberBridge unit tests', env => {
|
||||
}
|
||||
|
||||
function createTransferFromOpts(opts?: Partial<TransferFromOpts>): TransferFromOpts {
|
||||
const amount = getRandomInteger(1, TO_TOKEN_BASE.times(100));
|
||||
return {
|
||||
fromTokenAddress,
|
||||
toTokenAddress,
|
||||
amount,
|
||||
toAddress: randomAddress(),
|
||||
amount: getRandomInteger(1, 10e18),
|
||||
fillAmount: getRandomInteger(1, 10e18),
|
||||
fromTokenBalance: getRandomInteger(1, 10e18),
|
||||
fillAmount: getRandomPortion(amount),
|
||||
fromTokenBalance: getRandomInteger(1, FROM_TOKEN_BASE.times(100)),
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
@@ -119,9 +127,12 @@ blockchainTests.resets('KyberBridge unit tests', env => {
|
||||
}
|
||||
|
||||
function getMinimumConversionRate(opts: TransferFromOpts): BigNumber {
|
||||
const fromBase = opts.fromTokenAddress === wethAddress ? WETH_BASE : FROM_TOKEN_BASE;
|
||||
const toBase = opts.toTokenAddress === wethAddress ? WETH_BASE : TO_TOKEN_BASE;
|
||||
return opts.amount
|
||||
.times(constants.ONE_ETHER)
|
||||
.div(opts.fromTokenBalance)
|
||||
.div(toBase)
|
||||
.div(opts.fromTokenBalance.div(fromBase))
|
||||
.times(KYBER_RATE_BASE)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
}
|
||||
|
||||
|
@@ -176,7 +176,7 @@ blockchainTests.resets('UniswapBridge unit tests', env => {
|
||||
expect(calls[0].exchange).to.eq(exchangeAddress);
|
||||
expect(calls[0].tokensSold).to.bignumber.eq(opts.fromTokenBalance);
|
||||
expect(calls[0].minTokensBought).to.bignumber.eq(opts.amount);
|
||||
expect(calls[0].minEthBought).to.bignumber.eq(0);
|
||||
expect(calls[0].minEthBought).to.bignumber.eq(1);
|
||||
expect(calls[0].deadline).to.bignumber.eq(blockTime);
|
||||
expect(calls[0].recipient).to.eq(opts.toAddress);
|
||||
expect(calls[0].toTokenAddress).to.eq(opts.toTokenAddress);
|
||||
|
@@ -4,6 +4,7 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/chai_bridge';
|
||||
export * from '../test/generated-wrappers/dydx_bridge';
|
||||
export * from '../test/generated-wrappers/erc1155_proxy';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
||||
export * from '../test/generated-wrappers/erc20_proxy';
|
||||
@@ -14,6 +15,8 @@ export * from '../test/generated-wrappers/i_asset_proxy';
|
||||
export * from '../test/generated-wrappers/i_asset_proxy_dispatcher';
|
||||
export * from '../test/generated-wrappers/i_authorizable';
|
||||
export * from '../test/generated-wrappers/i_chai';
|
||||
export * from '../test/generated-wrappers/i_dydx';
|
||||
export * from '../test/generated-wrappers/i_dydx_bridge';
|
||||
export * from '../test/generated-wrappers/i_erc20_bridge';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_network_proxy';
|
||||
@@ -26,6 +29,7 @@ export * from '../test/generated-wrappers/multi_asset_proxy';
|
||||
export * from '../test/generated-wrappers/ownable';
|
||||
export * from '../test/generated-wrappers/static_call_proxy';
|
||||
export * from '../test/generated-wrappers/test_chai_bridge';
|
||||
export * from '../test/generated-wrappers/test_dydx_bridge';
|
||||
export * from '../test/generated-wrappers/test_erc20_bridge';
|
||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../test/generated-wrappers/test_kyber_bridge';
|
||||
|
@@ -4,6 +4,7 @@
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/ChaiBridge.json",
|
||||
"generated-artifacts/DydxBridge.json",
|
||||
"generated-artifacts/ERC1155Proxy.json",
|
||||
"generated-artifacts/ERC20BridgeProxy.json",
|
||||
"generated-artifacts/ERC20Proxy.json",
|
||||
@@ -11,12 +12,32 @@
|
||||
"generated-artifacts/Eth2DaiBridge.json",
|
||||
"generated-artifacts/IAssetData.json",
|
||||
"generated-artifacts/IAssetProxy.json",
|
||||
"generated-artifacts/IAssetProxyDispatcher.json",
|
||||
"generated-artifacts/IAuthorizable.json",
|
||||
"generated-artifacts/IChai.json",
|
||||
"generated-artifacts/IDydx.json",
|
||||
"generated-artifacts/IDydxBridge.json",
|
||||
"generated-artifacts/IERC20Bridge.json",
|
||||
"generated-artifacts/IEth2Dai.json",
|
||||
"generated-artifacts/IKyberNetworkProxy.json",
|
||||
"generated-artifacts/IUniswapExchange.json",
|
||||
"generated-artifacts/IUniswapExchangeFactory.json",
|
||||
"generated-artifacts/KyberBridge.json",
|
||||
"generated-artifacts/MixinAssetProxyDispatcher.json",
|
||||
"generated-artifacts/MixinAuthorizable.json",
|
||||
"generated-artifacts/MultiAssetProxy.json",
|
||||
"generated-artifacts/Ownable.json",
|
||||
"generated-artifacts/StaticCallProxy.json",
|
||||
"generated-artifacts/TestChaiBridge.json",
|
||||
"generated-artifacts/TestDydxBridge.json",
|
||||
"generated-artifacts/TestERC20Bridge.json",
|
||||
"generated-artifacts/TestEth2DaiBridge.json",
|
||||
"generated-artifacts/TestKyberBridge.json",
|
||||
"generated-artifacts/TestStaticCallTarget.json",
|
||||
"generated-artifacts/TestUniswapBridge.json",
|
||||
"generated-artifacts/UniswapBridge.json",
|
||||
"test/generated-artifacts/ChaiBridge.json",
|
||||
"test/generated-artifacts/DydxBridge.json",
|
||||
"test/generated-artifacts/ERC1155Proxy.json",
|
||||
"test/generated-artifacts/ERC20BridgeProxy.json",
|
||||
"test/generated-artifacts/ERC20Proxy.json",
|
||||
@@ -27,6 +48,8 @@
|
||||
"test/generated-artifacts/IAssetProxyDispatcher.json",
|
||||
"test/generated-artifacts/IAuthorizable.json",
|
||||
"test/generated-artifacts/IChai.json",
|
||||
"test/generated-artifacts/IDydx.json",
|
||||
"test/generated-artifacts/IDydxBridge.json",
|
||||
"test/generated-artifacts/IERC20Bridge.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberNetworkProxy.json",
|
||||
@@ -39,6 +62,7 @@
|
||||
"test/generated-artifacts/Ownable.json",
|
||||
"test/generated-artifacts/StaticCallProxy.json",
|
||||
"test/generated-artifacts/TestChaiBridge.json",
|
||||
"test/generated-artifacts/TestDydxBridge.json",
|
||||
"test/generated-artifacts/TestERC20Bridge.json",
|
||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||
"test/generated-artifacts/TestKyberBridge.json",
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "3.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-coordinator",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,19 +52,19 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-exchange": "^3.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-exchange": "^3.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -84,14 +84,14 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.1",
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contract-addresses": "^4.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/json-schemas": "^5.0.1",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/assert": "^3.0.3",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contract-addresses": "^4.2.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/json-schemas": "^5.0.3",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"ethereum-types": "^3.0.0",
|
||||
"http-status-codes": "^1.3.2"
|
||||
},
|
||||
|
@@ -1,4 +1,23 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fixed ERC721 duplicate token ID bug",
|
||||
"pr": 2400
|
||||
}
|
||||
],
|
||||
"timestamp": 1578272714
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "1.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "1.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.3 - _January 6, 2020_
|
||||
|
||||
* Fixed ERC721 duplicate token ID bug (#2400)
|
||||
|
||||
## v1.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"evmVersion": "constantinople",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1666,
|
||||
"runs": 200,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"outputSelection": {
|
||||
|
@@ -35,8 +35,7 @@ contract DevUtils is
|
||||
OrderValidationUtils,
|
||||
LibTransactionDecoder,
|
||||
LibEIP712ExchangeDomain,
|
||||
EthBalanceChecker,
|
||||
OrderTransferSimulationUtils
|
||||
EthBalanceChecker
|
||||
{
|
||||
constructor (address _exchange)
|
||||
public
|
||||
|
@@ -147,7 +147,7 @@ contract LibAssetData {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balance will be 0 if assetProxyId is unknown
|
||||
return balance;
|
||||
@@ -316,7 +316,7 @@ contract LibAssetData {
|
||||
return (balances, allowances);
|
||||
}
|
||||
|
||||
/// @dev Decode AssetProxy identifier
|
||||
/// @dev Decode AssetProxy identifier
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20, ERC-721, ERC1155, or MultiAsset asset.
|
||||
/// @return The AssetProxy identifier
|
||||
function decodeAssetProxyId(bytes memory assetData)
|
||||
@@ -353,7 +353,7 @@ contract LibAssetData {
|
||||
|
||||
/// @dev Decode ERC-20 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20 asset.
|
||||
/// @return The AssetProxy identifier, and the address of the ERC-20
|
||||
/// @return The AssetProxy identifier, and the address of the ERC-20
|
||||
/// contract hosting this asset.
|
||||
function decodeERC20AssetData(bytes memory assetData)
|
||||
public
|
||||
@@ -591,7 +591,7 @@ contract LibAssetData {
|
||||
|
||||
function revertIfInvalidAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
pure
|
||||
{
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
|
@@ -54,6 +54,51 @@ contract OrderTransferSimulationUtils is
|
||||
_EXCHANGE = IExchange(_exchange);
|
||||
}
|
||||
|
||||
/// @dev Simulates the maker transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
/// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill.
|
||||
/// @return The index of the first failed transfer (or 4 if all transfers are successful).
|
||||
function getSimulatedOrderMakerTransferResults(
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults orderTransferResults)
|
||||
{
|
||||
LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResults(
|
||||
order,
|
||||
takerAssetFillAmount,
|
||||
_EXCHANGE.protocolFeeMultiplier(),
|
||||
tx.gasprice
|
||||
);
|
||||
|
||||
bytes[] memory assetData = new bytes[](2);
|
||||
address[] memory fromAddresses = new address[](2);
|
||||
address[] memory toAddresses = new address[](2);
|
||||
uint256[] memory amounts = new uint256[](2);
|
||||
|
||||
// Transfer `makerAsset` from maker to taker
|
||||
assetData[0] = order.makerAssetData;
|
||||
fromAddresses[0] = order.makerAddress;
|
||||
toAddresses[0] = takerAddress;
|
||||
amounts[0] = fillResults.makerAssetFilledAmount;
|
||||
|
||||
// Transfer `makerFeeAsset` from maker to feeRecipient
|
||||
assetData[1] = order.makerFeeAssetData;
|
||||
fromAddresses[1] = order.makerAddress;
|
||||
toAddresses[1] = order.feeRecipientAddress;
|
||||
amounts[1] = fillResults.makerFeePaid;
|
||||
|
||||
return _simulateTransferFromCalls(
|
||||
assetData,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
amounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
@@ -104,6 +149,54 @@ contract OrderTransferSimulationUtils is
|
||||
toAddresses[3] = order.feeRecipientAddress;
|
||||
amounts[3] = fillResults.makerFeePaid;
|
||||
|
||||
return _simulateTransferFromCalls(
|
||||
assetData,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
amounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer.
|
||||
/// @param orders Array of orders to individually simulate transfers for.
|
||||
/// @param takerAddresses Array of addresses of takers that will fill each order.
|
||||
/// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order.
|
||||
/// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order.
|
||||
function getSimulatedOrdersTransferResults(
|
||||
LibOrder.Order[] memory orders,
|
||||
address[] memory takerAddresses,
|
||||
uint256[] memory takerAssetFillAmounts
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults[] memory orderTransferResults)
|
||||
{
|
||||
uint256 length = orders.length;
|
||||
orderTransferResults = new OrderTransferResults[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
orderTransferResults[i] = getSimulatedOrderTransferResults(
|
||||
orders[i],
|
||||
takerAddresses[i],
|
||||
takerAssetFillAmounts[i]
|
||||
);
|
||||
}
|
||||
return orderTransferResults;
|
||||
}
|
||||
|
||||
/// @dev Makes the simulation call with information about the transfers and processes
|
||||
/// the returndata.
|
||||
/// @param assetData The assetdata to use to make transfers.
|
||||
/// @param fromAddresses The addresses to transfer funds.
|
||||
/// @param toAddresses The addresses that will receive funds
|
||||
/// @param amounts The amounts involved in the transfer.
|
||||
function _simulateTransferFromCalls(
|
||||
bytes[] memory assetData,
|
||||
address[] memory fromAddresses,
|
||||
address[] memory toAddresses,
|
||||
uint256[] memory amounts
|
||||
)
|
||||
internal
|
||||
returns (OrderTransferResults orderTransferResults)
|
||||
{
|
||||
// Encode data for `simulateDispatchTransferFromCalls(assetData, fromAddresses, toAddresses, amounts)`
|
||||
bytes memory simulateDispatchTransferFromCallsData = abi.encodeWithSelector(
|
||||
IExchange(address(0)).simulateDispatchTransferFromCalls.selector,
|
||||
@@ -132,29 +225,4 @@ contract OrderTransferSimulationUtils is
|
||||
revert("UNKNOWN_RETURN_DATA");
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer.
|
||||
/// @param orders Array of orders to individually simulate transfers for.
|
||||
/// @param takerAddresses Array of addresses of takers that will fill each order.
|
||||
/// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order.
|
||||
/// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order.
|
||||
function getSimulatedOrdersTransferResults(
|
||||
LibOrder.Order[] memory orders,
|
||||
address[] memory takerAddresses,
|
||||
uint256[] memory takerAssetFillAmounts
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults[] memory orderTransferResults)
|
||||
{
|
||||
uint256 length = orders.length;
|
||||
orderTransferResults = new OrderTransferResults[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
orderTransferResults[i] = getSimulatedOrderTransferResults(
|
||||
orders[i],
|
||||
takerAddresses[i],
|
||||
takerAssetFillAmounts[i]
|
||||
);
|
||||
}
|
||||
return orderTransferResults;
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
@@ -25,10 +25,12 @@ import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "./LibAssetData.sol";
|
||||
import "./OrderTransferSimulationUtils.sol";
|
||||
|
||||
|
||||
contract OrderValidationUtils is
|
||||
LibAssetData
|
||||
LibAssetData,
|
||||
OrderTransferSimulationUtils
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
using LibSafeMath for uint256;
|
||||
@@ -50,7 +52,6 @@ contract OrderValidationUtils is
|
||||
/// amount of each asset that can be filled.
|
||||
function getOrderRelevantState(LibOrder.Order memory order, bytes memory signature)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
@@ -99,7 +100,6 @@ contract OrderValidationUtils is
|
||||
} else {
|
||||
// Get the transferable amount of the `makerFeeAsset`
|
||||
uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(makerAddress, order.makerFeeAssetData);
|
||||
|
||||
uint256 transferableMakerToTakerAmount = LibMath.getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
@@ -120,6 +120,13 @@ contract OrderValidationUtils is
|
||||
transferableTakerAssetAmount
|
||||
);
|
||||
|
||||
// Execute the maker transfers.
|
||||
fillableTakerAssetAmount = getSimulatedOrderMakerTransferResults(
|
||||
order,
|
||||
order.takerAddress,
|
||||
fillableTakerAssetAmount
|
||||
) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0;
|
||||
|
||||
return (orderInfo, fillableTakerAssetAmount, isValidSignature);
|
||||
}
|
||||
|
||||
@@ -135,7 +142,6 @@ contract OrderValidationUtils is
|
||||
/// the `takerAssetData` to get the final amount of each asset that can be filled.
|
||||
function getOrderRelevantStates(LibOrder.Order[] memory orders, bytes[] memory signatures)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory ordersInfo,
|
||||
uint256[] memory fillableTakerAssetAmounts,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-dev-utils",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -8,7 +8,7 @@
|
||||
"main": "lib/src/index.js",
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"test": "yarn assert_deployable && echo !!! Tests are run via @0x/contracts-tests !!!",
|
||||
"test": "yarn assert_deployable && echo !!! Tests are run via @0x/contracts-integrations !!!",
|
||||
"assert_deployable": "node -e \"const bytecodeLen = (require('./generated-artifacts/DevUtils.json').compilerOutput.evm.bytecode.object.length-2)/2; assert(bytecodeLen<=0x6000,'DevUtils contract is too big to deploy, per EIP-170. '+bytecodeLen+'>'+0x6000)\"",
|
||||
"build:ci": "yarn build",
|
||||
"pre_build": "run-s compile quantify_bytecode contracts:gen generate_contract_wrappers contracts:copy",
|
||||
@@ -41,10 +41,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/assert": "^3.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/assert": "^3.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@types/node": "*",
|
||||
@@ -59,7 +59,7 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1"
|
||||
"@0x/base-contract": "^6.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "2.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "2.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "2.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc1155",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,15 +52,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -80,10 +80,10 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -1,4 +1,24 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add gas limits to external quote calls.",
|
||||
"pr": 2405
|
||||
}
|
||||
],
|
||||
"timestamp": 1578272714
|
||||
},
|
||||
{
|
||||
"version": "1.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Do not query empty/unsigned orders. Swallow revets on DEX quotes.",
|
||||
"pr": 2395
|
||||
}
|
||||
],
|
||||
"timestamp": 1576540892
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "1.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.3 - _January 6, 2020_
|
||||
|
||||
* Add gas limits to external quote calls. (#2405)
|
||||
|
||||
## v1.0.2 - _December 17, 2019_
|
||||
|
||||
* Do not query empty/unsigned orders. Swallow revets on DEX quotes. (#2395)
|
||||
|
||||
## v1.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
|
||||
|
||||
contract DeploymentConstants {
|
||||
|
||||
/// @dev Address of the 0x Exchange contract.
|
||||
address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712;
|
||||
/// @dev Address of the Eth2Dai MatchingMarket contract.
|
||||
address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e;
|
||||
/// @dev Address of the UniswapExchangeFactory contract.
|
||||
address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
|
||||
/// @dev Address of the KyberNeworkProxy contract.
|
||||
address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755;
|
||||
/// @dev Address of the WETH contract.
|
||||
address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
/// @dev Kyber ETH pseudo-address.
|
||||
address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
|
||||
/// @dev An overridable way to retrieve the 0x Exchange contract.
|
||||
/// @return zeroex The 0x Exchange contract.
|
||||
function _getExchangeContract()
|
||||
internal
|
||||
view
|
||||
returns (IExchange zeroex)
|
||||
{
|
||||
return IExchange(EXCHANGE_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Eth2Dai exchange contract.
|
||||
/// @return eth2dai The Eth2Dai exchange contract.
|
||||
function _getEth2DaiContract()
|
||||
internal
|
||||
view
|
||||
returns (IEth2Dai eth2dai)
|
||||
{
|
||||
return IEth2Dai(ETH2DAI_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Uniswap exchange factory contract.
|
||||
/// @return uniswap The UniswapExchangeFactory contract.
|
||||
function _getUniswapExchangeFactoryContract()
|
||||
internal
|
||||
view
|
||||
returns (IUniswapExchangeFactory uniswap)
|
||||
{
|
||||
return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Kyber network proxy contract.
|
||||
/// @return kyber The KyberNeworkProxy contract.
|
||||
function _getKyberNetworkContract()
|
||||
internal
|
||||
view
|
||||
returns (IKyberNetwork kyber)
|
||||
{
|
||||
return IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the WETH contract address.
|
||||
/// @return weth The WETH contract address.
|
||||
function _getWETHAddress()
|
||||
internal
|
||||
view
|
||||
returns (address weth)
|
||||
{
|
||||
return WETH_ADDRESS;
|
||||
}
|
||||
}
|
@@ -23,11 +23,13 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IDevUtils.sol";
|
||||
import "./IERC20BridgeSampler.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
import "./IUniswapExchangeQuotes.sol";
|
||||
import "./DeploymentConstants.sol";
|
||||
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
@@ -35,29 +37,38 @@ contract ERC20BridgeSampler is
|
||||
DeploymentConstants
|
||||
{
|
||||
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
|
||||
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 600e3;
|
||||
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3;
|
||||
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 250e3;
|
||||
|
||||
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||
/// each taker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleSells(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures,
|
||||
address[] memory sources,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableTakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
)
|
||||
{
|
||||
require(orders.length != 0, "EMPTY_ORDERS");
|
||||
orderInfos = queryOrders(orders);
|
||||
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
|
||||
orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts(
|
||||
orders,
|
||||
orderSignatures
|
||||
);
|
||||
makerTokenAmountsBySource = sampleSells(
|
||||
sources,
|
||||
_assetDataToTokenAddress(orders[0].takerAssetData),
|
||||
@@ -68,26 +79,32 @@ contract ERC20BridgeSampler is
|
||||
|
||||
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||
/// each maker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleBuys(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures,
|
||||
address[] memory sources,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableMakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
)
|
||||
{
|
||||
require(orders.length != 0, "EMPTY_ORDERS");
|
||||
orderInfos = queryOrders(orders);
|
||||
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
|
||||
orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts(
|
||||
orders,
|
||||
orderSignatures
|
||||
);
|
||||
makerTokenAmountsBySource = sampleBuys(
|
||||
sources,
|
||||
_assetDataToTokenAddress(orders[0].takerAssetData),
|
||||
@@ -96,18 +113,77 @@ contract ERC20BridgeSampler is
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Queries the status of several native orders.
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// maker/taker asset amounts (returning 0).
|
||||
/// @param orders Native orders to query.
|
||||
/// @return orderInfos Order info for each respective order.
|
||||
function queryOrders(LibOrder.Order[] memory orders)
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableTakerAssetAmounts(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (LibOrder.OrderInfo[] memory orderInfos)
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts)
|
||||
{
|
||||
uint256 numOrders = orders.length;
|
||||
orderInfos = new LibOrder.OrderInfo[](numOrders);
|
||||
for (uint256 i = 0; i < numOrders; i++) {
|
||||
orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]);
|
||||
orderFillableTakerAssetAmounts = new uint256[](orders.length);
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
// Ignore orders with no signature or empty maker/taker amounts.
|
||||
if (orderSignatures[i].length == 0 ||
|
||||
orders[i].makerAssetAmount == 0 ||
|
||||
orders[i].takerAssetAmount == 0) {
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
continue;
|
||||
}
|
||||
(
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
) = IDevUtils(_getDevUtilsAddress()).getOrderRelevantState(
|
||||
orders[i],
|
||||
orderSignatures[i]
|
||||
);
|
||||
// The fillable amount is zero if the order is not fillable or if the
|
||||
// signature is invalid.
|
||||
if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE ||
|
||||
!isValidSignature) {
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
} else {
|
||||
orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableMakerAssetAmounts(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts)
|
||||
{
|
||||
orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts(
|
||||
orders,
|
||||
orderSignatures
|
||||
);
|
||||
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
|
||||
// convert them to maker asset amounts.
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
if (orderFillableMakerAssetAmounts[i] != 0) {
|
||||
orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil(
|
||||
orderFillableMakerAssetAmounts[i],
|
||||
orders[i].takerAssetAmount,
|
||||
orders[i].makerAssetAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,18 +263,25 @@ contract ERC20BridgeSampler is
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken;
|
||||
address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken;
|
||||
address _takerToken = takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken;
|
||||
address _makerToken = makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken;
|
||||
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
||||
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(uint256 rate,) = _getKyberNetworkContract().getExpectedRate(
|
||||
_takerToken,
|
||||
_makerToken,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getKyberNetworkProxyAddress().staticcall.gas(KYBER_SAMPLE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IKyberNetwork(0).getExpectedRate.selector,
|
||||
_takerToken,
|
||||
_makerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 rate = 0;
|
||||
if (didSucceed) {
|
||||
rate = abi.decode(resultData, (uint256));
|
||||
}
|
||||
makerTokenAmounts[i] =
|
||||
rate *
|
||||
takerTokenAmounts[i] *
|
||||
@@ -227,11 +310,19 @@ contract ERC20BridgeSampler is
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount(
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getBuyAmount.selector,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,11 +345,19 @@ contract ERC20BridgeSampler is
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount(
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getPayAmount.selector,
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,26 +379,38 @@ contract ERC20BridgeSampler is
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
if (makerToken == _getWETHAddress()) {
|
||||
makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice(
|
||||
if (makerToken == _getWethAddress()) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWETHAddress()) {
|
||||
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice(
|
||||
uint256 ethBought = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
|
||||
ethBought
|
||||
);
|
||||
if (ethBought != 0) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
ethBought
|
||||
);
|
||||
} else {
|
||||
makerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,26 +433,38 @@ contract ERC20BridgeSampler is
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
if (makerToken == _getWETHAddress()) {
|
||||
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
|
||||
if (makerToken == _getWethAddress()) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWETHAddress()) {
|
||||
takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice(
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice(
|
||||
uint256 ethSold = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
|
||||
ethSold
|
||||
);
|
||||
if (ethSold != 0) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
ethSold
|
||||
);
|
||||
} else {
|
||||
takerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +480,35 @@ contract ERC20BridgeSampler is
|
||||
return LibERC20Token.decimals(tokenAddress);
|
||||
}
|
||||
|
||||
/// @dev Gracefully calls a Uniswap pricing function.
|
||||
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
|
||||
/// @param functionSelector Selector of the target function.
|
||||
/// @param inputAmount Quantity parameter particular to the pricing function.
|
||||
/// @return outputAmount The returned amount from the function call. Will be
|
||||
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
|
||||
function _callUniswapExchangePriceFunction(
|
||||
address uniswapExchangeAddress,
|
||||
bytes4 functionSelector,
|
||||
uint256 inputAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount)
|
||||
{
|
||||
if (uniswapExchangeAddress == address(0)) {
|
||||
return 0;
|
||||
}
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
inputAmount
|
||||
));
|
||||
if (didSucceed) {
|
||||
outputAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Samples a supported sell source, defined by its address.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
@@ -373,16 +525,16 @@ contract ERC20BridgeSampler is
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
if (source == address(_getEth2DaiContract())) {
|
||||
if (source == _getEth2DaiAddress()) {
|
||||
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
|
||||
}
|
||||
if (source == address(_getUniswapExchangeFactoryContract())) {
|
||||
if (source == _getUniswapExchangeFactoryAddress()) {
|
||||
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
|
||||
}
|
||||
if (source == address(_getKyberNetworkContract())) {
|
||||
if (source == _getKyberNetworkProxyAddress()) {
|
||||
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
|
||||
}
|
||||
revert("UNSUPPORTED_SOURCE");
|
||||
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
|
||||
}
|
||||
|
||||
/// @dev Samples a supported buy source, defined by its address.
|
||||
@@ -401,13 +553,13 @@ contract ERC20BridgeSampler is
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
if (source == address(_getEth2DaiContract())) {
|
||||
if (source == _getEth2DaiAddress()) {
|
||||
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
|
||||
}
|
||||
if (source == address(_getUniswapExchangeFactoryContract())) {
|
||||
if (source == _getUniswapExchangeFactoryAddress()) {
|
||||
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
|
||||
}
|
||||
revert("UNSUPPORTED_SOURCE");
|
||||
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
|
||||
}
|
||||
|
||||
/// @dev Retrive an existing Uniswap exchange contract.
|
||||
@@ -420,9 +572,9 @@ contract ERC20BridgeSampler is
|
||||
returns (IUniswapExchangeQuotes exchange)
|
||||
{
|
||||
exchange = IUniswapExchangeQuotes(
|
||||
address(_getUniswapExchangeFactoryContract().getExchange(tokenAddress))
|
||||
address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress())
|
||||
.getExchange(tokenAddress))
|
||||
);
|
||||
require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE");
|
||||
}
|
||||
|
||||
/// @dev Extract the token address from ERC20 proxy asset data.
|
||||
@@ -433,19 +585,19 @@ contract ERC20BridgeSampler is
|
||||
pure
|
||||
returns (address tokenAddress)
|
||||
{
|
||||
require(assetData.length == 36, "INVALID_ASSET_DATA");
|
||||
require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA");
|
||||
bytes4 selector;
|
||||
assembly {
|
||||
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
|
||||
tokenAddress := mload(add(assetData, 0x24))
|
||||
}
|
||||
require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY");
|
||||
require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY");
|
||||
}
|
||||
|
||||
function _assertValidPair(address makerToken, address takerToken)
|
||||
private
|
||||
pure
|
||||
{
|
||||
require(makerToken != takerToken, "INVALID_TOKEN_PAIR");
|
||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
||||
}
|
||||
}
|
||||
|
45
contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
Normal file
45
contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IDevUtils {
|
||||
|
||||
/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
|
||||
/// @param order The order structure.
|
||||
/// @param signature Signature provided by maker that proves the order's authenticity.
|
||||
/// `0x01` can always be provided if the signature does not need to be validated.
|
||||
/// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order),
|
||||
/// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state),
|
||||
/// and isValidSignature (validity of the provided signature).
|
||||
/// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled"
|
||||
/// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final
|
||||
/// amount of each asset that can be filled.
|
||||
function getOrderRelevantState(LibOrder.Order calldata order, bytes calldata signature)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
);
|
||||
}
|
@@ -19,59 +19,82 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IERC20BridgeSampler {
|
||||
|
||||
/// @dev Query native orders and sample sell orders on multiple DEXes at once.
|
||||
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
||||
/// @param takerTokenAmounts Taker sell amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||
/// each taker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleSells(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address[] calldata sources,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableTakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
);
|
||||
|
||||
/// @dev Query native orders and sample buy orders on multiple DEXes at once.
|
||||
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
||||
/// @param makerTokenAmounts Maker sell amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||
/// each maker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleBuys(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address[] calldata sources,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableMakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
);
|
||||
|
||||
/// @dev Queries the status of several native orders.
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @return orderInfos Order info for each respective order.
|
||||
function queryOrders(LibOrder.Order[] calldata orders)
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableTakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (LibOrder.OrderInfo[] memory orderInfos);
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts);
|
||||
|
||||
/// @dev Queries the fillable maker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableMakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts);
|
||||
|
||||
/// @dev Sample sell quotes on multiple DEXes at once.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
|
@@ -23,6 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "../src/ERC20BridgeSampler.sol";
|
||||
import "../src/IEth2Dai.sol";
|
||||
import "../src/IDevUtils.sol";
|
||||
import "../src/IKyberNetwork.sol";
|
||||
|
||||
|
||||
@@ -90,9 +91,28 @@ library LibDeterministicQuotes {
|
||||
}
|
||||
|
||||
|
||||
contract FailTrigger {
|
||||
|
||||
// Give this address a balance to force operations to fail.
|
||||
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
|
||||
|
||||
// Funds `FAILURE_ADDRESS`.
|
||||
function enableFailTrigger() external payable {
|
||||
FAILURE_ADDRESS.transfer(msg.value);
|
||||
}
|
||||
|
||||
function _revertIfShouldFail() internal view {
|
||||
if (FAILURE_ADDRESS.balance != 0) {
|
||||
revert("FAIL_TRIGGERED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerUniswapExchange is
|
||||
IUniswapExchangeQuotes,
|
||||
DeploymentConstants
|
||||
DeploymentConstants,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
|
||||
|
||||
@@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 tokensBought)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
salt,
|
||||
tokenAddress,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
ethSold
|
||||
);
|
||||
}
|
||||
@@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 ethSold)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
salt,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokenAddress,
|
||||
tokensBought
|
||||
);
|
||||
@@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 ethBought)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
salt,
|
||||
tokenAddress,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokensSold
|
||||
);
|
||||
}
|
||||
@@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 tokensSold)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
salt,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokenAddress,
|
||||
ethBought
|
||||
);
|
||||
@@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
|
||||
contract TestERC20BridgeSamplerKyberNetwork is
|
||||
IKyberNetwork,
|
||||
DeploymentConstants
|
||||
DeploymentConstants,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
|
||||
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
@@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
||||
view
|
||||
returns (uint256 expectedRate, uint256)
|
||||
{
|
||||
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken;
|
||||
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken;
|
||||
_revertIfShouldFail();
|
||||
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
||||
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
||||
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
||||
SALT,
|
||||
fromToken,
|
||||
@@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerEth2Dai is
|
||||
IEth2Dai
|
||||
IEth2Dai,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
|
||||
|
||||
@@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
SALT,
|
||||
payToken,
|
||||
@@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
||||
view
|
||||
returns (uint256 payAmount)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
SALT,
|
||||
payToken,
|
||||
@@ -269,12 +298,15 @@ contract TestERC20BridgeSamplerUniswapExchangeFactory is
|
||||
|
||||
|
||||
contract TestERC20BridgeSampler is
|
||||
ERC20BridgeSampler
|
||||
ERC20BridgeSampler,
|
||||
FailTrigger
|
||||
{
|
||||
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
|
||||
TestERC20BridgeSamplerEth2Dai public eth2Dai;
|
||||
TestERC20BridgeSamplerKyberNetwork public kyber;
|
||||
|
||||
uint8 private constant MAX_ORDER_STATUS = uint8(LibOrder.OrderStatus.CANCELLED) + 1;
|
||||
|
||||
constructor() public {
|
||||
uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory();
|
||||
eth2Dai = new TestERC20BridgeSamplerEth2Dai();
|
||||
@@ -288,18 +320,29 @@ contract TestERC20BridgeSampler is
|
||||
uniswap.createTokenExchanges(tokenAddresses);
|
||||
}
|
||||
|
||||
// `IExchange.getOrderInfo()`, overridden to return deterministic order infos.
|
||||
function getOrderInfo(LibOrder.Order memory order)
|
||||
// `IDevUtils.getOrderRelevantState()`, overridden to return deterministic
|
||||
// states.
|
||||
function getOrderRelevantState(
|
||||
LibOrder.Order memory order,
|
||||
bytes memory
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (LibOrder.OrderInfo memory orderInfo)
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
)
|
||||
{
|
||||
// The order hash is just the hash of the salt.
|
||||
bytes32 orderHash = keccak256(abi.encode(order.salt));
|
||||
// Everything else is derived from the hash.
|
||||
orderInfo.orderHash = orderHash;
|
||||
orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1));
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus(uint256(orderHash) % MAX_ORDER_STATUS);
|
||||
orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount;
|
||||
fillableTakerAssetAmount =
|
||||
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;
|
||||
isValidSignature = uint256(orderHash) % 2 == 1;
|
||||
}
|
||||
|
||||
// Overriden to return deterministic decimals.
|
||||
@@ -312,38 +355,38 @@ contract TestERC20BridgeSampler is
|
||||
}
|
||||
|
||||
// Overriden to point to a this contract.
|
||||
function _getExchangeContract()
|
||||
function _getDevUtilsAddress()
|
||||
internal
|
||||
view
|
||||
returns (IExchange zeroex)
|
||||
returns (address devUtilAddress)
|
||||
{
|
||||
return IExchange(address(this));
|
||||
return address(this);
|
||||
}
|
||||
|
||||
// Overriden to point to a custom contract.
|
||||
function _getEth2DaiContract()
|
||||
function _getEth2DaiAddress()
|
||||
internal
|
||||
view
|
||||
returns (IEth2Dai eth2dai_)
|
||||
returns (address eth2daiAddress)
|
||||
{
|
||||
return eth2Dai;
|
||||
return address(eth2Dai);
|
||||
}
|
||||
|
||||
// Overriden to point to a custom contract.
|
||||
function _getUniswapExchangeFactoryContract()
|
||||
function _getUniswapExchangeFactoryAddress()
|
||||
internal
|
||||
view
|
||||
returns (IUniswapExchangeFactory uniswap_)
|
||||
returns (address uniswapAddress)
|
||||
{
|
||||
return uniswap;
|
||||
return address(uniswap);
|
||||
}
|
||||
|
||||
// Overriden to point to a custom contract.
|
||||
function _getKyberNetworkContract()
|
||||
function _getKyberNetworkProxyAddress()
|
||||
internal
|
||||
view
|
||||
returns (IKyberNetwork kyber_)
|
||||
returns (address kyberAddress)
|
||||
{
|
||||
return kyber;
|
||||
return address(kyber);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20-bridge-sampler",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
|
||||
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -50,18 +50,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-exchange": "^3.0.1",
|
||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-exchange": "^3.0.3",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -79,10 +79,10 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"ethereum-types": "^3.0.0",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
|
@@ -5,16 +5,16 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json';
|
||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
|
||||
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||
export const artifacts = {
|
||||
DeploymentConstants: DeploymentConstants as ContractArtifact,
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
IDevUtils: IDevUtils as ContractArtifact,
|
||||
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
getRandomPortion,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { Order, OrderInfo } from '@0x/types';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const INVALID_ASSET_DATA = hexUtils.random(37);
|
||||
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
|
||||
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
|
||||
const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS';
|
||||
const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY';
|
||||
const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA';
|
||||
const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE';
|
||||
const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE';
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR';
|
||||
const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
|
||||
const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
|
||||
const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
|
||||
const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
@@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
return quotes;
|
||||
}
|
||||
|
||||
function getDeterministicOrderInfo(order: Order): OrderInfo {
|
||||
const hash = getPackedHash(hexUtils.leftPad(order.salt, 32));
|
||||
return {
|
||||
orderHash: hash,
|
||||
orderStatus: new BigNumber(hash).mod(255).toNumber(),
|
||||
orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount),
|
||||
};
|
||||
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
|
||||
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
|
||||
const orderStatus = new BigNumber(hash).mod(255).toNumber();
|
||||
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
|
||||
if (orderStatus !== 3 || !isValidSignature) {
|
||||
return constants.ZERO_AMOUNT;
|
||||
}
|
||||
return order.takerAssetAmount.minus(new BigNumber(hash).mod(order.takerAssetAmount));
|
||||
}
|
||||
|
||||
function getDeterministicFillableMakerAssetAmount(order: Order): BigNumber {
|
||||
const takerAmount = getDeterministicFillableTakerAssetAmount(order);
|
||||
return order.makerAssetAmount
|
||||
.times(takerAmount)
|
||||
.div(order.takerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
}
|
||||
|
||||
function getERC20AssetData(tokenAddress: string): string {
|
||||
@@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
|
||||
}
|
||||
|
||||
describe('queryOrders()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
async function enableFailTriggerAsync(): Promise<void> {
|
||||
await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
|
||||
}
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
describe('getOrderFillableTakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expected = orders.map(getDeterministicOrderInfo);
|
||||
const actual = await testContract.queryOrders(orders).callAsync();
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('returns empty for no orders', async () => {
|
||||
const actual = await testContract.queryOrders([]).callAsync();
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync();
|
||||
expect(actual).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with an empty signature', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrderFillableMakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const expected = orders.map(getDeterministicFillableMakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('returns empty for no orders', async () => {
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts([], []).callAsync();
|
||||
expect(actual).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with an empty signature', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryOrdersAndSampleSells()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
it('returns expected fillable amounts for each order', async () => {
|
||||
const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
||||
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
|
||||
const [orderInfos] = await testContract
|
||||
.queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.callAsync();
|
||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
||||
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('can return quotes for all sources', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
|
||||
const [, quotes] = await testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
sampleAmounts,
|
||||
)
|
||||
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no orders are passed in', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
||||
.queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||
});
|
||||
@@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with an unsupported source', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('queryOrdersAndSampleBuys()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
it('returns expected fillable amounts for each order', async () => {
|
||||
const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
||||
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
|
||||
const [orderInfos] = await testContract
|
||||
.queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.callAsync();
|
||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
||||
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('can return quotes for all sources', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
|
||||
const [, quotes] = await testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
sampleAmounts,
|
||||
)
|
||||
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no orders are passed in', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
||||
.queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||
});
|
||||
@@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with an unsupported source', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const sources = [...BUY_SOURCES, 'Kyber'];
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
sources.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('sampleSells()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('sampleBuys()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromKyberNetwork()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token - token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
||||
@@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
@@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromEth2Dai()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleBuysFromEth2Dai()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleBuysFromEth2Dai()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromUniswap()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromUniswap()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
@@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
@@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the taker token', async () => {
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleBuysFromUniswap()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleBuysFromUniswap()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
@@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
@@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the taker token', async () => {
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,8 +3,8 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/deployment_constants';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/i_dev_utils';
|
||||
export * from '../test/generated-wrappers/i_erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_network';
|
||||
|
@@ -5,8 +5,8 @@
|
||||
"files": [
|
||||
"generated-artifacts/ERC20BridgeSampler.json",
|
||||
"generated-artifacts/IERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/DeploymentConstants.json",
|
||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/IDevUtils.json",
|
||||
"test/generated-artifacts/IERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberNetwork.json",
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "3.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -51,18 +51,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -82,7 +82,7 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1"
|
||||
"@0x/base-contract": "^6.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -3,6 +3,9 @@ export {
|
||||
DummyMultipleReturnERC20TokenContract,
|
||||
DummyNoReturnERC20TokenContract,
|
||||
WETH9Contract,
|
||||
WETH9Events,
|
||||
WETH9DepositEventArgs,
|
||||
WETH9TransferEventArgs,
|
||||
ZRXTokenContract,
|
||||
DummyERC20TokenTransferEventArgs,
|
||||
ERC20TokenEventArgs,
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "3.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc721",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,18 +52,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -84,7 +84,7 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1"
|
||||
"@0x/base-contract": "^6.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "4.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "4.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "4.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange-forwarder",
|
||||
"version": "4.0.1",
|
||||
"version": "4.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,24 +52,24 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-erc721": "^3.0.1",
|
||||
"@0x/contracts-exchange": "^3.0.1",
|
||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-erc721": "^3.0.3",
|
||||
"@0x/contracts-exchange": "^3.0.3",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -89,8 +89,8 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"ethereum-types": "^3.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "4.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "4.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "4.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -29,9 +29,11 @@ contract LibEIP712ExchangeDomain {
|
||||
// EIP712 Exchange Domain Version value
|
||||
string constant internal _EIP712_EXCHANGE_DOMAIN_VERSION = "3.0.0";
|
||||
|
||||
// Hash of the EIP712 Domain Separator data
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
// solhint-disable var-name-mixedcase
|
||||
/// @dev Hash of the EIP712 Domain Separator data
|
||||
/// @return 0 Domain hash.
|
||||
bytes32 public EIP712_EXCHANGE_DOMAIN_HASH;
|
||||
// solhint-enable var-name-mixedcase
|
||||
|
||||
/// @param chainId Chain ID of the network this contract is deployed on.
|
||||
/// @param verifyingContractAddressIfExists Address of the verifying contract (null if the address of this contract)
|
||||
|
@@ -60,6 +60,7 @@ library LibOrder {
|
||||
}
|
||||
|
||||
// solhint-disable max-line-length
|
||||
/// @dev Canonical order structure.
|
||||
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.
|
||||
@@ -78,8 +79,9 @@ library LibOrder {
|
||||
}
|
||||
// solhint-enable max-line-length
|
||||
|
||||
/// @dev Order information returned by `getOrderInfo()`.
|
||||
struct OrderInfo {
|
||||
uint8 orderStatus; // Status that describes order's validity and fillability.
|
||||
OrderStatus 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.
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange-libs",
|
||||
"version": "4.0.1",
|
||||
"version": "4.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,15 +52,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/libs/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/subproviders": "^6.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/subproviders": "^6.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -81,12 +81,12 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"ethereum-types": "^3.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -103,7 +103,7 @@ export function calculateFillResults(
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
);
|
||||
const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
|
||||
const makerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee);
|
||||
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
|
||||
return {
|
||||
makerAssetFilledAmount,
|
||||
@@ -113,3 +113,30 @@ export function calculateFillResults(
|
||||
protocolFeePaid: safeMul(protocolFeeMultiplier, gasPrice),
|
||||
};
|
||||
}
|
||||
|
||||
export const LibFractions = {
|
||||
add: (n1: BigNumber, d1: BigNumber, n2: BigNumber, d2: BigNumber): [BigNumber, BigNumber] => {
|
||||
if (n1.isZero()) {
|
||||
return [n2, d2];
|
||||
}
|
||||
if (n2.isZero()) {
|
||||
return [n1, d1];
|
||||
}
|
||||
const numerator = safeAdd(safeMul(n1, d2), safeMul(n2, d1));
|
||||
const denominator = safeMul(d1, d2);
|
||||
return [numerator, denominator];
|
||||
},
|
||||
normalize: (
|
||||
numerator: BigNumber,
|
||||
denominator: BigNumber,
|
||||
maxValue: BigNumber = new BigNumber(2).exponentiatedBy(127),
|
||||
): [BigNumber, BigNumber] => {
|
||||
if (numerator.isGreaterThan(maxValue) || denominator.isGreaterThan(maxValue)) {
|
||||
let rescaleBase = numerator.isGreaterThanOrEqualTo(denominator) ? numerator : denominator;
|
||||
rescaleBase = safeDiv(rescaleBase, maxValue);
|
||||
return [safeDiv(numerator, rescaleBase), safeDiv(denominator, rescaleBase)];
|
||||
} else {
|
||||
return [numerator, denominator];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "3.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.3 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -29,6 +29,7 @@ import "./MixinTransferSimulator.sol";
|
||||
// MixinAssetProxyDispatcher, MixinExchangeCore, MixinSignatureValidator,
|
||||
// and MixinTransactions are all inherited via the other Mixins that are
|
||||
// used.
|
||||
/// @dev The 0x Exchange contract.
|
||||
contract Exchange is
|
||||
LibEIP712ExchangeDomain,
|
||||
MixinMatchOrders,
|
||||
|
@@ -62,11 +62,11 @@ contract MixinAssetProxyDispatcher is
|
||||
|
||||
/// @dev Gets an asset proxy.
|
||||
/// @param assetProxyId Id of the asset proxy.
|
||||
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
||||
/// @return assetProxy The asset proxy address registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
||||
function getAssetProxy(bytes4 assetProxyId)
|
||||
external
|
||||
view
|
||||
returns (address)
|
||||
returns (address assetProxy)
|
||||
{
|
||||
return _assetProxies[assetProxyId];
|
||||
}
|
||||
|
@@ -45,14 +45,21 @@ contract MixinExchangeCore is
|
||||
using LibSafeMath for uint256;
|
||||
using LibBytes for bytes;
|
||||
|
||||
// Mapping of orderHash => amount of takerAsset already bought by maker
|
||||
/// @dev Mapping of orderHash => amount of takerAsset already bought by maker
|
||||
/// @param 0 Order hash.
|
||||
/// @return 0 The amount of taker asset filled.
|
||||
mapping (bytes32 => uint256) public filled;
|
||||
|
||||
// Mapping of orderHash => cancelled
|
||||
/// @dev Mapping of orderHash => cancelled
|
||||
/// @param 0 Order hash.
|
||||
/// @return 0 Whether the order was cancelled.
|
||||
mapping (bytes32 => bool) public cancelled;
|
||||
|
||||
// Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable
|
||||
// Orders with specified senderAddress and with a salt less than their epoch are considered cancelled
|
||||
/// @dev Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable
|
||||
/// Orders with specified senderAddress and with a salt less than their epoch are considered cancelled
|
||||
/// @param 0 Address of the order's maker.
|
||||
/// @param 1 Address of the order's sender.
|
||||
/// @return 0 Minimum valid order epoch.
|
||||
mapping (address => mapping (address => uint256)) public orderEpoch;
|
||||
|
||||
/// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch
|
||||
@@ -94,7 +101,7 @@ contract MixinExchangeCore is
|
||||
/// @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.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function fillOrder(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -125,7 +132,7 @@ contract MixinExchangeCore is
|
||||
|
||||
/// @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.
|
||||
/// @return orderInfo Information about the order and its state.
|
||||
/// See LibOrder.OrderInfo for a complete description.
|
||||
function getOrderInfo(LibOrder.Order memory order)
|
||||
public
|
||||
@@ -140,7 +147,7 @@ contract MixinExchangeCore is
|
||||
// edge cases in the supporting infrastructure because they have
|
||||
// an 'infinite' price when computed by a simple division.
|
||||
if (order.makerAssetAmount == 0) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.INVALID_MAKER_ASSET_AMOUNT);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.INVALID_MAKER_ASSET_AMOUNT;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
@@ -149,35 +156,35 @@ contract MixinExchangeCore is
|
||||
// Instead of distinguishing between unfilled and filled zero taker
|
||||
// amount orders, we choose not to support them.
|
||||
if (order.takerAssetAmount == 0) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.INVALID_TAKER_ASSET_AMOUNT);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.INVALID_TAKER_ASSET_AMOUNT;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Validate order availability
|
||||
if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.FULLY_FILLED);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.FULLY_FILLED;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Validate order expiration
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
if (block.timestamp >= order.expirationTimeSeconds) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.EXPIRED);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.EXPIRED;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Check if order has been cancelled
|
||||
if (cancelled[orderInfo.orderHash]) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.CANCELLED);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.CANCELLED;
|
||||
return orderInfo;
|
||||
}
|
||||
if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) {
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.CANCELLED);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.CANCELLED;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// All other statuses are ruled out: order is Fillable
|
||||
orderInfo.orderStatus = uint8(LibOrder.OrderStatus.FILLABLE);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.FILLABLE;
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
@@ -185,7 +192,7 @@ contract MixinExchangeCore is
|
||||
/// @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.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function _fillOrder(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -255,7 +262,7 @@ contract MixinExchangeCore is
|
||||
_assertValidCancel(order, orderInfo);
|
||||
|
||||
// Noop if order is already unfillable
|
||||
if (orderInfo.orderStatus != uint8(LibOrder.OrderStatus.FILLABLE)) {
|
||||
if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,7 +344,7 @@ contract MixinExchangeCore is
|
||||
view
|
||||
{
|
||||
// An order can only be filled if its status is FILLABLE.
|
||||
if (orderInfo.orderStatus != uint8(LibOrder.OrderStatus.FILLABLE)) {
|
||||
if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.OrderStatusError(
|
||||
orderInfo.orderHash,
|
||||
LibOrder.OrderStatus(orderInfo.orderStatus)
|
||||
|
@@ -29,10 +29,12 @@ contract MixinProtocolFees is
|
||||
IProtocolFees,
|
||||
Ownable
|
||||
{
|
||||
// The protocol fee multiplier -- the owner can update this field.
|
||||
/// @dev The protocol fee multiplier -- the owner can update this field.
|
||||
/// @return 0 Gas multplier.
|
||||
uint256 public protocolFeeMultiplier;
|
||||
|
||||
// The address of the registered protocolFeeCollector contract -- the owner can update this field.
|
||||
/// @dev The address of the registered protocolFeeCollector contract -- the owner can update this field.
|
||||
/// @return 0 Contract to forward protocol fees to.
|
||||
address public protocolFeeCollector;
|
||||
|
||||
/// @dev Allows the owner to update the protocol fee multiplier.
|
||||
|
@@ -47,10 +47,16 @@ contract MixinSignatureValidator is
|
||||
// bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)"))
|
||||
bytes4 private constant LEGACY_WALLET_MAGIC_VALUE = 0xb0671381;
|
||||
|
||||
// Mapping of hash => signer => signed
|
||||
/// @dev Mapping of hash => signer => signed
|
||||
/// @param 0 Order hash.
|
||||
/// @param 1 Signer address.
|
||||
/// @return 0 Whether the hash is presigned.
|
||||
mapping (bytes32 => mapping (address => bool)) public preSigned;
|
||||
|
||||
// Mapping of signer => validator => approved
|
||||
/// @dev Mapping of signer => validator => approved
|
||||
/// @param 0 Signer address.
|
||||
/// @param 1 Signature validator address.
|
||||
/// @return 0 Whether the validator is allowed to validate on behalf of the signer.
|
||||
mapping (address => mapping (address => bool)) public allowedValidators;
|
||||
|
||||
/// @dev Approves a hash on-chain.
|
||||
|
@@ -36,11 +36,14 @@ contract MixinTransactions is
|
||||
{
|
||||
using LibZeroExTransaction for LibZeroExTransaction.ZeroExTransaction;
|
||||
|
||||
// Mapping of transaction hash => executed
|
||||
// This prevents transactions from being executed more than once.
|
||||
/// @dev Mapping of transaction hash => executed
|
||||
/// This prevents transactions from being executed more than once.
|
||||
/// @param 0 The transaction hash.
|
||||
/// @return 0 Whether the transation was executed.
|
||||
mapping (bytes32 => bool) public transactionsExecuted;
|
||||
|
||||
// Address of current transaction signer
|
||||
/// @dev Address of current transaction signer.
|
||||
/// @return 0 The address associated with the the current transaction.
|
||||
address public currentContextAddress;
|
||||
|
||||
/// @dev Executes an Exchange method call in the context of signer.
|
||||
@@ -62,7 +65,7 @@ contract MixinTransactions is
|
||||
/// @dev Executes a batch of Exchange method calls in the context of signer(s).
|
||||
/// @param transactions Array of 0x transaction structures.
|
||||
/// @param signatures Array of proofs that transactions have been signed by signer(s).
|
||||
/// @return Array containing ABI encoded return data for each of the underlying Exchange function calls.
|
||||
/// @return returnData Array containing ABI encoded return data for each of the underlying Exchange function calls.
|
||||
function batchExecuteTransactions(
|
||||
LibZeroExTransaction.ZeroExTransaction[] memory transactions,
|
||||
bytes[] memory signatures
|
||||
@@ -70,10 +73,10 @@ contract MixinTransactions is
|
||||
public
|
||||
payable
|
||||
disableRefundUntilEnd
|
||||
returns (bytes[] memory)
|
||||
returns (bytes[] memory returnData)
|
||||
{
|
||||
uint256 length = transactions.length;
|
||||
bytes[] memory returnData = new bytes[](length);
|
||||
returnData = new bytes[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
returnData[i] = _executeTransaction(transactions[i], signatures[i]);
|
||||
}
|
||||
@@ -117,7 +120,7 @@ contract MixinTransactions is
|
||||
_setCurrentContextAddressIfRequired(signerAddress, address(0));
|
||||
|
||||
emit TransactionExecution(transactionHash);
|
||||
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
@@ -36,10 +36,11 @@ contract MixinWrapperFunctions is
|
||||
{
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
|
||||
/// @dev Fills the input order. Reverts if exact `takerAssetFillAmount` not filled.
|
||||
/// @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 fillResults Amounts filled and fees paid.
|
||||
function fillOrKillOrder(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -62,7 +63,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
/// @return Array of amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Array of amounts filled and fees paid by makers and taker.
|
||||
function batchFillOrders(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
@@ -89,7 +90,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
/// @return Array of amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Array of amounts filled and fees paid by makers and taker.
|
||||
function batchFillOrKillOrders(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
@@ -116,7 +117,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
/// @return Array of amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Array of amounts filled and fees paid by makers and taker.
|
||||
function batchFillOrdersNoThrow(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
@@ -145,7 +146,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Amounts filled and fees paid by makers and taker.
|
||||
function marketSellOrdersNoThrow(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -186,7 +187,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param makerAssetFillAmount Desired amount of makerAsset to buy.
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Amounts filled and fees paid by makers and taker.
|
||||
function marketBuyOrdersNoThrow(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 makerAssetFillAmount,
|
||||
@@ -234,7 +235,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param takerAssetFillAmount Minimum amount of takerAsset to sell.
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Amounts filled and fees paid by makers and taker.
|
||||
function marketSellOrdersFillOrKill(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -259,7 +260,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param orders Array of order specifications.
|
||||
/// @param makerAssetFillAmount Minimum amount of makerAsset to buy.
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
/// @return fillResults Amounts filled and fees paid by makers and taker.
|
||||
function marketBuyOrdersFillOrKill(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 makerAssetFillAmount,
|
||||
@@ -295,7 +296,7 @@ contract MixinWrapperFunctions is
|
||||
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
|
||||
/// @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.
|
||||
/// @param fillResults ignature Proof that order has been created by maker.
|
||||
function _fillOrKillOrder(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
@@ -324,7 +325,7 @@ contract MixinWrapperFunctions is
|
||||
/// @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.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function _fillOrderNoThrow(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
|
@@ -30,7 +30,7 @@ import "../src/Exchange.sol";
|
||||
contract TestWrapperFunctions is
|
||||
Exchange
|
||||
{
|
||||
uint8 internal constant MAX_ORDER_STATUS = uint8(LibOrder.OrderStatus.CANCELLED);
|
||||
LibOrder.OrderStatus internal constant MAX_ORDER_STATUS = LibOrder.OrderStatus.CANCELLED;
|
||||
uint256 internal constant ALWAYS_FAILING_SALT = uint256(-1);
|
||||
string internal constant ALWAYS_FAILING_SALT_REVERT_REASON = "ALWAYS_FAILING_SALT";
|
||||
|
||||
@@ -61,7 +61,7 @@ contract TestWrapperFunctions is
|
||||
// Lower uint128 of `order.salt` is the `orderTakerAssetFilledAmount`.
|
||||
orderInfo.orderTakerAssetFilledAmount = uint128(order.salt);
|
||||
// High byte of `order.salt` is the `orderStatus`.
|
||||
orderInfo.orderStatus = uint8(order.salt >> 248) % (MAX_ORDER_STATUS + 1);
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus(uint8(order.salt >> 248) % (uint8(MAX_ORDER_STATUS) + 1));
|
||||
orderInfo.orderHash = order.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-exchange",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,21 +52,21 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-multisig": "^4.0.1",
|
||||
"@0x/contracts-staking": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-multisig": "^4.0.3",
|
||||
"@0x/contracts-staking": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -88,13 +88,13 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-erc1155": "^2.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-erc721": "^3.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-erc1155": "^2.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-erc721": "^3.0.3",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -107,23 +107,6 @@ describe('Reference functions', () => {
|
||||
).to.throw(expectedError.message);
|
||||
});
|
||||
|
||||
it('reverts if `order.makerAssetAmount` is 0', () => {
|
||||
const order = makeOrder({
|
||||
makerAssetAmount: constants.ZERO_AMOUNT,
|
||||
takerAssetAmount: ONE_ETHER,
|
||||
});
|
||||
const takerAssetFilledAmount = ONE_ETHER;
|
||||
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
|
||||
return expect(() =>
|
||||
LibReferenceFunctions.calculateFillResults(
|
||||
order,
|
||||
takerAssetFilledAmount,
|
||||
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
|
||||
DEFAULT_GAS_PRICE,
|
||||
),
|
||||
).to.throw(expectedError.message);
|
||||
});
|
||||
|
||||
it('reverts if `order.takerAssetAmount` is 0', () => {
|
||||
const order = makeOrder({
|
||||
makerAssetAmount: ONE_ETHER,
|
||||
|
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "5.1.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "5.1.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "5.1.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v5.1.2 - _January 6, 2020_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.1.1 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.1.0 - _December 9, 2019_
|
||||
|
||||
* Export function `encodeDutchAuctionAssetData` (#2373)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-extensions",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.2",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,24 +52,24 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-erc721": "^3.0.1",
|
||||
"@0x/contracts-exchange": "^3.0.1",
|
||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-erc721": "^3.0.3",
|
||||
"@0x/contracts-exchange": "^3.0.3",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
@@ -91,8 +91,8 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"ethereum-types": "^3.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -1,4 +1,27 @@
|
||||
[
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Integration tests for DydxBridge with (i) Exchange v3 and (ii) Mainnet dYdX SoloMargin contract.",
|
||||
"pr": 2401
|
||||
},
|
||||
{
|
||||
"note": "Add aggregator mainnet tests.",
|
||||
"pr": 2407
|
||||
}
|
||||
],
|
||||
"timestamp": 1578272714
|
||||
},
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "2.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "2.0.1",
|
||||
|
@@ -5,6 +5,15 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.1.0 - _January 6, 2020_
|
||||
|
||||
* Integration tests for DydxBridge with (i) Exchange v3 and (ii) Mainnet dYdX SoloMargin contract. (#2401)
|
||||
* Add aggregator mainnet tests. (#2407)
|
||||
|
||||
## v2.0.2 - _December 17, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.1 - _December 9, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
207
contracts/integrations/contracts/test/TestDydxUser.sol
Normal file
207
contracts/integrations/contracts/test/TestDydxUser.sol
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/// @dev THIS IS A SELF-CONTAINED CONVENIENCE CONTRACT FOR TESTING THE DYDX BRIDGE ON MAINNET.
|
||||
/// Currently deployed at 0xeb58c2caa96f39626dcceb74fdbb7a9a8b54ec18.
|
||||
/// Steps:
|
||||
/// 1. Deploy to mainnet (can copy-paste this into Remix and deploy).
|
||||
/// 2. Send some DAI to the contract.
|
||||
/// 3. Call `init()` to configure.
|
||||
interface IERC20Token {
|
||||
|
||||
/// @dev `msg.sender` approves `_spender` to spend `_value` tokens
|
||||
/// @param spender The address of the account able to transfer the tokens
|
||||
/// @param value The amount of wei to be approved for transfer
|
||||
/// @return Always true if the call has enough gas to complete execution
|
||||
function approve(address spender, uint256 value)
|
||||
external
|
||||
returns (bool);
|
||||
|
||||
/// @dev Query the balance of owner
|
||||
/// @param owner The address from which the balance will be retrieved
|
||||
/// @return Balance of owner
|
||||
function balanceOf(address owner)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
}
|
||||
|
||||
|
||||
/// @dev TestDydxUser uses this interface to interact with dydx.
|
||||
interface IDydx {
|
||||
|
||||
/// @dev Respresents an operator's privileges.
|
||||
struct OperatorArg {
|
||||
address operator;
|
||||
bool trusted;
|
||||
}
|
||||
|
||||
/// @dev Represents the unique key that specifies an account
|
||||
struct AccountInfo {
|
||||
address owner; // The address that owns the account
|
||||
uint256 number; // A nonce that allows a single address to control many accounts
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
Deposit, // supply tokens
|
||||
Withdraw, // borrow tokens
|
||||
Transfer, // transfer balance between accounts
|
||||
Buy, // buy an amount of some token (externally)
|
||||
Sell, // sell an amount of some token (externally)
|
||||
Trade, // trade tokens against another account
|
||||
Liquidate, // liquidate an undercollateralized or expiring account
|
||||
Vaporize, // use excess tokens to zero-out a completely negative account
|
||||
Call // send arbitrary data to an address
|
||||
}
|
||||
|
||||
/// @dev Arguments that are passed to Solo in an ordered list as part of a single operation.
|
||||
/// Each ActionArgs has an actionType which specifies which action struct that this data will be
|
||||
/// parsed into before being processed.
|
||||
struct ActionArgs {
|
||||
ActionType actionType;
|
||||
uint256 accountId;
|
||||
AssetAmount amount;
|
||||
uint256 primaryMarketId;
|
||||
uint256 secondaryMarketId;
|
||||
address otherAddress;
|
||||
uint256 otherAccountId;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
enum AssetDenomination {
|
||||
Wei, // the amount is denominated in wei
|
||||
Par // the amount is denominated in par
|
||||
}
|
||||
|
||||
enum AssetReference {
|
||||
Delta, // the amount is given as a delta from the current value
|
||||
Target // the amount is given as an exact number to end up at
|
||||
}
|
||||
|
||||
struct AssetAmount {
|
||||
bool sign; // true if positive
|
||||
AssetDenomination denomination;
|
||||
AssetReference ref;
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
/// @dev The main entry-point to Solo that allows users and contracts to manage accounts.
|
||||
/// Take one or more actions on one or more accounts. The msg.sender must be the owner or
|
||||
/// operator of all accounts except for those being liquidated, vaporized, or traded with.
|
||||
/// One call to operate() is considered a singular "operation". Account collateralization is
|
||||
/// ensured only after the completion of the entire operation.
|
||||
/// @param accounts A list of all accounts that will be used in this operation. Cannot contain
|
||||
/// duplicates. In each action, the relevant account will be referred-to by its
|
||||
/// index in the list.
|
||||
/// @param actions An ordered list of all actions that will be taken in this operation. The
|
||||
/// actions will be processed in order.
|
||||
function operate(
|
||||
AccountInfo[] calldata accounts,
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Sets operators of dydx account.
|
||||
/// @param operators to give/remove access.
|
||||
function setOperators(OperatorArg[] calldata operators)
|
||||
external;
|
||||
}
|
||||
|
||||
|
||||
/// @dev Deploy this contract and call `init` to run the mainnet DydxBridge integration tests.
|
||||
contract TestDydxUser {
|
||||
|
||||
address public constant DYDX_BRIDGE_ADDRESS = 0x96DdBa19b69D6EA2549f6a12d005595167414744;
|
||||
address public constant DYDX_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e;
|
||||
address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
|
||||
uint256 public constant DYDX_DAI_MARKET_ID = 3;
|
||||
bytes4 public constant MAGIC_BYTES = bytes4(keccak256("init()"));
|
||||
|
||||
function init()
|
||||
public
|
||||
returns (bytes4)
|
||||
{
|
||||
// 1. Assert that this account has DAI.
|
||||
uint256 daiBalance = IERC20Token(DAI_ADDRESS).balanceOf(address(this));
|
||||
require(
|
||||
daiBalance > 0,
|
||||
"TestDydxUser/DAI_BALANCE_MUST_BE_NONZERO"
|
||||
);
|
||||
|
||||
// 2. Set allowance for Dydx to transfer DAI.
|
||||
require(
|
||||
IERC20Token(DAI_ADDRESS).approve(
|
||||
DYDX_ADDRESS,
|
||||
daiBalance
|
||||
),
|
||||
"TestDydxUser/FAILED_TO_SET_DAI_ALLOWANCE"
|
||||
);
|
||||
|
||||
// 3. Add DydxBridge as operator on dydx.
|
||||
// This will revert on failure.
|
||||
IDydx.OperatorArg[] memory operatorArgs = new IDydx.OperatorArg[](1);
|
||||
operatorArgs[0] = IDydx.OperatorArg({
|
||||
operator: DYDX_BRIDGE_ADDRESS,
|
||||
trusted: true
|
||||
});
|
||||
IDydx(DYDX_ADDRESS).setOperators(operatorArgs);
|
||||
|
||||
// 4. Deposit 1/2 DAI balance into dydx. This allows us to test withdrawals.
|
||||
// 4.i Create dydx account struct.
|
||||
IDydx.AccountInfo[] memory accounts = new IDydx.AccountInfo[](1);
|
||||
accounts[0] = IDydx.AccountInfo({
|
||||
owner: address(this),
|
||||
number: 0
|
||||
});
|
||||
|
||||
// 4.ii Create dydx amount.
|
||||
IDydx.AssetAmount memory dydxAmount = IDydx.AssetAmount({
|
||||
sign: true, // true if positive.
|
||||
denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account.
|
||||
ref: IDydx.AssetReference.Delta, // Delta => a relative amount.
|
||||
value: daiBalance / 2 // amount to deposit.
|
||||
});
|
||||
|
||||
// 4.iii Create dydx deposit action.
|
||||
IDydx.ActionArgs[] memory actions = new IDydx.ActionArgs[](1);
|
||||
actions[0] = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Deposit, // deposit tokens.
|
||||
amount: dydxAmount, // amount to deposit.
|
||||
accountId: 0, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: DYDX_DAI_MARKET_ID, // indicates which token to deposit.
|
||||
otherAddress: address(this), // deposit from the account owner.
|
||||
// unused parameters
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
data: hex''
|
||||
});
|
||||
|
||||
// 4.iv Deposit DAI into dydx. This will revert on failure.
|
||||
IDydx(DYDX_ADDRESS).operate(
|
||||
accounts,
|
||||
actions
|
||||
);
|
||||
|
||||
// Return magic bytes on success.
|
||||
return MAGIC_BYTES;
|
||||
}
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
|
||||
|
||||
/// @dev A forwarder contract for filling 0x asset-swapper aggregated orders.
|
||||
/// The forwarder is necessary to purchase taker assets and set up
|
||||
/// approvals in one transaction. Only call the functions on this contract
|
||||
/// in an `eth_call` context or you will lose money!
|
||||
contract TestMainnetAggregatorFills is
|
||||
DeploymentConstants
|
||||
{
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
address constant internal EXCHANGE_ADDRESS = 0x61935CbDd02287B511119DDb11Aeb42F1593b7Ef;
|
||||
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
|
||||
|
||||
struct SimulatedMarketFillResults {
|
||||
uint256 makerAssetBalanceBefore;
|
||||
uint256 takerAssetBalanceBefore;
|
||||
uint256 makerAssetBalanceAfter;
|
||||
uint256 takerAssetBalanceAfter;
|
||||
LibFillResults.FillResults fillResults;
|
||||
}
|
||||
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
function() external payable {}
|
||||
|
||||
/// @dev Buy taker assets with ETH from `takerOrders` and then perform a
|
||||
/// market buy on `makerOrders`.
|
||||
function marketBuy(
|
||||
address makerTokenAddress,
|
||||
address takerTokenAddress,
|
||||
LibOrder.Order[] memory makerOrders,
|
||||
LibOrder.Order[] memory takerOrders,
|
||||
bytes[] memory makerOrderSignatures,
|
||||
bytes[] memory takerOrderSignatures,
|
||||
uint256 makerAssetBuyAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
returns (SimulatedMarketFillResults memory results)
|
||||
{
|
||||
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
results.fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||
.marketBuyOrdersNoThrow
|
||||
.value(address(this).balance)(
|
||||
makerOrders,
|
||||
makerAssetBuyAmount,
|
||||
makerOrderSignatures
|
||||
);
|
||||
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
}
|
||||
|
||||
/// @dev Buy taker assets with ETH from `takerOrders` and then perform a
|
||||
/// market sell on `makerOrders`.
|
||||
function marketSell(
|
||||
address makerTokenAddress,
|
||||
address takerTokenAddress,
|
||||
LibOrder.Order[] memory makerOrders,
|
||||
LibOrder.Order[] memory takerOrders,
|
||||
bytes[] memory makerOrderSignatures,
|
||||
bytes[] memory takerOrderSignatures,
|
||||
uint256 takerAssetSellAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
returns (SimulatedMarketFillResults memory results)
|
||||
{
|
||||
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
results.fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||
.marketSellOrdersNoThrow
|
||||
.value(address(this).balance)(
|
||||
makerOrders,
|
||||
takerAssetSellAmount,
|
||||
makerOrderSignatures
|
||||
);
|
||||
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
}
|
||||
|
||||
/// @dev Like `marketSell`, but calls `fillOrder()` individually to detect
|
||||
/// errors.
|
||||
function fillOrders(
|
||||
address makerTokenAddress,
|
||||
address takerTokenAddress,
|
||||
LibOrder.Order[] memory makerOrders,
|
||||
LibOrder.Order[] memory takerOrders,
|
||||
bytes[] memory makerOrderSignatures,
|
||||
bytes[] memory takerOrderSignatures,
|
||||
uint256 takerAssetSellAmount
|
||||
)
|
||||
public
|
||||
payable
|
||||
returns (SimulatedMarketFillResults memory results)
|
||||
{
|
||||
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
for (uint256 i = 0; i < makerOrders.length; i++) {
|
||||
if (takerAssetSellAmount == 0) {
|
||||
break;
|
||||
}
|
||||
LibFillResults.FillResults memory fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||
.fillOrder
|
||||
.value(address(this).balance)(
|
||||
makerOrders[i],
|
||||
takerAssetSellAmount,
|
||||
makerOrderSignatures[i]
|
||||
);
|
||||
results.fillResults = LibFillResults.addFillResults(results.fillResults, fillResults);
|
||||
takerAssetSellAmount = takerAssetSellAmount.safeSub(fillResults.takerAssetFilledAmount);
|
||||
}
|
||||
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||
}
|
||||
|
||||
function _approveAssetProxy(address tokenAddress) private {
|
||||
address assetProxyAddress = IExchange(EXCHANGE_ADDRESS).getAssetProxy(ERC20_PROXY_ID);
|
||||
LibERC20Token.approve(tokenAddress, assetProxyAddress, uint256(-1));
|
||||
}
|
||||
|
||||
/// @dev Buys as much of `takerOrders` as possible with the ETH transferred
|
||||
/// to this contract, leaving enough ETH behind for protocol fees.
|
||||
function _prepareFunds(
|
||||
address takerTokenAddress,
|
||||
LibOrder.Order[] memory makerOrders,
|
||||
LibOrder.Order[] memory takerOrders,
|
||||
bytes[] memory takerOrderSignatures
|
||||
)
|
||||
private
|
||||
{
|
||||
_approveAssetProxy(_getWethAddress());
|
||||
uint256 protocolFee = IExchange(EXCHANGE_ADDRESS).protocolFeeMultiplier() * tx.gasprice;
|
||||
uint256 maxProtocolFees = protocolFee * (takerOrders.length + makerOrders.length);
|
||||
uint256 ethSellAmount = msg.value.safeSub(maxProtocolFees);
|
||||
IEtherToken(_getWethAddress()).deposit.value(ethSellAmount)();
|
||||
if (takerTokenAddress != _getWethAddress()) {
|
||||
IExchange(EXCHANGE_ADDRESS)
|
||||
.marketSellOrdersNoThrow
|
||||
.value(maxProtocolFees)(
|
||||
takerOrders,
|
||||
ethSellAmount,
|
||||
takerOrderSignatures
|
||||
);
|
||||
_approveAssetProxy(takerTokenAddress);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-integrations",
|
||||
"version": "2.0.1",
|
||||
"version": "2.1.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -18,7 +18,8 @@
|
||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 1000000 --bail --exit",
|
||||
"test:fuzz": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/fuzz_tests/*.js' --timeout 0 --bail --exit",
|
||||
"compile": "sol-compiler",
|
||||
"watch": "sol-compiler -w",
|
||||
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
|
||||
@@ -37,7 +38,7 @@
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "TestFramework",
|
||||
"abis": "./test/generated-artifacts/@(TestEth2Dai|TestEth2DaiBridge|TestFramework|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
@@ -50,25 +51,27 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.0.1",
|
||||
"@0x/contract-addresses": "^4.0.0",
|
||||
"@0x/contracts-coordinator": "^3.0.1",
|
||||
"@0x/contracts-dev-utils": "^1.0.1",
|
||||
"@0x/contracts-exchange-forwarder": "^4.0.1",
|
||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
||||
"@0x/contracts-gen": "^2.0.1",
|
||||
"@0x/contracts-utils": "^4.0.1",
|
||||
"@0x/coordinator-server": "^1.0.4",
|
||||
"@0x/dev-utils": "^3.0.1",
|
||||
"@0x/migrations": "^5.0.1",
|
||||
"@0x/order-utils": "^10.0.0",
|
||||
"@0x/sol-compiler": "^4.0.1",
|
||||
"@0x/abi-gen": "^5.0.3",
|
||||
"@0x/contract-addresses": "^4.2.0",
|
||||
"@0x/contract-wrappers": "^13.3.0",
|
||||
"@0x/contracts-coordinator": "^3.0.3",
|
||||
"@0x/contracts-dev-utils": "^1.0.3",
|
||||
"@0x/contracts-exchange-forwarder": "^4.0.3",
|
||||
"@0x/contracts-exchange-libs": "^4.0.3",
|
||||
"@0x/contracts-gen": "^2.0.3",
|
||||
"@0x/contracts-utils": "^4.0.3",
|
||||
"@0x/coordinator-server": "^1.0.5",
|
||||
"@0x/dev-utils": "^3.1.0",
|
||||
"@0x/migrations": "^5.1.0",
|
||||
"@0x/order-utils": "^10.1.0",
|
||||
"@0x/sol-compiler": "^4.0.3",
|
||||
"@0x/tslint-config": "^4.0.0",
|
||||
"@0x/web3-wrapper": "^7.0.1",
|
||||
"@0x/web3-wrapper": "^7.0.3",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.0",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "*",
|
||||
"@types/seedrandom": "^2.4.28",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^3.0.0",
|
||||
@@ -78,6 +81,7 @@
|
||||
"mocha": "^6.2.0",
|
||||
"nock": "^10.0.6",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"seedrandom": "^3.0.5",
|
||||
"shx": "^0.2.2",
|
||||
"solhint": "^1.4.1",
|
||||
"truffle": "^5.0.32",
|
||||
@@ -85,19 +89,21 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.0.1",
|
||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
||||
"@0x/contracts-erc1155": "^2.0.1",
|
||||
"@0x/contracts-erc20": "^3.0.1",
|
||||
"@0x/contracts-erc721": "^3.0.1",
|
||||
"@0x/contracts-exchange": "^3.0.1",
|
||||
"@0x/contracts-multisig": "^4.0.1",
|
||||
"@0x/contracts-staking": "^2.0.1",
|
||||
"@0x/contracts-test-utils": "^5.0.0",
|
||||
"@0x/types": "^3.1.0",
|
||||
"@0x/typescript-typings": "^5.0.0",
|
||||
"@0x/utils": "^5.1.0",
|
||||
"@0x/asset-swapper": "^3.0.3",
|
||||
"@0x/base-contract": "^6.0.3",
|
||||
"@0x/contracts-asset-proxy": "^3.1.0",
|
||||
"@0x/contracts-erc1155": "^2.0.3",
|
||||
"@0x/contracts-erc20": "^3.0.3",
|
||||
"@0x/contracts-erc721": "^3.0.3",
|
||||
"@0x/contracts-exchange": "^3.0.3",
|
||||
"@0x/contracts-multisig": "^4.0.3",
|
||||
"@0x/contracts-staking": "^2.0.3",
|
||||
"@0x/contracts-test-utils": "^5.1.0",
|
||||
"@0x/types": "^3.1.1",
|
||||
"@0x/typescript-typings": "^5.0.1",
|
||||
"@0x/utils": "^5.1.2",
|
||||
"ethereum-types": "^3.0.0",
|
||||
"ethereumjs-util": "^6.2.0",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
239
contracts/integrations/test/aggregation/fill_test.ts
Normal file
239
contracts/integrations/test/aggregation/fill_test.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { MarketBuySwapQuote, MarketSellSwapQuote, Orderbook, SwapQuoter } from '@0x/asset-swapper';
|
||||
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { FillResults, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TestMainnetAggregatorFillsContract } from '../wrappers';
|
||||
|
||||
import { tokens } from './tokens';
|
||||
|
||||
blockchainTests.live('Aggregator Mainnet Tests', env => {
|
||||
// Mainnet address of the `TestMainnetAggregatorFills` contract.
|
||||
const TEST_CONTRACT_ADDRESS = '0x37Ca306F42748b7fe105F89FCBb2CD03D27c8146';
|
||||
const TAKER_ADDRESS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; // Vitalik
|
||||
const ORDERBOOK_POLLING_MS = 1000;
|
||||
const GAS_PRICE = new BigNumber(1);
|
||||
const TAKER_ASSET_ETH_VALUE = 500e18;
|
||||
const MIN_BALANCE = 500.1e18;
|
||||
const SYMBOLS = ['ETH', 'DAI', 'USDC', 'FOAM'];
|
||||
const TEST_PAIRS = _.flatten(SYMBOLS.map(m => SYMBOLS.filter(t => t !== m).map(t => [m, t])));
|
||||
const FILL_VALUES = [1, 10, 1e2, 1e3, 1e4, 2.5e4, 5e4];
|
||||
|
||||
let testContract: TestMainnetAggregatorFillsContract;
|
||||
let swapQuoter: SwapQuoter;
|
||||
let takerEthBalance: BigNumber;
|
||||
const orderbooks: { [name: string]: Orderbook } = {};
|
||||
|
||||
async function getTakerOrdersAsync(takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||
if (takerAssetSymbol === 'ETH') {
|
||||
return [];
|
||||
}
|
||||
return getOrdersAsync(takerAssetSymbol, 'ETH');
|
||||
}
|
||||
|
||||
// Fetches ETH -> taker asset orders for the forwarder contract.
|
||||
async function getOrdersAsync(makerAssetSymbol: string, takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||
const takerTokenAddress = tokens[takerAssetSymbol].address;
|
||||
const makerTokenAddress = tokens[makerAssetSymbol].address;
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||
const orders = _.flatten(
|
||||
await Promise.all(
|
||||
Object.keys(orderbooks).map(async name =>
|
||||
getOrdersFromOrderBookAsync(name, makerAssetData, takerAssetData),
|
||||
),
|
||||
),
|
||||
);
|
||||
const uniqueOrders: SignedOrder[] = [];
|
||||
for (const order of orders) {
|
||||
if (!order.makerFee.eq(0) || !order.takerFee.eq(0)) {
|
||||
continue;
|
||||
}
|
||||
if (uniqueOrders.findIndex(o => isSameOrder(order, o)) === -1) {
|
||||
uniqueOrders.push(order);
|
||||
}
|
||||
}
|
||||
return uniqueOrders;
|
||||
}
|
||||
|
||||
async function getOrdersFromOrderBookAsync(
|
||||
name: string,
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
): Promise<SignedOrder[]> {
|
||||
try {
|
||||
return (await orderbooks[name].getOrdersAsync(makerAssetData, takerAssetData)).map(r => r.order);
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to retrieve orders from orderbook "${name}".`);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function isSameOrder(a: SignedOrder, b: SignedOrder): boolean {
|
||||
for (const [k, v] of Object.entries(a)) {
|
||||
if (k in (b as any)) {
|
||||
if (BigNumber.isBigNumber(v) && !v.eq((b as any)[k])) {
|
||||
return false;
|
||||
}
|
||||
if (v !== (b as any)[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function toTokenUnits(symbol: string, weis: Numberish): BigNumber {
|
||||
return new BigNumber(weis).div(new BigNumber(10).pow(tokens[symbol].decimals));
|
||||
}
|
||||
|
||||
function fromTokenUnits(symbol: string, units: Numberish): BigNumber {
|
||||
return new BigNumber(units)
|
||||
.times(new BigNumber(10).pow(tokens[symbol].decimals))
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
}
|
||||
|
||||
interface MarketOperationResult {
|
||||
makerAssetBalanceBefore: BigNumber;
|
||||
takerAssetBalanceBefore: BigNumber;
|
||||
makerAssetBalanceAfter: BigNumber;
|
||||
takerAssetBalanceAfter: BigNumber;
|
||||
fillResults: FillResults;
|
||||
}
|
||||
|
||||
// Liquidity is low right now so it's possible we didn't have
|
||||
// enough taker assets to cover the orders, so occasionally we'll get incomplete
|
||||
// fills. This function will catch those cases.
|
||||
// TODO(dorothy-zbornak): Remove this special case when liquidity is up.
|
||||
function checkHadEnoughTakerAsset(
|
||||
quote: MarketBuySwapQuote | MarketSellSwapQuote,
|
||||
result: MarketOperationResult,
|
||||
): boolean {
|
||||
if (result.takerAssetBalanceBefore.gte(quote.worstCaseQuoteInfo.takerAssetAmount)) {
|
||||
return true;
|
||||
}
|
||||
const takerAssetPct = result.takerAssetBalanceBefore
|
||||
.div(quote.worstCaseQuoteInfo.takerAssetAmount)
|
||||
.times(100)
|
||||
.toNumber()
|
||||
.toFixed(1);
|
||||
logUtils.warn(`Could not acquire enough taker asset to complete the fill: ${takerAssetPct}%`);
|
||||
expect(result.fillResults.makerAssetFilledAmount).to.bignumber.lt(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||
return false;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
testContract = new TestMainnetAggregatorFillsContract(TEST_CONTRACT_ADDRESS, env.provider, {
|
||||
...env.txDefaults,
|
||||
gasPrice: GAS_PRICE,
|
||||
gas: 10e6,
|
||||
});
|
||||
swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(env.provider, 'https://api.0x.org/sra');
|
||||
// Pool orderbooks because we're desperate for liquidity.
|
||||
orderbooks.swapQuoter = swapQuoter.orderbook;
|
||||
orderbooks.bamboo = Orderbook.getOrderbookForPollingProvider({
|
||||
httpEndpoint: 'https://sra.bamboorelay.com/0x/v3',
|
||||
pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||
});
|
||||
// TODO(dorothy-zbornak): Uncomment when radar's SRA is up.
|
||||
// orderbooks.radar = Orderbook.getOrderbookForPollingProvider({
|
||||
// httpEndpoint: 'https://api-v3.radarrelay.com/v3',
|
||||
// pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||
// });
|
||||
takerEthBalance = await env.web3Wrapper.getBalanceInWeiAsync(TAKER_ADDRESS);
|
||||
});
|
||||
|
||||
it('taker has minimum ETH', async () => {
|
||||
expect(takerEthBalance).to.bignumber.gte(MIN_BALANCE);
|
||||
});
|
||||
|
||||
describe('market sells', () => {
|
||||
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||
for (const fillValue of FILL_VALUES) {
|
||||
const fillAmount = fromTokenUnits(takerSymbol, new BigNumber(fillValue).div(tokens[takerSymbol].price));
|
||||
it(`sell ${toTokenUnits(takerSymbol, fillAmount)} ${takerSymbol} for ${makerSymbol}`, async () => {
|
||||
const [quote, takerOrders] = await Promise.all([
|
||||
swapQuoter.getMarketSellSwapQuoteAsync(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
fillAmount,
|
||||
{ gasPrice: GAS_PRICE },
|
||||
),
|
||||
getTakerOrdersAsync(takerSymbol),
|
||||
]);
|
||||
// Buy taker assets from `takerOrders` and and perform a
|
||||
// market sell on the bridge orders.
|
||||
const fill = await testContract
|
||||
.marketSell(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
quote.orders,
|
||||
takerOrders,
|
||||
quote.orders.map(o => o.signature),
|
||||
takerOrders.map(o => o.signature),
|
||||
quote.takerAssetFillAmount,
|
||||
)
|
||||
.callAsync({
|
||||
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||
from: TAKER_ADDRESS,
|
||||
gasPrice: quote.gasPrice,
|
||||
});
|
||||
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||
quote.worstCaseQuoteInfo.makerAssetAmount,
|
||||
);
|
||||
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||
quote.takerAssetFillAmount,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('market buys', () => {
|
||||
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||
for (const fillValue of FILL_VALUES) {
|
||||
const fillAmount = fromTokenUnits(makerSymbol, new BigNumber(fillValue).div(tokens[makerSymbol].price));
|
||||
it(`buy ${toTokenUnits(makerSymbol, fillAmount)} ${makerSymbol} with ${takerSymbol}`, async () => {
|
||||
const [quote, takerOrders] = await Promise.all([
|
||||
swapQuoter.getMarketBuySwapQuoteAsync(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
fillAmount,
|
||||
{ gasPrice: GAS_PRICE },
|
||||
),
|
||||
getTakerOrdersAsync(takerSymbol),
|
||||
]);
|
||||
// Buy taker assets from `takerOrders` and and perform a
|
||||
// market buy on the bridge orders.
|
||||
const fill = await testContract
|
||||
.marketBuy(
|
||||
tokens[makerSymbol].address,
|
||||
tokens[takerSymbol].address,
|
||||
quote.orders,
|
||||
takerOrders,
|
||||
quote.orders.map(o => o.signature),
|
||||
takerOrders.map(o => o.signature),
|
||||
quote.makerAssetFillAmount,
|
||||
)
|
||||
.callAsync({
|
||||
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||
from: TAKER_ADDRESS,
|
||||
gasPrice: quote.gasPrice,
|
||||
});
|
||||
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||
quote.worstCaseQuoteInfo.takerAssetAmount,
|
||||
);
|
||||
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||
quote.makerAssetFillAmount,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
77
contracts/integrations/test/aggregation/tokens.ts
Normal file
77
contracts/integrations/test/aggregation/tokens.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export const tokens: { [symbol: string]: { address: string; decimals: number; price: number } } = {
|
||||
ETH: {
|
||||
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
decimals: 18,
|
||||
price: 133,
|
||||
},
|
||||
SAI: {
|
||||
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
|
||||
decimals: 18,
|
||||
price: 1,
|
||||
},
|
||||
DAI: {
|
||||
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||
decimals: 18,
|
||||
price: 1,
|
||||
},
|
||||
USDC: {
|
||||
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
decimals: 6,
|
||||
price: 1,
|
||||
},
|
||||
WBTC: {
|
||||
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||
decimals: 8,
|
||||
price: 6900,
|
||||
},
|
||||
MKR: {
|
||||
address: '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||
decimals: 18,
|
||||
price: 454,
|
||||
},
|
||||
BAT: {
|
||||
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
|
||||
decimals: 18,
|
||||
price: 0.17,
|
||||
},
|
||||
OMG: {
|
||||
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
|
||||
decimals: 18,
|
||||
price: 0.65,
|
||||
},
|
||||
ZRX: {
|
||||
address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
|
||||
decimals: 18,
|
||||
price: 0.19,
|
||||
},
|
||||
ZIL: {
|
||||
address: '0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27',
|
||||
decimals: 12,
|
||||
price: 0.004,
|
||||
},
|
||||
FOAM: {
|
||||
address: '0x4946Fcea7C692606e8908002e55A582af44AC121',
|
||||
decimals: 18,
|
||||
price: 0.004,
|
||||
},
|
||||
USDT: {
|
||||
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
decimals: 6,
|
||||
price: 0.019,
|
||||
},
|
||||
REP: {
|
||||
address: '0x1985365e9f78359a9B6AD760e32412f4a445E862',
|
||||
decimals: 18,
|
||||
price: 8.9,
|
||||
},
|
||||
MANA: {
|
||||
address: '0x0F5D2fB29fb7d3CFeE444a200298f468908cC942',
|
||||
decimals: 18,
|
||||
price: 0.025,
|
||||
},
|
||||
LINK: {
|
||||
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
||||
decimals: 18,
|
||||
price: 1.8,
|
||||
},
|
||||
};
|
@@ -5,16 +5,20 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as TestDydxUser from '../test/generated-artifacts/TestDydxUser.json';
|
||||
import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json';
|
||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||
import * as TestFramework from '../test/generated-artifacts/TestFramework.json';
|
||||
import * as TestMainnetAggregatorFills from '../test/generated-artifacts/TestMainnetAggregatorFills.json';
|
||||
import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json';
|
||||
import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json';
|
||||
import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json';
|
||||
export const artifacts = {
|
||||
TestDydxUser: TestDydxUser as ContractArtifact,
|
||||
TestEth2Dai: TestEth2Dai as ContractArtifact,
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
TestFramework: TestFramework as ContractArtifact,
|
||||
TestMainnetAggregatorFills: TestMainnetAggregatorFills as ContractArtifact,
|
||||
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||
TestUniswapExchange: TestUniswapExchange as ContractArtifact,
|
||||
TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact,
|
||||
|
1002
contracts/integrations/test/bridges/abi/dydxEvents.ts
Normal file
1002
contracts/integrations/test/bridges/abi/dydxEvents.ts
Normal file
File diff suppressed because it is too large
Load Diff
22
contracts/integrations/test/bridges/deploy_dydx_bridge.ts
Normal file
22
contracts/integrations/test/bridges/deploy_dydx_bridge.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { artifacts as assetProxyArtifacts, TestDydxBridgeContract } from '@0x/contracts-asset-proxy';
|
||||
import { BlockchainTestsEnvironment } from '@0x/contracts-test-utils';
|
||||
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
|
||||
/**
|
||||
* Deploys test DydxBridge contract configured to work alongside the provided `deployment`.
|
||||
*/
|
||||
export async function deployDydxBridgeAsync(
|
||||
deployment: DeploymentManager,
|
||||
environment: BlockchainTestsEnvironment,
|
||||
): Promise<TestDydxBridgeContract> {
|
||||
const tokenHolders = deployment.accounts;
|
||||
const dydxBridge = await TestDydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.TestDydxBridge,
|
||||
environment.provider,
|
||||
deployment.txDefaults,
|
||||
assetProxyArtifacts,
|
||||
tokenHolders,
|
||||
);
|
||||
return dydxBridge;
|
||||
}
|
159
contracts/integrations/test/bridges/dydx_bridge_mainnet_test.ts
Normal file
159
contracts/integrations/test/bridges/dydx_bridge_mainnet_test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
artifacts as assetProxyArtifacts,
|
||||
DydxBridgeActionType,
|
||||
DydxBridgeContract,
|
||||
DydxBridgeData,
|
||||
dydxBridgeDataEncoder,
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, constants, describe, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { contractAddresses, dydxAccountOwner } from '../mainnet_fork_utils';
|
||||
|
||||
import { dydxEvents } from './abi/dydxEvents';
|
||||
|
||||
blockchainTests.fork.resets('Mainnet dydx bridge tests', env => {
|
||||
let testContract: DydxBridgeContract;
|
||||
// random account to receive tokens from dydx
|
||||
const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308';
|
||||
const defaultAccountNumber = new BigNumber(0);
|
||||
const daiMarketId = new BigNumber(3);
|
||||
const defaultAmount = toBaseUnitAmount(0.01);
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId: daiMarketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId: daiMarketId,
|
||||
// This ratio must be less than the `1` to account
|
||||
// for interest in dydx balances because the test
|
||||
// account has an initial dydx balance of zero.
|
||||
conversionRateNumerator: new BigNumber(1),
|
||||
conversionRateDenominator: new BigNumber(2),
|
||||
};
|
||||
before(async () => {
|
||||
testContract = new DydxBridgeContract(contractAddresses.dydxBridge, env.provider, env.txDefaults, {
|
||||
DydxBridge: assetProxyArtifacts.DydxBridge.compilerOutput.abi,
|
||||
ERC20: erc20Artifacts.ERC20Token.compilerOutput.abi,
|
||||
Dydx: dydxEvents.abi,
|
||||
});
|
||||
});
|
||||
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
const callAndVerifyDydxEvents = async (bridgeData: DydxBridgeData): Promise<void> => {
|
||||
const txReceipt = await testContract
|
||||
.bridgeTransferFrom(
|
||||
constants.NULL_ADDRESS,
|
||||
dydxAccountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
dydxBridgeDataEncoder.encode({ bridgeData }),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: contractAddresses.erc20BridgeProxy, gasPrice: 0 });
|
||||
|
||||
// Construct expected events
|
||||
const expectedDepositEvents = [];
|
||||
const expectedWithdrawEvents = [];
|
||||
for (const action of bridgeData.actions) {
|
||||
const scaledAmount = action.conversionRateDenominator.gt(0)
|
||||
? defaultAmount
|
||||
.times(action.conversionRateNumerator)
|
||||
.dividedToIntegerBy(action.conversionRateDenominator)
|
||||
: defaultAmount;
|
||||
switch (action.actionType) {
|
||||
case DydxBridgeActionType.Deposit:
|
||||
expectedDepositEvents.push({
|
||||
accountOwner: dydxAccountOwner,
|
||||
accountNumber: bridgeData.accountNumbers[action.accountId.toNumber()],
|
||||
market: action.marketId,
|
||||
update: [[true, scaledAmount]],
|
||||
from: dydxAccountOwner,
|
||||
});
|
||||
break;
|
||||
|
||||
case DydxBridgeActionType.Withdraw:
|
||||
expectedWithdrawEvents.push({
|
||||
accountOwner: dydxAccountOwner,
|
||||
accountNumber: bridgeData.accountNumbers[action.accountId.toNumber()],
|
||||
market: action.marketId,
|
||||
update: [[false, scaledAmount]],
|
||||
to: receiver,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unrecognized Action: ${action.actionType}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify events
|
||||
let nextExpectedDepositEventIdx = 0;
|
||||
let nextExpectedWithdrawEventIdx = 0;
|
||||
for (const rawLog of txReceipt.logs) {
|
||||
// tslint:disable-next-line no-unnecessary-type-assertion
|
||||
const log = rawLog as LogWithDecodedArgs<DecodedLogArgs>;
|
||||
if (log.event !== 'LogDeposit' && log.event !== 'LogWithdraw') {
|
||||
continue;
|
||||
}
|
||||
const expectedEvent =
|
||||
log.event === 'LogDeposit'
|
||||
? expectedDepositEvents[nextExpectedDepositEventIdx++]
|
||||
: expectedWithdrawEvents[nextExpectedWithdrawEventIdx++];
|
||||
expect(log.args.accountOwner, 'accountOwner').to.equal(expectedEvent.accountOwner);
|
||||
expect(log.args.accountNumber, 'accountNumber').to.bignumber.equal(expectedEvent.accountNumber);
|
||||
expect(log.args.market, 'market').to.bignumber.equal(expectedEvent.market);
|
||||
expect(log.args.from, 'from').to.equal(expectedEvent.from);
|
||||
// We only check the first update field because it's the delta balance (amount deposited).
|
||||
// The next field is the new total, which depends on interest rates at the time of execution.
|
||||
expect(log.args.update[0][0], 'update sign').to.equal(expectedEvent.update[0][0]);
|
||||
const updateValueHex = log.args.update[0][1]._hex;
|
||||
const updateValueBn = new BigNumber(updateValueHex, 16);
|
||||
expect(updateValueBn, 'update value').to.bignumber.equal(expectedEvent.update[0][1]);
|
||||
}
|
||||
};
|
||||
|
||||
it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
});
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultDepositAction],
|
||||
});
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and a single account', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction],
|
||||
});
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction],
|
||||
});
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
});
|
||||
});
|
||||
it('succeeds when calling `operate` with multiple actions under a single account', async () => {
|
||||
await callAndVerifyDydxEvents({
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -19,7 +19,7 @@ const coordinatorEndpoint = 'http://localhost:';
|
||||
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
blockchainTests.skip('Coordinator Client', env => {
|
||||
blockchainTests('Coordinator Client', env => {
|
||||
const takerTokenFillAmount = new BigNumber(0);
|
||||
const chainId = 1337;
|
||||
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
||||
|
@@ -33,7 +33,7 @@ import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/wrapper_interfaces';
|
||||
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/utils/wrapper_interfaces';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('Deployment and Configuration End to End Tests', env => {
|
||||
|
54
contracts/integrations/test/dev-utils/get_order_hash.ts
Normal file
54
contracts/integrations/test/dev-utils/get_order_hash.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { artifacts, DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, orderHashUtils } from '@0x/contracts-test-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
blockchainTests('DevUtils.getOrderHash', env => {
|
||||
let devUtils: DevUtilsContract;
|
||||
let exchange: ExchangeContract;
|
||||
let chainId: number;
|
||||
|
||||
before(async () => {
|
||||
chainId = await env.getChainIdAsync();
|
||||
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
exchangeArtifacts,
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
devUtils = await DevUtilsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DevUtils,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
exchange.address,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the order hash', async () => {
|
||||
const order: Order = {
|
||||
makerAddress: constants.NULL_ADDRESS,
|
||||
takerAddress: constants.NULL_ADDRESS,
|
||||
senderAddress: constants.NULL_ADDRESS,
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
makerAssetData: constants.NULL_ADDRESS,
|
||||
takerAssetData: constants.NULL_ADDRESS,
|
||||
makerFeeAssetData: constants.NULL_ADDRESS,
|
||||
takerFeeAssetData: constants.NULL_ADDRESS,
|
||||
salt: new BigNumber(0),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
makerAssetAmount: new BigNumber(0),
|
||||
takerAssetAmount: new BigNumber(0),
|
||||
expirationTimeSeconds: new BigNumber(0),
|
||||
exchangeAddress: exchange.address,
|
||||
chainId,
|
||||
};
|
||||
expect(await devUtils.getOrderHash(order, new BigNumber(chainId), exchange.address).callAsync()).to.be.equal(
|
||||
orderHashUtils.getOrderHashHex(order),
|
||||
);
|
||||
});
|
||||
});
|
@@ -1,6 +1,5 @@
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
import * as crypto from 'crypto';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import {
|
||||
artifacts as proxyArtifacts,
|
||||
@@ -19,19 +18,12 @@ import {
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
|
||||
import { chaiSetup, constants, LogDecoder, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber, providerUtils, StringRevertError, LibBytesRevertErrors } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import { BigNumber, hexUtils, LibBytesRevertErrors, StringRevertError } from '@0x/utils';
|
||||
|
||||
import { artifacts, LibAssetDataContract } from '@0x/contracts-dev-utils';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
const KNOWN_ERC20_ENCODING = {
|
||||
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
@@ -69,7 +61,8 @@ const KNOWN_STATIC_CALL_ENCODING = {
|
||||
'0xc339d10a0000000000000000000000006dfff22588be9b3ef8cf0ad6dc9b84796f9fb45f0000000000000000000000000000000000000000000000000000000000000060b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000000000000000000000000000000000000000000000000000024ed2cfc9c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000',
|
||||
};
|
||||
|
||||
describe('LibAssetData', () => {
|
||||
// TODO(jalextowle): This file could really be cleaned up by using the DeploymentManager tool.
|
||||
blockchainTests.resets('LibAssetData', env => {
|
||||
let exchange: ExchangeContract;
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
let erc721Proxy: ERC721ProxyContract;
|
||||
@@ -93,44 +86,43 @@ describe('LibAssetData', () => {
|
||||
let erc1155TokenId: BigNumber;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
const chainId = await providerUtils.getChainIdAsync(provider);
|
||||
const chainId = await env.getChainIdAsync();
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
|
||||
erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC20Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC721Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
erc1155Proxy = await ERC1155ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC1155Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.MultiAssetProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
staticCallProxy = await StaticCallProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.StaticCallProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
|
||||
@@ -142,25 +134,25 @@ describe('LibAssetData', () => {
|
||||
|
||||
libAssetData = await LibAssetDataContract.deployFrom0xArtifactAsync(
|
||||
artifacts.LibAssetData,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
exchange.address,
|
||||
);
|
||||
|
||||
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.TestStaticCallTarget,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
|
||||
[tokenOwnerAddress] = await web3Wrapper.getAvailableAddressesAsync();
|
||||
[tokenOwnerAddress] = await env.getAccountAddressesAsync();
|
||||
|
||||
erc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
'Dummy',
|
||||
'DUM',
|
||||
@@ -170,8 +162,8 @@ describe('LibAssetData', () => {
|
||||
|
||||
erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
|
||||
erc721Artifacts.DummyERC721Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
'Dummy',
|
||||
'DUM',
|
||||
@@ -187,12 +179,12 @@ describe('LibAssetData', () => {
|
||||
|
||||
erc1155Token = await ERC1155MintableContract.deployFrom0xArtifactAsync(
|
||||
erc1155Artifacts.ERC1155Mintable,
|
||||
provider,
|
||||
txDefaults,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
|
||||
const logDecoder = new LogDecoder(web3Wrapper, erc1155Artifacts);
|
||||
const logDecoder = new LogDecoder(env.web3Wrapper, erc1155Artifacts);
|
||||
const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync(
|
||||
await erc1155Token.create('uri:Dummy', /*isNonFungible:*/ false).sendTransactionAsync(),
|
||||
);
|
||||
@@ -204,17 +196,6 @@ describe('LibAssetData', () => {
|
||||
.awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
it('should have a deployed-to address', () => {
|
||||
expect(libAssetData.address.slice(0, 2)).to.equal('0x');
|
||||
});
|
||||
@@ -345,7 +326,7 @@ describe('LibAssetData', () => {
|
||||
});
|
||||
|
||||
it('should revert for invalid assetProxyId', async () => {
|
||||
const badAssetData = '0x' + crypto.randomBytes(4).toString('hex') + constants.NULL_ADDRESS;
|
||||
const badAssetData = `0x${crypto.randomBytes(4).toString('hex')}${constants.NULL_ADDRESS}`;
|
||||
await expect(libAssetData.revertIfInvalidAssetData(badAssetData).callAsync()).to.eventually.be.rejectedWith(
|
||||
StringRevertError,
|
||||
);
|
||||
@@ -441,8 +422,7 @@ describe('LibAssetData', () => {
|
||||
|
||||
it('should return a balance of MAX_UINT256 if the the StaticCallProxy assetData contains data for a successful staticcall', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber(new BigNumber(1)).getABIEncodedTransactionData();
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const expectedResultHash = hexUtils.hash(hexUtils.leftPad(1));
|
||||
const assetData = await libAssetData
|
||||
.encodeStaticCallAssetData(staticCallTarget.address, staticCallData, expectedResultHash)
|
||||
.callAsync();
|
||||
@@ -452,8 +432,7 @@ describe('LibAssetData', () => {
|
||||
|
||||
it('should return a balance of 0 if the the StaticCallProxy assetData contains data for an unsuccessful staticcall', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber(new BigNumber(0)).getABIEncodedTransactionData();
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const expectedResultHash = hexUtils.hash(hexUtils.leftPad(1));
|
||||
const assetData = await libAssetData
|
||||
.encodeStaticCallAssetData(staticCallTarget.address, staticCallData, expectedResultHash)
|
||||
.callAsync();
|
@@ -1,161 +1,92 @@
|
||||
import {
|
||||
artifacts as proxyArtifacts,
|
||||
ERC20ProxyContract,
|
||||
ERC20Wrapper,
|
||||
ERC721ProxyContract,
|
||||
ERC721Wrapper,
|
||||
MultiAssetProxyContract,
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
|
||||
import {
|
||||
chaiSetup,
|
||||
constants,
|
||||
orderHashUtils,
|
||||
OrderFactory,
|
||||
OrderStatus,
|
||||
provider,
|
||||
txDefaults,
|
||||
web3Wrapper,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { ExchangeContract } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, orderHashUtils, OrderStatus } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { OrderTransferResults, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { artifacts, DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { Maker } from '../framework/actors/maker';
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
let makerAddress: string;
|
||||
// TODO(jalextowle): This can be cleaned up by using the actors more.
|
||||
blockchainTests.resets('OrderValidationUtils/OrderTransferSimulatorUtils', env => {
|
||||
let takerAddress: string;
|
||||
let owner: string;
|
||||
let erc20AssetData: string;
|
||||
let erc20AssetData2: string;
|
||||
let erc721AssetData: string;
|
||||
let feeAssetData: string;
|
||||
|
||||
let maker: Maker;
|
||||
let devUtils: DevUtilsContract;
|
||||
let erc20Token: DummyERC20TokenContract;
|
||||
let erc20Token2: DummyERC20TokenContract;
|
||||
let feeErc20Token: DummyERC20TokenContract;
|
||||
let erc721Token: DummyERC721TokenContract;
|
||||
let exchange: ExchangeContract;
|
||||
let devUtils: DevUtilsContract;
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
let erc721Proxy: ERC721ProxyContract;
|
||||
let multiAssetProxy: MultiAssetProxyContract;
|
||||
let exchange: ExchangeContract;
|
||||
let deployment: DeploymentManager;
|
||||
|
||||
let erc20AssetData: string;
|
||||
let erc20AssetData2: string;
|
||||
let feeAssetData: string;
|
||||
let erc721AssetData: string;
|
||||
|
||||
let signedOrder: SignedOrder;
|
||||
let orderFactory: OrderFactory;
|
||||
|
||||
const tokenId = new BigNumber(123456789);
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
[takerAddress, owner] = await env.getAccountAddressesAsync();
|
||||
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress] = accounts.slice(0, 3));
|
||||
const chainId = await providerUtils.getChainIdAsync(provider);
|
||||
deployment = await DeploymentManager.deployAsync(env, {
|
||||
numErc20TokensToDeploy: 3,
|
||||
numErc721TokensToDeploy: 1,
|
||||
owner,
|
||||
});
|
||||
erc20Token = deployment.tokens.erc20[0];
|
||||
erc20Token2 = deployment.tokens.erc20[1];
|
||||
feeErc20Token = deployment.tokens.erc20[2];
|
||||
erc20Proxy = deployment.assetProxies.erc20Proxy;
|
||||
devUtils = deployment.devUtils;
|
||||
exchange = deployment.exchange;
|
||||
|
||||
const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20Token2.address);
|
||||
feeAssetData = assetDataUtils.encodeERC20AssetData(feeErc20Token.address);
|
||||
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20Token, erc20Token2, feeErc20Token] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
|
||||
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
erc721Proxy = await erc721Wrapper.deployProxyAsync();
|
||||
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
{},
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
|
||||
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.MultiAssetProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
|
||||
await exchange.registerAssetProxy(erc20Proxy.address).awaitTransactionSuccessAsync({ from: owner });
|
||||
await exchange.registerAssetProxy(erc721Proxy.address).awaitTransactionSuccessAsync({ from: owner });
|
||||
await exchange.registerAssetProxy(multiAssetProxy.address).awaitTransactionSuccessAsync({ from: owner });
|
||||
await erc20Proxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync({ from: owner });
|
||||
await erc721Proxy.addAuthorizedAddress(exchange.address).awaitTransactionSuccessAsync({ from: owner });
|
||||
|
||||
devUtils = await DevUtilsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DevUtils,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
exchange.address,
|
||||
);
|
||||
|
||||
feeAssetData = await devUtils.encodeERC20AssetData(feeErc20Token.address).callAsync();
|
||||
erc20AssetData = await devUtils.encodeERC20AssetData(erc20Token.address).callAsync();
|
||||
erc20AssetData2 = await devUtils.encodeERC20AssetData(erc20Token2.address).callAsync();
|
||||
erc721AssetData = await devUtils.encodeERC721AssetData(erc721Token.address, tokenId).callAsync();
|
||||
const defaultOrderParams = {
|
||||
...constants.STATIC_ORDER_PARAMS,
|
||||
makerAddress,
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
makerAssetData: erc20AssetData,
|
||||
takerAssetData: erc20AssetData2,
|
||||
makerFeeAssetData: feeAssetData,
|
||||
takerFeeAssetData: feeAssetData,
|
||||
exchangeAddress: exchange.address,
|
||||
chainId,
|
||||
};
|
||||
const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
maker = new Maker({
|
||||
name: 'Maker',
|
||||
deployment,
|
||||
orderConfig: {
|
||||
makerAssetData: erc20AssetData,
|
||||
takerAssetData: erc20AssetData2,
|
||||
makerFeeAssetData: feeAssetData,
|
||||
takerFeeAssetData: feeAssetData,
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
},
|
||||
});
|
||||
const [tokenID] = await maker.configureERC721TokenAsync(deployment.tokens.erc721[0]);
|
||||
erc721AssetData = assetDataUtils.encodeERC721AssetData(deployment.tokens.erc721[0].address, tokenID);
|
||||
});
|
||||
|
||||
describe('getTransferableAssetAmount', () => {
|
||||
it('should return the balance when balance < allowance', async () => {
|
||||
const balance = new BigNumber(123);
|
||||
const allowance = new BigNumber(456);
|
||||
await erc20Token.setBalance(makerAddress, balance).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, balance).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, allowance).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const transferableAmount = await devUtils
|
||||
.getTransferableAssetAmount(makerAddress, erc20AssetData)
|
||||
.getTransferableAssetAmount(maker.address, erc20AssetData)
|
||||
.callAsync();
|
||||
expect(transferableAmount).to.bignumber.equal(balance);
|
||||
});
|
||||
it('should return the allowance when allowance < balance', async () => {
|
||||
const balance = new BigNumber(456);
|
||||
const allowance = new BigNumber(123);
|
||||
await erc20Token.setBalance(makerAddress, balance).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, balance).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, allowance).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const transferableAmount = await devUtils
|
||||
.getTransferableAssetAmount(makerAddress, erc20AssetData)
|
||||
.getTransferableAssetAmount(maker.address, erc20AssetData)
|
||||
.callAsync();
|
||||
expect(transferableAmount).to.bignumber.equal(allowance);
|
||||
});
|
||||
@@ -165,23 +96,23 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
.callAsync();
|
||||
const transferableAmount1 = new BigNumber(10);
|
||||
const transferableAmount2 = new BigNumber(5);
|
||||
await erc20Token.setBalance(makerAddress, transferableAmount1).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, transferableAmount1).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, transferableAmount1).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await erc20Token2.setBalance(makerAddress, transferableAmount2).awaitTransactionSuccessAsync();
|
||||
await erc20Token2.setBalance(maker.address, transferableAmount2).awaitTransactionSuccessAsync();
|
||||
await erc20Token2.approve(erc20Proxy.address, transferableAmount2).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const transferableAmount = await devUtils
|
||||
.getTransferableAssetAmount(makerAddress, multiAssetData)
|
||||
.getTransferableAssetAmount(maker.address, multiAssetData)
|
||||
.callAsync();
|
||||
expect(transferableAmount).to.bignumber.equal(transferableAmount2);
|
||||
});
|
||||
});
|
||||
describe('getOrderRelevantState', () => {
|
||||
beforeEach(async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync();
|
||||
signedOrder = await maker.signOrderAsync({});
|
||||
});
|
||||
it('should return the correct orderInfo when the order is valid', async () => {
|
||||
const [orderInfo] = await devUtils.getOrderRelevantState(signedOrder, signedOrder.signature).callAsync();
|
||||
@@ -209,9 +140,9 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when fee balances/allowances are insufficient', async () => {
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -222,10 +153,44 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
const multiAssetData = await devUtils
|
||||
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc20AssetData, erc20AssetData2])
|
||||
.callAsync();
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
signedOrder = await maker.signOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
.callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in the maker side of a multi-asset proxy order', async () => {
|
||||
const multiAssetData = await devUtils
|
||||
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc721AssetData, erc721AssetData])
|
||||
.callAsync();
|
||||
signedOrder = await maker.signOrderAsync({
|
||||
makerAssetData: multiAssetData,
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetData: erc721AssetData,
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
makerFee: constants.ZERO_AMOUNT,
|
||||
takerFee: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
.callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in the taker side of a multi-asset proxy order', async () => {
|
||||
const multiAssetData = await devUtils
|
||||
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc721AssetData, erc721AssetData])
|
||||
.callAsync();
|
||||
signedOrder = await maker.signOrderAsync({
|
||||
makerAssetData: erc721AssetData,
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetData: multiAssetData,
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
makerFee: constants.ZERO_AMOUNT,
|
||||
takerFee: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -233,16 +198,16 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when fee balances/allowances are partially sufficient', async () => {
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const divisor = 4;
|
||||
await feeErc20Token
|
||||
.setBalance(makerAddress, signedOrder.makerFee.dividedToIntegerBy(divisor))
|
||||
.setBalance(maker.address, signedOrder.makerFee.dividedToIntegerBy(divisor))
|
||||
.awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -254,14 +219,14 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
it('should return the correct fillableTakerAssetAmount when non-fee balances/allowances are partially sufficient', async () => {
|
||||
const divisor = 4;
|
||||
await erc20Token
|
||||
.setBalance(makerAddress, signedOrder.makerAssetAmount.dividedToIntegerBy(divisor))
|
||||
.setBalance(maker.address, signedOrder.makerAssetAmount.dividedToIntegerBy(divisor))
|
||||
.awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -274,23 +239,23 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
const multiAssetData = await devUtils
|
||||
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc20AssetData, erc20AssetData2])
|
||||
.callAsync();
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
signedOrder = await maker.signOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const divisor = 4;
|
||||
await erc20Token2
|
||||
.setBalance(makerAddress, signedOrder.makerAssetAmount.dividedToIntegerBy(divisor))
|
||||
.setBalance(maker.address, signedOrder.makerAssetAmount.dividedToIntegerBy(divisor))
|
||||
.awaitTransactionSuccessAsync();
|
||||
await erc20Token2
|
||||
.approve(erc20Proxy.address, signedOrder.makerAssetAmount.dividedToIntegerBy(divisor))
|
||||
.awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -300,9 +265,9 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when non-fee balances/allowances are insufficient', async () => {
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -310,13 +275,13 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount equal to the takerAssetAmount when the order is unfilled and balances/allowances are sufficient', async () => {
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -324,16 +289,16 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when balances/allowances are partially sufficient and makerAsset=makerFeeAsset', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({
|
||||
signedOrder = await maker.signOrderAsync({
|
||||
makerAssetData: feeAssetData,
|
||||
makerAssetAmount: new BigNumber(10),
|
||||
takerAssetAmount: new BigNumber(20),
|
||||
makerFee: new BigNumber(40),
|
||||
});
|
||||
const transferableMakerAssetAmount = new BigNumber(10);
|
||||
await feeErc20Token.setBalance(makerAddress, transferableMakerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, transferableMakerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, transferableMakerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const expectedFillableTakerAssetAmount = transferableMakerAssetAmount
|
||||
.times(signedOrder.takerAssetAmount)
|
||||
@@ -344,10 +309,10 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(expectedFillableTakerAssetAmount);
|
||||
});
|
||||
it('should return the correct fillabeTakerassetAmount when makerAsset balances/allowances are sufficient and there are no maker fees', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerFee: constants.ZERO_AMOUNT });
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
signedOrder = await maker.signOrderAsync({ makerFee: constants.ZERO_AMOUNT });
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -355,13 +320,13 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount when the remaining takerAssetAmount is less than the transferable amount', async () => {
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await erc20Token2.setBalance(takerAddress, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
@@ -375,7 +340,7 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.dividedToIntegerBy(4);
|
||||
await exchange
|
||||
.fillOrder(signedOrder, takerAssetFillAmount, signedOrder.signature)
|
||||
.awaitTransactionSuccessAsync({ from: takerAddress });
|
||||
.awaitTransactionSuccessAsync({ from: takerAddress, value: DeploymentManager.protocolFee });
|
||||
const [, fillableTakerAssetAmount] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
.callAsync();
|
||||
@@ -384,15 +349,15 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
);
|
||||
});
|
||||
it('should return correct info even when there are no fees specified', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({
|
||||
signedOrder = await maker.signOrderAsync({
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
makerFeeAssetData: '0x',
|
||||
takerFeeAssetData: '0x',
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [orderInfo, fillableTakerAssetAmount, isValidSignature] = await devUtils
|
||||
.getOrderRelevantState(signedOrder, signedOrder.signature)
|
||||
@@ -406,18 +371,21 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
});
|
||||
describe('getOrderRelevantStates', async () => {
|
||||
it('should return the correct information for multiple orders', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync();
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
signedOrder = await maker.signOrderAsync();
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync();
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const signedOrder2 = await maker.signOrderAsync({
|
||||
makerAssetData: erc721AssetData,
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
});
|
||||
const signedOrder2 = await orderFactory.newSignedOrderAsync({ makerAssetData: erc721AssetData });
|
||||
const invalidSignature = '0x01';
|
||||
await exchange.cancelOrder(signedOrder2).awaitTransactionSuccessAsync({ from: makerAddress });
|
||||
await exchange.cancelOrder(signedOrder2).awaitTransactionSuccessAsync({ from: maker.address });
|
||||
const [ordersInfo, fillableTakerAssetAmounts, isValidSignature] = await devUtils
|
||||
.getOrderRelevantStates([signedOrder, signedOrder2], [signedOrder.signature, invalidSignature])
|
||||
.callAsync();
|
||||
@@ -435,7 +403,7 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
});
|
||||
describe('getSimulatedOrderTransferResults', () => {
|
||||
beforeEach(async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync();
|
||||
signedOrder = await maker.signOrderAsync();
|
||||
});
|
||||
it('should return TakerAssetDataFailed if the takerAsset transfer fails', async () => {
|
||||
const orderTransferResults = await devUtils
|
||||
@@ -462,11 +430,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const orderTransferResults = await devUtils
|
||||
.getSimulatedOrderTransferResults(signedOrder, takerAddress, signedOrder.takerAssetAmount)
|
||||
@@ -480,11 +448,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(takerAddress, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
@@ -504,11 +472,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(takerAddress, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
@@ -516,11 +484,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const orderTransferResults = await devUtils
|
||||
.getSimulatedOrderTransferResults(signedOrder, takerAddress, signedOrder.takerAssetAmount)
|
||||
@@ -536,11 +504,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(takerAddress, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
@@ -548,11 +516,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const orderTransferResults = await devUtils
|
||||
.getSimulatedOrderTransferResults(signedOrder, takerAddress, signedOrder.takerAssetAmount.dividedBy(2))
|
||||
@@ -569,11 +537,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await erc20Token2.approve(erc20Proxy.address, signedOrder.takerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await erc20Token.setBalance(makerAddress, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
await feeErc20Token.setBalance(takerAddress, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
@@ -581,11 +549,11 @@ describe('OrderValidationUtils/OrderTransferSimulatorUtils', () => {
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.takerFee).awaitTransactionSuccessAsync({
|
||||
from: takerAddress,
|
||||
});
|
||||
await feeErc20Token.setBalance(makerAddress, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
await feeErc20Token.setBalance(maker.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: owner,
|
||||
});
|
||||
await feeErc20Token.approve(erc20Proxy.address, signedOrder.makerFee).awaitTransactionSuccessAsync({
|
||||
from: makerAddress,
|
||||
from: maker.address,
|
||||
});
|
||||
const [orderTransferResults1, orderTransferResults2] = await devUtils
|
||||
.getSimulatedOrdersTransferResults(
|
278
contracts/integrations/test/exchange/fill_dydx_order_test.ts
Normal file
278
contracts/integrations/test/exchange/fill_dydx_order_test.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { DydxBridgeActionType, dydxBridgeDataEncoder, TestDydxBridgeContract } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { LibMathRevertErrors } from '@0x/contracts-exchange-libs';
|
||||
import { blockchainTests, constants, describe, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
||||
import { SignedOrder } from '@0x/order-utils';
|
||||
import { BigNumber, RevertError } from '@0x/utils';
|
||||
import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { deployDydxBridgeAsync } from '../bridges/deploy_dydx_bridge';
|
||||
import { Actor } from '../framework/actors/base';
|
||||
import { Maker } from '../framework/actors/maker';
|
||||
import { Taker } from '../framework/actors/taker';
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
|
||||
blockchainTests.resets('Exchange fills dydx orders', env => {
|
||||
let testContract: TestDydxBridgeContract;
|
||||
let makerToken: DummyERC20TokenContract;
|
||||
let takerToken: DummyERC20TokenContract;
|
||||
const marketId = new BigNumber(3);
|
||||
const dydxConversionRateNumerator = toBaseUnitAmount(6);
|
||||
const dydxConversionRateDenominator = toBaseUnitAmount(1);
|
||||
let maker: Maker;
|
||||
let taker: Taker;
|
||||
let orderConfig: Partial<SignedOrder>;
|
||||
let dydxBridgeProxyAssetData: string;
|
||||
let deployment: DeploymentManager;
|
||||
let testTokenAddress: string;
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: dydxConversionRateNumerator,
|
||||
conversionRateDenominator: dydxConversionRateDenominator,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const bridgeData = {
|
||||
accountNumbers: [new BigNumber(0)],
|
||||
actions: [defaultDepositAction, defaultWithdrawAction],
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
// Deploy contracts
|
||||
deployment = await DeploymentManager.deployAsync(env, {
|
||||
numErc20TokensToDeploy: 2,
|
||||
});
|
||||
testContract = await deployDydxBridgeAsync(deployment, env);
|
||||
const encodedBridgeData = dydxBridgeDataEncoder.encode({ bridgeData });
|
||||
testTokenAddress = await testContract.getTestToken().callAsync();
|
||||
dydxBridgeProxyAssetData = deployment.assetDataEncoder
|
||||
.ERC20Bridge(testTokenAddress, testContract.address, encodedBridgeData)
|
||||
.getABIEncodedTransactionData();
|
||||
[makerToken, takerToken] = deployment.tokens.erc20;
|
||||
|
||||
// Configure default order parameters.
|
||||
orderConfig = {
|
||||
makerAssetAmount: toBaseUnitAmount(1),
|
||||
takerAssetAmount: toBaseUnitAmount(1),
|
||||
makerAssetData: deployment.assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(),
|
||||
takerAssetData: deployment.assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(),
|
||||
// Not important for this test.
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
makerFeeAssetData: deployment.assetDataEncoder
|
||||
.ERC20Token(makerToken.address)
|
||||
.getABIEncodedTransactionData(),
|
||||
takerFeeAssetData: deployment.assetDataEncoder
|
||||
.ERC20Token(takerToken.address)
|
||||
.getABIEncodedTransactionData(),
|
||||
makerFee: toBaseUnitAmount(1),
|
||||
takerFee: toBaseUnitAmount(1),
|
||||
};
|
||||
|
||||
// Configure maker.
|
||||
maker = new Maker({
|
||||
name: 'Maker',
|
||||
deployment,
|
||||
orderConfig,
|
||||
});
|
||||
await maker.configureERC20TokenAsync(makerToken, deployment.assetProxies.erc20Proxy.address);
|
||||
|
||||
// Configure taker.
|
||||
taker = new Taker({
|
||||
name: 'Taker',
|
||||
deployment,
|
||||
});
|
||||
await taker.configureERC20TokenAsync(takerToken, deployment.assetProxies.erc20Proxy.address);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
describe('fillOrder', () => {
|
||||
interface DydxFillResults {
|
||||
makerAssetFilledAmount: BigNumber;
|
||||
takerAssetFilledAmount: BigNumber;
|
||||
makerFeePaid: BigNumber;
|
||||
takerFeePaid: BigNumber;
|
||||
amountDepositedIntoDydx: BigNumber;
|
||||
amountWithdrawnFromDydx: BigNumber;
|
||||
}
|
||||
const fillOrder = async (
|
||||
signedOrder: SignedOrder,
|
||||
customTakerAssetFillAmount?: BigNumber,
|
||||
): Promise<DydxFillResults> => {
|
||||
// Fill order
|
||||
const takerAssetFillAmount =
|
||||
customTakerAssetFillAmount !== undefined ? customTakerAssetFillAmount : signedOrder.takerAssetAmount;
|
||||
const tx = await taker.fillOrderAsync(signedOrder, takerAssetFillAmount);
|
||||
|
||||
// Extract values from fill event.
|
||||
// tslint:disable no-unnecessary-type-assertion
|
||||
const fillEvent = _.find(tx.logs, log => {
|
||||
return (log as any).event === 'Fill';
|
||||
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
||||
|
||||
// Extract amount deposited into dydx from maker.
|
||||
const dydxDepositEvent = _.find(tx.logs, log => {
|
||||
return (
|
||||
(log as any).event === 'OperateAction' &&
|
||||
(log as any).args.actionType === DydxBridgeActionType.Deposit
|
||||
);
|
||||
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
||||
|
||||
// Extract amount withdrawn from dydx to taker.
|
||||
const dydxWithdrawEvent = _.find(tx.logs, log => {
|
||||
return (
|
||||
(log as any).event === 'OperateAction' &&
|
||||
(log as any).args.actionType === DydxBridgeActionType.Withdraw
|
||||
);
|
||||
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
||||
|
||||
// Return values of interest for assertions.
|
||||
return {
|
||||
makerAssetFilledAmount: fillEvent.args.makerAssetFilledAmount,
|
||||
takerAssetFilledAmount: fillEvent.args.takerAssetFilledAmount,
|
||||
makerFeePaid: fillEvent.args.makerFeePaid,
|
||||
takerFeePaid: fillEvent.args.takerFeePaid,
|
||||
amountDepositedIntoDydx: dydxDepositEvent.args.amountValue,
|
||||
amountWithdrawnFromDydx: dydxWithdrawEvent.args.amountValue,
|
||||
};
|
||||
};
|
||||
it('should successfully fill a dydx order (DydxBridge used in makerAssetData)', async () => {
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
// Invert the dydx conversion rate when using the bridge in the makerAssetData.
|
||||
makerAssetData: dydxBridgeProxyAssetData,
|
||||
makerAssetAmount: dydxConversionRateDenominator,
|
||||
takerAssetAmount: dydxConversionRateNumerator,
|
||||
});
|
||||
const dydxFillResults = await fillOrder(signedOrder);
|
||||
expect(
|
||||
dydxFillResults.makerAssetFilledAmount,
|
||||
'makerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
||||
expect(
|
||||
dydxFillResults.takerAssetFilledAmount,
|
||||
'takerAssetFilledAmount should equal amountDepositedIntoDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
||||
});
|
||||
it('should successfully fill a dydx order (DydxBridge used in takerAssetData)', async () => {
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
// Match the dydx conversion rate when using the bridge in the takerAssetData.
|
||||
takerAssetData: dydxBridgeProxyAssetData,
|
||||
makerAssetAmount: dydxConversionRateNumerator,
|
||||
takerAssetAmount: dydxConversionRateDenominator,
|
||||
});
|
||||
const dydxFillResults = await fillOrder(signedOrder);
|
||||
expect(
|
||||
dydxFillResults.makerAssetFilledAmount,
|
||||
'makerAssetFilledAmount should equal amountDepositedIntoDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
||||
expect(
|
||||
dydxFillResults.takerAssetFilledAmount,
|
||||
'takerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
||||
});
|
||||
it('should successfully fill a dydx order (DydxBridge used in makerFeeAssetData)', async () => {
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
// Invert the dydx conversion rate when using the bridge in the makerFeeAssetData.
|
||||
makerFeeAssetData: dydxBridgeProxyAssetData,
|
||||
makerFee: dydxConversionRateDenominator,
|
||||
takerFee: dydxConversionRateNumerator,
|
||||
});
|
||||
const dydxFillResults = await fillOrder(signedOrder);
|
||||
expect(
|
||||
dydxFillResults.makerFeePaid,
|
||||
'makerFeePaid should equal amountWithdrawnFromDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
||||
expect(
|
||||
dydxFillResults.takerFeePaid,
|
||||
'takerFeePaid should equal amountDepositedIntoDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
||||
});
|
||||
it('should successfully fill a dydx order (DydxBridge used in takerFeeAssetData)', async () => {
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
// Match the dydx conversion rate when using the bridge in the takerFeeAssetData.
|
||||
takerFeeAssetData: dydxBridgeProxyAssetData,
|
||||
makerFee: dydxConversionRateNumerator,
|
||||
takerFee: dydxConversionRateDenominator,
|
||||
});
|
||||
const dydxFillResults = await fillOrder(signedOrder);
|
||||
expect(
|
||||
dydxFillResults.makerFeePaid,
|
||||
'makerFeePaid should equal amountDepositedIntoDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
||||
expect(
|
||||
dydxFillResults.takerFeePaid,
|
||||
'takerFeePaid should equal amountWithdrawnFromDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
||||
});
|
||||
it('should partially fill a dydx order (DydxBridge used in makerAssetData)', async () => {
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
// Invert the dydx conversion rate when using the bridge in the makerAssetData.
|
||||
makerAssetData: dydxBridgeProxyAssetData,
|
||||
makerAssetAmount: dydxConversionRateDenominator,
|
||||
takerAssetAmount: dydxConversionRateNumerator,
|
||||
});
|
||||
const dydxFillResults = await fillOrder(signedOrder, signedOrder.takerAssetAmount.div(2));
|
||||
expect(
|
||||
dydxFillResults.makerAssetFilledAmount,
|
||||
'makerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
||||
expect(
|
||||
dydxFillResults.takerAssetFilledAmount,
|
||||
'takerAssetFilledAmount should equal amountDepositedIntoDydx',
|
||||
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
||||
});
|
||||
it('should revert in DydxBridge when there is a rounding error', async () => {
|
||||
// This order will not trigger the rounding error in the Exchange,
|
||||
// but will trigger one in the DydxBridge.
|
||||
const badDepositAction = {
|
||||
...defaultDepositAction,
|
||||
conversionRateNumerator: new BigNumber(5318),
|
||||
conversionRateDenominator: new BigNumber(47958),
|
||||
};
|
||||
const badBridgeData = {
|
||||
accountNumbers: [new BigNumber(0)],
|
||||
actions: [badDepositAction],
|
||||
};
|
||||
const encodedBridgeData = dydxBridgeDataEncoder.encode({ bridgeData: badBridgeData });
|
||||
const badDydxBridgeProxyAssetData = deployment.assetDataEncoder
|
||||
.ERC20Bridge(testTokenAddress, testContract.address, encodedBridgeData)
|
||||
.getABIEncodedTransactionData();
|
||||
const signedOrder = await maker.signOrderAsync({
|
||||
makerAssetData: badDydxBridgeProxyAssetData,
|
||||
makerAssetAmount: badDepositAction.conversionRateDenominator,
|
||||
takerAssetAmount: badDepositAction.conversionRateNumerator,
|
||||
});
|
||||
const takerAssetFillAmount = new BigNumber(998);
|
||||
|
||||
// Compute expected error.
|
||||
const target = takerAssetFillAmount
|
||||
.multipliedBy(signedOrder.makerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
||||
const expectedAssetProxyError = new LibMathRevertErrors.RoundingError(
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
target,
|
||||
);
|
||||
|
||||
// Call fillOrder and assert the rounding error generated by the DydxBridge.
|
||||
const txPromise = taker.fillOrderAsync(signedOrder, takerAssetFillAmount);
|
||||
let assetProxyError;
|
||||
try {
|
||||
await txPromise.catch();
|
||||
} catch (e) {
|
||||
assetProxyError = RevertError.decode(e.values.errorData);
|
||||
}
|
||||
expect(assetProxyError).to.deep.equal(expectedAssetProxyError);
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user