Compare commits
44 Commits
@0x/contra
...
@0x/dev-ut
Author | SHA1 | Date | |
---|---|---|---|
|
51ca3109eb | ||
|
2bcb79dc44 | ||
|
ecec985649 | ||
|
994908549d | ||
|
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 |
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "3.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
@@ -64,6 +73,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Implement `KyberBridge`.",
|
"note": "Implement `KyberBridge`.",
|
||||||
"pr": 2352
|
"pr": 2352
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Implement `DydxBridge`.",
|
||||||
|
"pr": 2365
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timestamp": 1575290197
|
"timestamp": 1575290197
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v3.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v3.0.1 - _December 9, 2019_
|
## v3.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
@@ -26,6 +30,7 @@ CHANGELOG
|
|||||||
## v2.3.0-beta.4 - _December 2, 2019_
|
## v2.3.0-beta.4 - _December 2, 2019_
|
||||||
|
|
||||||
* Implement `KyberBridge`. (#2352)
|
* Implement `KyberBridge`. (#2352)
|
||||||
|
* Implement `DydxBridge`. (#2365)
|
||||||
|
|
||||||
## v2.3.0-beta.3 - _November 20, 2019_
|
## 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''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
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.
|
||||||
|
}
|
||||||
|
}
|
101
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
101
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 "../src/bridges/DydxBridge.sol";
|
||||||
|
|
||||||
|
|
||||||
|
// solhint-disable space-after-comma
|
||||||
|
contract TestDydxBridge is
|
||||||
|
IDydx,
|
||||||
|
DydxBridge
|
||||||
|
{
|
||||||
|
|
||||||
|
address private constant ALWAYS_REVERT_ADDRESS = address(1);
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @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
|
||||||
|
{
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @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;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-asset-proxy",
|
"name": "@0x/contracts-asset-proxy",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"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"
|
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,ChaiBridge,TestStaticCallTarget",
|
"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": "./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:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -52,15 +51,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -80,15 +79,16 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-erc1155": "^2.0.1",
|
"@0x/contracts-erc1155": "^2.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-erc721": "^3.0.1",
|
"@0x/contracts-erc721": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/utils": "^5.1.1",
|
||||||
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"ethereum-types": "^3.0.0",
|
"ethereum-types": "^3.0.0",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
|
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 ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
|
||||||
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
|
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
|
||||||
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.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 Eth2DaiBridge from '../generated-artifacts/Eth2DaiBridge.json';
|
||||||
import * as IAssetData from '../generated-artifacts/IAssetData.json';
|
import * as IAssetData from '../generated-artifacts/IAssetData.json';
|
||||||
import * as IAssetProxy from '../generated-artifacts/IAssetProxy.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 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 MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
|
||||||
|
import * as Ownable from '../generated-artifacts/Ownable.json';
|
||||||
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.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 TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json';
|
||||||
|
import * as TestUniswapBridge from '../generated-artifacts/TestUniswapBridge.json';
|
||||||
import * as UniswapBridge from '../generated-artifacts/UniswapBridge.json';
|
import * as UniswapBridge from '../generated-artifacts/UniswapBridge.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
|
MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact,
|
||||||
|
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
|
||||||
|
Ownable: Ownable as ContractArtifact,
|
||||||
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
|
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
|
||||||
|
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
|
||||||
ERC20Proxy: ERC20Proxy as ContractArtifact,
|
ERC20Proxy: ERC20Proxy as ContractArtifact,
|
||||||
ERC721Proxy: ERC721Proxy as ContractArtifact,
|
ERC721Proxy: ERC721Proxy as ContractArtifact,
|
||||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||||
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
|
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||||
|
DydxBridge: DydxBridge as ContractArtifact,
|
||||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||||
|
KyberBridge: KyberBridge as ContractArtifact,
|
||||||
|
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||||
IAssetData: IAssetData as ContractArtifact,
|
IAssetData: IAssetData as ContractArtifact,
|
||||||
IAssetProxy: IAssetProxy as ContractArtifact,
|
IAssetProxy: IAssetProxy as ContractArtifact,
|
||||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||||
KyberBridge: KyberBridge as ContractArtifact,
|
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||||
ChaiBridge: ChaiBridge 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,
|
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
|
||||||
|
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
@@ -5,6 +5,7 @@ export {
|
|||||||
ERC20ProxyContract,
|
ERC20ProxyContract,
|
||||||
ERC721ProxyContract,
|
ERC721ProxyContract,
|
||||||
Eth2DaiBridgeContract,
|
Eth2DaiBridgeContract,
|
||||||
|
DydxBridgeContract,
|
||||||
IAssetDataContract,
|
IAssetDataContract,
|
||||||
IAssetProxyContract,
|
IAssetProxyContract,
|
||||||
MultiAssetProxyContract,
|
MultiAssetProxyContract,
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
export * from '../generated-wrappers/chai_bridge';
|
export * from '../generated-wrappers/chai_bridge';
|
||||||
|
export * from '../generated-wrappers/dydx_bridge';
|
||||||
export * from '../generated-wrappers/erc1155_proxy';
|
export * from '../generated-wrappers/erc1155_proxy';
|
||||||
export * from '../generated-wrappers/erc20_bridge_proxy';
|
export * from '../generated-wrappers/erc20_bridge_proxy';
|
||||||
export * from '../generated-wrappers/erc20_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/eth2_dai_bridge';
|
||||||
export * from '../generated-wrappers/i_asset_data';
|
export * from '../generated-wrappers/i_asset_data';
|
||||||
export * from '../generated-wrappers/i_asset_proxy';
|
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/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/multi_asset_proxy';
|
||||||
|
export * from '../generated-wrappers/ownable';
|
||||||
export * from '../generated-wrappers/static_call_proxy';
|
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_static_call_target';
|
||||||
|
export * from '../generated-wrappers/test_uniswap_bridge';
|
||||||
export * from '../generated-wrappers/uniswap_bridge';
|
export * from '../generated-wrappers/uniswap_bridge';
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
|
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 ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
|
||||||
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
||||||
import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.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 IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json';
|
||||||
import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json';
|
import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json';
|
||||||
import * as IChai from '../test/generated-artifacts/IChai.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 IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
||||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||||
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.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 Ownable from '../test/generated-artifacts/Ownable.json';
|
||||||
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
||||||
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.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 TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
|
||||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||||
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
|
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
|
||||||
@@ -45,6 +49,7 @@ export const artifacts = {
|
|||||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||||
|
DydxBridge: DydxBridge as ContractArtifact,
|
||||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||||
KyberBridge: KyberBridge as ContractArtifact,
|
KyberBridge: KyberBridge as ContractArtifact,
|
||||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||||
@@ -53,12 +58,15 @@ export const artifacts = {
|
|||||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||||
IAuthorizable: IAuthorizable as ContractArtifact,
|
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||||
IChai: IChai as ContractArtifact,
|
IChai: IChai as ContractArtifact,
|
||||||
|
IDydx: IDydx as ContractArtifact,
|
||||||
|
IDydxBridge: IDydxBridge as ContractArtifact,
|
||||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||||
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
||||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||||
|
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||||
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
||||||
|
244
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
244
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||||
|
import { AssetProxyId, RevertReason } from '@0x/types';
|
||||||
|
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
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';
|
||||||
|
let testContract: TestDydxBridgeContract;
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bridgeTransferFrom()', () => {
|
||||||
|
enum BridgeActionType {
|
||||||
|
Deposit,
|
||||||
|
Withdraw,
|
||||||
|
}
|
||||||
|
interface BrigeAction {
|
||||||
|
actionType: BridgeActionType;
|
||||||
|
accountId: BigNumber;
|
||||||
|
marketId: BigNumber;
|
||||||
|
conversionRateNumerator: BigNumber;
|
||||||
|
conversionRateDenominator: BigNumber;
|
||||||
|
}
|
||||||
|
interface BridgeData {
|
||||||
|
accountNumbers: BigNumber[];
|
||||||
|
actions: BrigeAction[];
|
||||||
|
}
|
||||||
|
const defaultDepositAction = {
|
||||||
|
actionType: BridgeActionType.Deposit as number,
|
||||||
|
accountId: constants.ZERO_AMOUNT,
|
||||||
|
marketId,
|
||||||
|
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||||
|
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
const defaultWithdrawAction = {
|
||||||
|
actionType: BridgeActionType.Withdraw as number,
|
||||||
|
accountId: constants.ZERO_AMOUNT,
|
||||||
|
marketId,
|
||||||
|
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||||
|
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
const bridgeDataEncoder = 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' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const callBridgeTransferFrom = async (
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
amount: BigNumber,
|
||||||
|
bridgeData: BridgeData,
|
||||||
|
sender: string,
|
||||||
|
): Promise<string> => {
|
||||||
|
const returnValue = await testContract
|
||||||
|
.bridgeTransferFrom(constants.NULL_ADDRESS, from, to, amount, bridgeDataEncoder.encode({ bridgeData }))
|
||||||
|
.callAsync({ from: sender });
|
||||||
|
return returnValue;
|
||||||
|
};
|
||||||
|
const callBridgeTransferFromAndVerifyEvents = async (
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
amount: BigNumber,
|
||||||
|
bridgeData: BridgeData,
|
||||||
|
sender: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
// Execute transaction.
|
||||||
|
const txReceipt = await testContract
|
||||||
|
.bridgeTransferFrom(constants.NULL_ADDRESS, from, to, amount, bridgeDataEncoder.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 === BridgeActionType.Deposit ? true : false,
|
||||||
|
amountDenomination: weiDenomination,
|
||||||
|
amountRef: deltaAmountRef,
|
||||||
|
amountValue: action.conversionRateDenominator.gt(0)
|
||||||
|
? amount.times(action.conversionRateNumerator).div(action.conversionRateDenominator)
|
||||||
|
: amount,
|
||||||
|
primaryMarketId: marketId,
|
||||||
|
secondaryMarketId: constants.ZERO_AMOUNT,
|
||||||
|
otherAddress: action.actionType === BridgeActionType.Deposit ? from : to,
|
||||||
|
otherAccountId: constants.ZERO_AMOUNT,
|
||||||
|
data: '0x',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
verifyEventsFromLogs(txReceipt.logs, expectedOperateActionEvents, TestDydxBridgeEvents.OperateAction);
|
||||||
|
};
|
||||||
|
it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
|
||||||
|
const bridgeData = {
|
||||||
|
accountNumbers: [defaultAccountNumber],
|
||||||
|
actions: [defaultDepositAction],
|
||||||
|
};
|
||||||
|
await callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||||
|
});
|
||||||
|
it('succeeds when calling `operate` with the `withdraw` action and a single accuont', async () => {
|
||||||
|
const bridgeData = {
|
||||||
|
accountNumbers: [defaultAccountNumber],
|
||||||
|
actions: [defaultWithdrawAction],
|
||||||
|
};
|
||||||
|
await callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(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 callBridgeTransferFromAndVerifyEvents(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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -4,6 +4,7 @@
|
|||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
export * from '../test/generated-wrappers/chai_bridge';
|
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/erc1155_proxy';
|
||||||
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
||||||
export * from '../test/generated-wrappers/erc20_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_asset_proxy_dispatcher';
|
||||||
export * from '../test/generated-wrappers/i_authorizable';
|
export * from '../test/generated-wrappers/i_authorizable';
|
||||||
export * from '../test/generated-wrappers/i_chai';
|
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_erc20_bridge';
|
||||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||||
export * from '../test/generated-wrappers/i_kyber_network_proxy';
|
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/ownable';
|
||||||
export * from '../test/generated-wrappers/static_call_proxy';
|
export * from '../test/generated-wrappers/static_call_proxy';
|
||||||
export * from '../test/generated-wrappers/test_chai_bridge';
|
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_erc20_bridge';
|
||||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||||
export * from '../test/generated-wrappers/test_kyber_bridge';
|
export * from '../test/generated-wrappers/test_kyber_bridge';
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||||
"files": [
|
"files": [
|
||||||
"generated-artifacts/ChaiBridge.json",
|
"generated-artifacts/ChaiBridge.json",
|
||||||
|
"generated-artifacts/DydxBridge.json",
|
||||||
"generated-artifacts/ERC1155Proxy.json",
|
"generated-artifacts/ERC1155Proxy.json",
|
||||||
"generated-artifacts/ERC20BridgeProxy.json",
|
"generated-artifacts/ERC20BridgeProxy.json",
|
||||||
"generated-artifacts/ERC20Proxy.json",
|
"generated-artifacts/ERC20Proxy.json",
|
||||||
@@ -11,12 +12,32 @@
|
|||||||
"generated-artifacts/Eth2DaiBridge.json",
|
"generated-artifacts/Eth2DaiBridge.json",
|
||||||
"generated-artifacts/IAssetData.json",
|
"generated-artifacts/IAssetData.json",
|
||||||
"generated-artifacts/IAssetProxy.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/KyberBridge.json",
|
||||||
|
"generated-artifacts/MixinAssetProxyDispatcher.json",
|
||||||
|
"generated-artifacts/MixinAuthorizable.json",
|
||||||
"generated-artifacts/MultiAssetProxy.json",
|
"generated-artifacts/MultiAssetProxy.json",
|
||||||
|
"generated-artifacts/Ownable.json",
|
||||||
"generated-artifacts/StaticCallProxy.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/TestStaticCallTarget.json",
|
||||||
|
"generated-artifacts/TestUniswapBridge.json",
|
||||||
"generated-artifacts/UniswapBridge.json",
|
"generated-artifacts/UniswapBridge.json",
|
||||||
"test/generated-artifacts/ChaiBridge.json",
|
"test/generated-artifacts/ChaiBridge.json",
|
||||||
|
"test/generated-artifacts/DydxBridge.json",
|
||||||
"test/generated-artifacts/ERC1155Proxy.json",
|
"test/generated-artifacts/ERC1155Proxy.json",
|
||||||
"test/generated-artifacts/ERC20BridgeProxy.json",
|
"test/generated-artifacts/ERC20BridgeProxy.json",
|
||||||
"test/generated-artifacts/ERC20Proxy.json",
|
"test/generated-artifacts/ERC20Proxy.json",
|
||||||
@@ -27,6 +48,8 @@
|
|||||||
"test/generated-artifacts/IAssetProxyDispatcher.json",
|
"test/generated-artifacts/IAssetProxyDispatcher.json",
|
||||||
"test/generated-artifacts/IAuthorizable.json",
|
"test/generated-artifacts/IAuthorizable.json",
|
||||||
"test/generated-artifacts/IChai.json",
|
"test/generated-artifacts/IChai.json",
|
||||||
|
"test/generated-artifacts/IDydx.json",
|
||||||
|
"test/generated-artifacts/IDydxBridge.json",
|
||||||
"test/generated-artifacts/IERC20Bridge.json",
|
"test/generated-artifacts/IERC20Bridge.json",
|
||||||
"test/generated-artifacts/IEth2Dai.json",
|
"test/generated-artifacts/IEth2Dai.json",
|
||||||
"test/generated-artifacts/IKyberNetworkProxy.json",
|
"test/generated-artifacts/IKyberNetworkProxy.json",
|
||||||
@@ -39,6 +62,7 @@
|
|||||||
"test/generated-artifacts/Ownable.json",
|
"test/generated-artifacts/Ownable.json",
|
||||||
"test/generated-artifacts/StaticCallProxy.json",
|
"test/generated-artifacts/StaticCallProxy.json",
|
||||||
"test/generated-artifacts/TestChaiBridge.json",
|
"test/generated-artifacts/TestChaiBridge.json",
|
||||||
|
"test/generated-artifacts/TestDydxBridge.json",
|
||||||
"test/generated-artifacts/TestERC20Bridge.json",
|
"test/generated-artifacts/TestERC20Bridge.json",
|
||||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||||
"test/generated-artifacts/TestKyberBridge.json",
|
"test/generated-artifacts/TestKyberBridge.json",
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "3.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v3.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v3.0.1 - _December 9, 2019_
|
## v3.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-coordinator",
|
"name": "@0x/contracts-coordinator",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,19 +52,19 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-exchange": "^3.0.1",
|
"@0x/contracts-exchange": "^3.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -84,14 +84,14 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^3.0.1",
|
"@0x/assert": "^3.0.2",
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contract-addresses": "^4.0.0",
|
"@0x/contract-addresses": "^4.1.0",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/json-schemas": "^5.0.1",
|
"@0x/json-schemas": "^5.0.2",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"ethereum-types": "^3.0.0",
|
"ethereum-types": "^3.0.0",
|
||||||
"http-status-codes": "^1.3.2"
|
"http-status-codes": "^1.3.2"
|
||||||
},
|
},
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "1.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v1.0.1 - _December 9, 2019_
|
## v1.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-dev-utils",
|
"name": "@0x/contracts-dev-utils",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -41,10 +41,10 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/assert": "^3.0.1",
|
"@0x/assert": "^3.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1"
|
"@0x/base-contract": "^6.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "2.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v2.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v2.0.1 - _December 9, 2019_
|
## v2.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-erc1155",
|
"name": "@0x/contracts-erc1155",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,15 +52,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -80,10 +80,10 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -1,4 +1,14 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "1.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Do not query empty/unsigned orders. Swallow revets on DEX quotes.",
|
||||||
|
"pr": 2365
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": 1576540892
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Do not query empty/unsigned orders. Swallow revets on DEX quotes. (#2365)
|
||||||
|
|
||||||
## v1.0.1 - _December 9, 2019_
|
## v1.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* 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-erc20/contracts/src/LibERC20Token.sol";
|
||||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.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/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 "./IERC20BridgeSampler.sol";
|
||||||
import "./IEth2Dai.sol";
|
import "./IEth2Dai.sol";
|
||||||
import "./IKyberNetwork.sol";
|
import "./IKyberNetwork.sol";
|
||||||
import "./IUniswapExchangeQuotes.sol";
|
import "./IUniswapExchangeQuotes.sol";
|
||||||
import "./DeploymentConstants.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract ERC20BridgeSampler is
|
contract ERC20BridgeSampler is
|
||||||
@@ -38,26 +40,32 @@ contract ERC20BridgeSampler is
|
|||||||
|
|
||||||
/// @dev Query native orders and sample sell quotes 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 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 sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
/// @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
|
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||||
/// each taker token amount. First indexed by source index, then sample
|
/// each taker token amount. First indexed by source index, then sample
|
||||||
/// index.
|
/// index.
|
||||||
function queryOrdersAndSampleSells(
|
function queryOrdersAndSampleSells(
|
||||||
LibOrder.Order[] memory orders,
|
LibOrder.Order[] memory orders,
|
||||||
|
bytes[] memory orderSignatures,
|
||||||
address[] memory sources,
|
address[] memory sources,
|
||||||
uint256[] memory takerTokenAmounts
|
uint256[] memory takerTokenAmounts
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
view
|
view
|
||||||
returns (
|
returns (
|
||||||
LibOrder.OrderInfo[] memory orderInfos,
|
uint256[] memory orderFillableTakerAssetAmounts,
|
||||||
uint256[][] memory makerTokenAmountsBySource
|
uint256[][] memory makerTokenAmountsBySource
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
require(orders.length != 0, "EMPTY_ORDERS");
|
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
|
||||||
orderInfos = queryOrders(orders);
|
orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts(
|
||||||
|
orders,
|
||||||
|
orderSignatures
|
||||||
|
);
|
||||||
makerTokenAmountsBySource = sampleSells(
|
makerTokenAmountsBySource = sampleSells(
|
||||||
sources,
|
sources,
|
||||||
_assetDataToTokenAddress(orders[0].takerAssetData),
|
_assetDataToTokenAddress(orders[0].takerAssetData),
|
||||||
@@ -68,26 +76,32 @@ contract ERC20BridgeSampler is
|
|||||||
|
|
||||||
/// @dev Query native orders and sample buy quotes 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 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 sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
/// @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
|
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||||
/// each maker token amount. First indexed by source index, then sample
|
/// each maker token amount. First indexed by source index, then sample
|
||||||
/// index.
|
/// index.
|
||||||
function queryOrdersAndSampleBuys(
|
function queryOrdersAndSampleBuys(
|
||||||
LibOrder.Order[] memory orders,
|
LibOrder.Order[] memory orders,
|
||||||
|
bytes[] memory orderSignatures,
|
||||||
address[] memory sources,
|
address[] memory sources,
|
||||||
uint256[] memory makerTokenAmounts
|
uint256[] memory makerTokenAmounts
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
view
|
view
|
||||||
returns (
|
returns (
|
||||||
LibOrder.OrderInfo[] memory orderInfos,
|
uint256[] memory orderFillableMakerAssetAmounts,
|
||||||
uint256[][] memory makerTokenAmountsBySource
|
uint256[][] memory makerTokenAmountsBySource
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
require(orders.length != 0, "EMPTY_ORDERS");
|
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
|
||||||
orderInfos = queryOrders(orders);
|
orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts(
|
||||||
|
orders,
|
||||||
|
orderSignatures
|
||||||
|
);
|
||||||
makerTokenAmountsBySource = sampleBuys(
|
makerTokenAmountsBySource = sampleBuys(
|
||||||
sources,
|
sources,
|
||||||
_assetDataToTokenAddress(orders[0].takerAssetData),
|
_assetDataToTokenAddress(orders[0].takerAssetData),
|
||||||
@@ -96,18 +110,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.
|
/// @param orders Native orders to query.
|
||||||
/// @return orderInfos Order info for each respective order.
|
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||||
function queryOrders(LibOrder.Order[] memory 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
|
public
|
||||||
view
|
view
|
||||||
returns (LibOrder.OrderInfo[] memory orderInfos)
|
returns (uint256[] memory orderFillableTakerAssetAmounts)
|
||||||
{
|
{
|
||||||
uint256 numOrders = orders.length;
|
orderFillableTakerAssetAmounts = new uint256[](orders.length);
|
||||||
orderInfos = new LibOrder.OrderInfo[](numOrders);
|
for (uint256 i = 0; i != orders.length; i++) {
|
||||||
for (uint256 i = 0; i < numOrders; i++) {
|
// Ignore orders with no signature or empty maker/taker amounts.
|
||||||
orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]);
|
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 != uint8(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 +260,24 @@ contract ERC20BridgeSampler is
|
|||||||
returns (uint256[] memory makerTokenAmounts)
|
returns (uint256[] memory makerTokenAmounts)
|
||||||
{
|
{
|
||||||
_assertValidPair(makerToken, takerToken);
|
_assertValidPair(makerToken, takerToken);
|
||||||
address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken;
|
address _takerToken = takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken;
|
||||||
address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken;
|
address _makerToken = makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken;
|
||||||
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
||||||
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
uint256 numSamples = takerTokenAmounts.length;
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
makerTokenAmounts = new uint256[](numSamples);
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
for (uint256 i = 0; i < numSamples; i++) {
|
||||||
(uint256 rate,) = _getKyberNetworkContract().getExpectedRate(
|
(bool didSucceed, bytes memory resultData) =
|
||||||
_takerToken,
|
_getKyberNetworkProxyAddress().staticcall(abi.encodeWithSelector(
|
||||||
_makerToken,
|
IKyberNetwork(0).getExpectedRate.selector,
|
||||||
takerTokenAmounts[i]
|
_takerToken,
|
||||||
);
|
_makerToken,
|
||||||
|
takerTokenAmounts[i]
|
||||||
|
));
|
||||||
|
uint256 rate = 0;
|
||||||
|
if (didSucceed) {
|
||||||
|
rate = abi.decode(resultData, (uint256));
|
||||||
|
}
|
||||||
makerTokenAmounts[i] =
|
makerTokenAmounts[i] =
|
||||||
rate *
|
rate *
|
||||||
takerTokenAmounts[i] *
|
takerTokenAmounts[i] *
|
||||||
@@ -227,11 +306,18 @@ contract ERC20BridgeSampler is
|
|||||||
uint256 numSamples = takerTokenAmounts.length;
|
uint256 numSamples = takerTokenAmounts.length;
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
makerTokenAmounts = new uint256[](numSamples);
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
for (uint256 i = 0; i < numSamples; i++) {
|
||||||
makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount(
|
(bool didSucceed, bytes memory resultData) =
|
||||||
makerToken,
|
_getEth2DaiAddress().staticcall(abi.encodeWithSelector(
|
||||||
takerToken,
|
IEth2Dai(0).getBuyAmount.selector,
|
||||||
takerTokenAmounts[i]
|
makerToken,
|
||||||
);
|
takerToken,
|
||||||
|
takerTokenAmounts[i]
|
||||||
|
));
|
||||||
|
uint256 buyAmount = 0;
|
||||||
|
if (didSucceed) {
|
||||||
|
buyAmount = abi.decode(resultData, (uint256));
|
||||||
|
}
|
||||||
|
makerTokenAmounts[i] = buyAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,11 +340,18 @@ contract ERC20BridgeSampler is
|
|||||||
uint256 numSamples = makerTokenAmounts.length;
|
uint256 numSamples = makerTokenAmounts.length;
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
takerTokenAmounts = new uint256[](numSamples);
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
for (uint256 i = 0; i < numSamples; i++) {
|
||||||
takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount(
|
(bool didSucceed, bytes memory resultData) =
|
||||||
takerToken,
|
_getEth2DaiAddress().staticcall(abi.encodeWithSelector(
|
||||||
makerToken,
|
IEth2Dai(0).getPayAmount.selector,
|
||||||
makerTokenAmounts[i]
|
takerToken,
|
||||||
);
|
makerToken,
|
||||||
|
makerTokenAmounts[i]
|
||||||
|
));
|
||||||
|
uint256 sellAmount = 0;
|
||||||
|
if (didSucceed) {
|
||||||
|
sellAmount = abi.decode(resultData, (uint256));
|
||||||
|
}
|
||||||
|
takerTokenAmounts[i] = sellAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,26 +373,38 @@ contract ERC20BridgeSampler is
|
|||||||
_assertValidPair(makerToken, takerToken);
|
_assertValidPair(makerToken, takerToken);
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
uint256 numSamples = takerTokenAmounts.length;
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
makerTokenAmounts = new uint256[](numSamples);
|
||||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
|
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
|
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
for (uint256 i = 0; i < numSamples; i++) {
|
||||||
if (makerToken == _getWETHAddress()) {
|
if (makerToken == _getWethAddress()) {
|
||||||
makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice(
|
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
|
address(takerTokenExchange),
|
||||||
|
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||||
takerTokenAmounts[i]
|
takerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
} else if (takerToken == _getWETHAddress()) {
|
} else if (takerToken == _getWethAddress()) {
|
||||||
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
|
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
|
address(makerTokenExchange),
|
||||||
|
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||||
takerTokenAmounts[i]
|
takerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice(
|
uint256 ethBought = _callUniswapExchangePriceFunction(
|
||||||
|
address(takerTokenExchange),
|
||||||
|
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||||
takerTokenAmounts[i]
|
takerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
|
if (ethBought != 0) {
|
||||||
ethBought
|
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
);
|
address(makerTokenExchange),
|
||||||
|
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||||
|
ethBought
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
makerTokenAmounts[i] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,26 +427,38 @@ contract ERC20BridgeSampler is
|
|||||||
_assertValidPair(makerToken, takerToken);
|
_assertValidPair(makerToken, takerToken);
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
uint256 numSamples = makerTokenAmounts.length;
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
takerTokenAmounts = new uint256[](numSamples);
|
||||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
|
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
|
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
for (uint256 i = 0; i < numSamples; i++) {
|
||||||
if (makerToken == _getWETHAddress()) {
|
if (makerToken == _getWethAddress()) {
|
||||||
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
|
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
|
address(takerTokenExchange),
|
||||||
|
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||||
makerTokenAmounts[i]
|
makerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
} else if (takerToken == _getWETHAddress()) {
|
} else if (takerToken == _getWethAddress()) {
|
||||||
takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice(
|
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
|
address(makerTokenExchange),
|
||||||
|
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||||
makerTokenAmounts[i]
|
makerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice(
|
uint256 ethSold = _callUniswapExchangePriceFunction(
|
||||||
|
address(makerTokenExchange),
|
||||||
|
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||||
makerTokenAmounts[i]
|
makerTokenAmounts[i]
|
||||||
);
|
);
|
||||||
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
|
if (ethSold != 0) {
|
||||||
ethSold
|
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||||
);
|
address(takerTokenExchange),
|
||||||
|
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||||
|
ethSold
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
takerTokenAmounts[i] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,6 +474,34 @@ contract ERC20BridgeSampler is
|
|||||||
return LibERC20Token.decimals(tokenAddress);
|
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(abi.encodeWithSelector(
|
||||||
|
functionSelector,
|
||||||
|
inputAmount
|
||||||
|
));
|
||||||
|
if (didSucceed) {
|
||||||
|
outputAmount = abi.decode(resultData, (uint256));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Samples a supported sell source, defined by its address.
|
/// @dev Samples a supported sell source, defined by its address.
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
/// @param takerToken Address of the taker token (what to sell).
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
/// @param makerToken Address of the maker token (what to buy).
|
||||||
@@ -373,16 +518,16 @@ contract ERC20BridgeSampler is
|
|||||||
view
|
view
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
returns (uint256[] memory makerTokenAmounts)
|
||||||
{
|
{
|
||||||
if (source == address(_getEth2DaiContract())) {
|
if (source == _getEth2DaiAddress()) {
|
||||||
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
|
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
|
||||||
}
|
}
|
||||||
if (source == address(_getUniswapExchangeFactoryContract())) {
|
if (source == _getUniswapExchangeFactoryAddress()) {
|
||||||
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
|
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
|
||||||
}
|
}
|
||||||
if (source == address(_getKyberNetworkContract())) {
|
if (source == _getKyberNetworkProxyAddress()) {
|
||||||
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
|
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
|
||||||
}
|
}
|
||||||
revert("UNSUPPORTED_SOURCE");
|
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Samples a supported buy source, defined by its address.
|
/// @dev Samples a supported buy source, defined by its address.
|
||||||
@@ -401,13 +546,13 @@ contract ERC20BridgeSampler is
|
|||||||
view
|
view
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
returns (uint256[] memory takerTokenAmounts)
|
||||||
{
|
{
|
||||||
if (source == address(_getEth2DaiContract())) {
|
if (source == _getEth2DaiAddress()) {
|
||||||
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
|
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
|
||||||
}
|
}
|
||||||
if (source == address(_getUniswapExchangeFactoryContract())) {
|
if (source == _getUniswapExchangeFactoryAddress()) {
|
||||||
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
|
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
|
||||||
}
|
}
|
||||||
revert("UNSUPPORTED_SOURCE");
|
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Retrive an existing Uniswap exchange contract.
|
/// @dev Retrive an existing Uniswap exchange contract.
|
||||||
@@ -420,9 +565,9 @@ contract ERC20BridgeSampler is
|
|||||||
returns (IUniswapExchangeQuotes exchange)
|
returns (IUniswapExchangeQuotes exchange)
|
||||||
{
|
{
|
||||||
exchange = IUniswapExchangeQuotes(
|
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.
|
/// @dev Extract the token address from ERC20 proxy asset data.
|
||||||
@@ -433,19 +578,19 @@ contract ERC20BridgeSampler is
|
|||||||
pure
|
pure
|
||||||
returns (address tokenAddress)
|
returns (address tokenAddress)
|
||||||
{
|
{
|
||||||
require(assetData.length == 36, "INVALID_ASSET_DATA");
|
require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA");
|
||||||
bytes4 selector;
|
bytes4 selector;
|
||||||
assembly {
|
assembly {
|
||||||
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
|
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
|
||||||
tokenAddress := mload(add(assetData, 0x24))
|
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)
|
function _assertValidPair(address makerToken, address takerToken)
|
||||||
private
|
private
|
||||||
pure
|
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 solidity ^0.5.9;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
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/LibOrder.sol";
|
||||||
|
|
||||||
|
|
||||||
interface IERC20BridgeSampler {
|
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 orders Native orders to query.
|
||||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||||
/// @param takerTokenAmounts Taker sell amount for each sample.
|
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
/// @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
|
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||||
/// each taker token amount. First indexed by source index, then sample
|
/// each taker token amount. First indexed by source index, then sample
|
||||||
/// index.
|
/// index.
|
||||||
function queryOrdersAndSampleSells(
|
function queryOrdersAndSampleSells(
|
||||||
LibOrder.Order[] calldata orders,
|
LibOrder.Order[] calldata orders,
|
||||||
|
bytes[] calldata orderSignatures,
|
||||||
address[] calldata sources,
|
address[] calldata sources,
|
||||||
uint256[] calldata takerTokenAmounts
|
uint256[] calldata takerTokenAmounts
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (
|
returns (
|
||||||
LibOrder.OrderInfo[] memory orderInfos,
|
uint256[] memory orderFillableTakerAssetAmounts,
|
||||||
uint256[][] memory makerTokenAmountsBySource
|
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 orders Native orders to query.
|
||||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||||
/// @param makerTokenAmounts Maker sell amount for each sample.
|
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
/// @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
|
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||||
/// each maker token amount. First indexed by source index, then sample
|
/// each maker token amount. First indexed by source index, then sample
|
||||||
/// index.
|
/// index.
|
||||||
function queryOrdersAndSampleBuys(
|
function queryOrdersAndSampleBuys(
|
||||||
LibOrder.Order[] calldata orders,
|
LibOrder.Order[] calldata orders,
|
||||||
|
bytes[] calldata orderSignatures,
|
||||||
address[] calldata sources,
|
address[] calldata sources,
|
||||||
uint256[] calldata makerTokenAmounts
|
uint256[] calldata makerTokenAmounts
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (
|
returns (
|
||||||
LibOrder.OrderInfo[] memory orderInfos,
|
uint256[] memory orderFillableMakerAssetAmounts,
|
||||||
uint256[][] memory makerTokenAmountsBySource
|
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.
|
/// @param orders Native orders to query.
|
||||||
/// @return orderInfos Order info for each respective order.
|
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||||
function queryOrders(LibOrder.Order[] calldata 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
|
external
|
||||||
view
|
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.
|
/// @dev Sample sell quotes on multiple DEXes at once.
|
||||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
/// @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 "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||||
import "../src/ERC20BridgeSampler.sol";
|
import "../src/ERC20BridgeSampler.sol";
|
||||||
import "../src/IEth2Dai.sol";
|
import "../src/IEth2Dai.sol";
|
||||||
|
import "../src/IDevUtils.sol";
|
||||||
import "../src/IKyberNetwork.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
|
contract TestERC20BridgeSamplerUniswapExchange is
|
||||||
IUniswapExchangeQuotes,
|
IUniswapExchangeQuotes,
|
||||||
DeploymentConstants
|
DeploymentConstants,
|
||||||
|
FailTrigger
|
||||||
{
|
{
|
||||||
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
|
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
|
||||||
|
|
||||||
@@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
|||||||
view
|
view
|
||||||
returns (uint256 tokensBought)
|
returns (uint256 tokensBought)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||||
salt,
|
salt,
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
WETH_ADDRESS,
|
_getWethAddress(),
|
||||||
ethSold
|
ethSold
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
|||||||
view
|
view
|
||||||
returns (uint256 ethSold)
|
returns (uint256 ethSold)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||||
salt,
|
salt,
|
||||||
WETH_ADDRESS,
|
_getWethAddress(),
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
tokensBought
|
tokensBought
|
||||||
);
|
);
|
||||||
@@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
|||||||
view
|
view
|
||||||
returns (uint256 ethBought)
|
returns (uint256 ethBought)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||||
salt,
|
salt,
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
WETH_ADDRESS,
|
_getWethAddress(),
|
||||||
tokensSold
|
tokensSold
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
|||||||
view
|
view
|
||||||
returns (uint256 tokensSold)
|
returns (uint256 tokensSold)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||||
salt,
|
salt,
|
||||||
WETH_ADDRESS,
|
_getWethAddress(),
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
ethBought
|
ethBought
|
||||||
);
|
);
|
||||||
@@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
|||||||
|
|
||||||
contract TestERC20BridgeSamplerKyberNetwork is
|
contract TestERC20BridgeSamplerKyberNetwork is
|
||||||
IKyberNetwork,
|
IKyberNetwork,
|
||||||
DeploymentConstants
|
DeploymentConstants,
|
||||||
|
FailTrigger
|
||||||
{
|
{
|
||||||
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
|
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
|
||||||
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||||
@@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
|||||||
view
|
view
|
||||||
returns (uint256 expectedRate, uint256)
|
returns (uint256 expectedRate, uint256)
|
||||||
{
|
{
|
||||||
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken;
|
_revertIfShouldFail();
|
||||||
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken;
|
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
||||||
|
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
||||||
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
||||||
SALT,
|
SALT,
|
||||||
fromToken,
|
fromToken,
|
||||||
@@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
|||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSamplerEth2Dai is
|
contract TestERC20BridgeSamplerEth2Dai is
|
||||||
IEth2Dai
|
IEth2Dai,
|
||||||
|
FailTrigger
|
||||||
{
|
{
|
||||||
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
|
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
|
||||||
|
|
||||||
@@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
|||||||
view
|
view
|
||||||
returns (uint256 buyAmount)
|
returns (uint256 buyAmount)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||||
SALT,
|
SALT,
|
||||||
payToken,
|
payToken,
|
||||||
@@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
|||||||
view
|
view
|
||||||
returns (uint256 payAmount)
|
returns (uint256 payAmount)
|
||||||
{
|
{
|
||||||
|
_revertIfShouldFail();
|
||||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||||
SALT,
|
SALT,
|
||||||
payToken,
|
payToken,
|
||||||
@@ -269,7 +298,8 @@ contract TestERC20BridgeSamplerUniswapExchangeFactory is
|
|||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSampler is
|
contract TestERC20BridgeSampler is
|
||||||
ERC20BridgeSampler
|
ERC20BridgeSampler,
|
||||||
|
FailTrigger
|
||||||
{
|
{
|
||||||
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
|
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
|
||||||
TestERC20BridgeSamplerEth2Dai public eth2Dai;
|
TestERC20BridgeSamplerEth2Dai public eth2Dai;
|
||||||
@@ -288,18 +318,30 @@ contract TestERC20BridgeSampler is
|
|||||||
uniswap.createTokenExchanges(tokenAddresses);
|
uniswap.createTokenExchanges(tokenAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `IExchange.getOrderInfo()`, overridden to return deterministic order infos.
|
// `IDevUtils.getOrderRelevantState()`, overridden to return deterministic
|
||||||
function getOrderInfo(LibOrder.Order memory order)
|
// states.
|
||||||
|
function getOrderRelevantState(
|
||||||
|
LibOrder.Order memory order,
|
||||||
|
bytes memory
|
||||||
|
)
|
||||||
public
|
public
|
||||||
pure
|
view
|
||||||
returns (LibOrder.OrderInfo memory orderInfo)
|
returns (
|
||||||
|
LibOrder.OrderInfo memory orderInfo,
|
||||||
|
uint256 fillableTakerAssetAmount,
|
||||||
|
bool isValidSignature
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// The order hash is just the hash of the salt.
|
// The order hash is just the hash of the salt.
|
||||||
bytes32 orderHash = keccak256(abi.encode(order.salt));
|
bytes32 orderHash = keccak256(abi.encode(order.salt));
|
||||||
// Everything else is derived from the hash.
|
// Everything else is derived from the hash.
|
||||||
orderInfo.orderHash = orderHash;
|
orderInfo.orderHash = orderHash;
|
||||||
orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1));
|
orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1));
|
||||||
orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount;
|
orderInfo.orderTakerAssetFilledAmount =
|
||||||
|
uint256(orderHash) % order.takerAssetAmount;
|
||||||
|
fillableTakerAssetAmount =
|
||||||
|
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;
|
||||||
|
isValidSignature = uint256(orderHash) % 2 == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overriden to return deterministic decimals.
|
// Overriden to return deterministic decimals.
|
||||||
@@ -312,38 +354,38 @@ contract TestERC20BridgeSampler is
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overriden to point to a this contract.
|
// Overriden to point to a this contract.
|
||||||
function _getExchangeContract()
|
function _getDevUtilsAddress()
|
||||||
internal
|
internal
|
||||||
view
|
view
|
||||||
returns (IExchange zeroex)
|
returns (address devUtilAddress)
|
||||||
{
|
{
|
||||||
return IExchange(address(this));
|
return address(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overriden to point to a custom contract.
|
// Overriden to point to a custom contract.
|
||||||
function _getEth2DaiContract()
|
function _getEth2DaiAddress()
|
||||||
internal
|
internal
|
||||||
view
|
view
|
||||||
returns (IEth2Dai eth2dai_)
|
returns (address eth2daiAddress)
|
||||||
{
|
{
|
||||||
return eth2Dai;
|
return address(eth2Dai);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overriden to point to a custom contract.
|
// Overriden to point to a custom contract.
|
||||||
function _getUniswapExchangeFactoryContract()
|
function _getUniswapExchangeFactoryAddress()
|
||||||
internal
|
internal
|
||||||
view
|
view
|
||||||
returns (IUniswapExchangeFactory uniswap_)
|
returns (address uniswapAddress)
|
||||||
{
|
{
|
||||||
return uniswap;
|
return address(uniswap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overriden to point to a custom contract.
|
// Overriden to point to a custom contract.
|
||||||
function _getKyberNetworkContract()
|
function _getKyberNetworkProxyAddress()
|
||||||
internal
|
internal
|
||||||
view
|
view
|
||||||
returns (IKyberNetwork kyber_)
|
returns (address kyberAddress)
|
||||||
{
|
{
|
||||||
return kyber;
|
return address(kyber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-erc20-bridge-sampler",
|
"name": "@0x/contracts-erc20-bridge-sampler",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
|
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
|
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -50,18 +50,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-exchange": "^3.0.1",
|
"@0x/contracts-exchange": "^3.0.2",
|
||||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -79,10 +79,10 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"ethereum-types": "^3.0.0",
|
"ethereum-types": "^3.0.0",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
|
@@ -5,16 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
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 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 IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
|
||||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||||
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
||||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
DeploymentConstants: DeploymentConstants as ContractArtifact,
|
|
||||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||||
|
IDevUtils: IDevUtils as ContractArtifact,
|
||||||
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
||||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
getRandomPortion,
|
getRandomPortion,
|
||||||
randomAddress,
|
randomAddress,
|
||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import { Order, OrderInfo } from '@0x/types';
|
import { Order } from '@0x/types';
|
||||||
import { BigNumber, hexUtils } from '@0x/utils';
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
@@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
const INVALID_ASSET_DATA = hexUtils.random(37);
|
const INVALID_ASSET_DATA = hexUtils.random(37);
|
||||||
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
|
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
|
||||||
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
|
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
|
||||||
const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS';
|
const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
|
||||||
const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY';
|
const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
|
||||||
const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA';
|
const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
|
||||||
const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE';
|
const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
|
||||||
const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE';
|
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
|
||||||
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR';
|
const MAKER_TOKEN = randomAddress();
|
||||||
|
const TAKER_TOKEN = randomAddress();
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||||
@@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
return quotes;
|
return quotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeterministicOrderInfo(order: Order): OrderInfo {
|
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
|
||||||
const hash = getPackedHash(hexUtils.leftPad(order.salt, 32));
|
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
|
||||||
return {
|
const orderStatus = new BigNumber(hash).mod(255).toNumber();
|
||||||
orderHash: hash,
|
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
|
||||||
orderStatus: new BigNumber(hash).mod(255).toNumber(),
|
if (orderStatus !== 3 || !isValidSignature) {
|
||||||
orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount),
|
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 {
|
function getERC20AssetData(tokenAddress: string): string {
|
||||||
@@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
|
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('queryOrders()', () => {
|
async function enableFailTriggerAsync(): Promise<void> {
|
||||||
const MAKER_TOKEN = randomAddress();
|
await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
|
||||||
const TAKER_TOKEN = randomAddress();
|
}
|
||||||
|
|
||||||
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 orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||||
const expected = orders.map(getDeterministicOrderInfo);
|
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||||
const actual = await testContract.queryOrders(orders).callAsync();
|
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
|
||||||
|
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||||
expect(actual).to.deep.eq(expected);
|
expect(actual).to.deep.eq(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty for no orders', async () => {
|
it('returns empty for no orders', async () => {
|
||||||
const actual = await testContract.queryOrders([]).callAsync();
|
const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync();
|
||||||
expect(actual).to.deep.eq([]);
|
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()', () => {
|
describe('queryOrdersAndSampleSells()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||||
const TAKER_TOKEN = randomAddress();
|
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
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 takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
|
||||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
|
||||||
const [orderInfos] = await testContract
|
const [orderInfos] = await testContract
|
||||||
.queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return quotes for all sources', async () => {
|
it('can return quotes for all sources', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
|
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
|
||||||
const [, quotes] = await testContract
|
const [, quotes] = await testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
|
||||||
SELL_SOURCES.map(n => allSources[n]),
|
|
||||||
sampleAmounts,
|
|
||||||
)
|
|
||||||
.callAsync();
|
.callAsync();
|
||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
expect(quotes).to.deep.eq(expectedQuotes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if no orders are passed in', async () => {
|
it('throws if no orders are passed in', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
.queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
||||||
.callAsync();
|
.callAsync();
|
||||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||||
});
|
});
|
||||||
@@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with an unsupported source', async () => {
|
it('throws with an unsupported source', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
ORDERS,
|
||||||
|
SIGNATURES,
|
||||||
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
|
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||||
getSampleAmounts(TAKER_TOKEN),
|
getSampleAmounts(TAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with non-ERC20 maker asset data', async () => {
|
it('throws with non-ERC20 maker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
SELL_SOURCES.map(n => allSources[n]),
|
SELL_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(TAKER_TOKEN),
|
getSampleAmounts(TAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with non-ERC20 taker asset data', async () => {
|
it('throws with non-ERC20 taker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
SELL_SOURCES.map(n => allSources[n]),
|
SELL_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(TAKER_TOKEN),
|
getSampleAmounts(TAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with invalid maker asset data', async () => {
|
it('throws with invalid maker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
makerAssetData: INVALID_ASSET_DATA,
|
makerAssetData: INVALID_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
SELL_SOURCES.map(n => allSources[n]),
|
SELL_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(TAKER_TOKEN),
|
getSampleAmounts(TAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with invalid taker asset data', async () => {
|
it('throws with invalid taker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleSells(
|
.queryOrdersAndSampleSells(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
takerAssetData: INVALID_ASSET_DATA,
|
takerAssetData: INVALID_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
SELL_SOURCES.map(n => allSources[n]),
|
SELL_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(TAKER_TOKEN),
|
getSampleAmounts(TAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('queryOrdersAndSampleBuys()', () => {
|
describe('queryOrdersAndSampleBuys()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||||
const TAKER_TOKEN = randomAddress();
|
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
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 takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
|
||||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
|
||||||
const [orderInfos] = await testContract
|
const [orderInfos] = await testContract
|
||||||
.queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return quotes for all sources', async () => {
|
it('can return quotes for all sources', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
|
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
|
||||||
const [, quotes] = await testContract
|
const [, quotes] = await testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
|
||||||
BUY_SOURCES.map(n => allSources[n]),
|
|
||||||
sampleAmounts,
|
|
||||||
)
|
|
||||||
.callAsync();
|
.callAsync();
|
||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
expect(quotes).to.deep.eq(expectedQuotes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if no orders are passed in', async () => {
|
it('throws if no orders are passed in', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
.queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
||||||
.callAsync();
|
.callAsync();
|
||||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||||
});
|
});
|
||||||
@@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with an unsupported source', async () => {
|
it('throws with an unsupported source', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
ORDERS,
|
||||||
|
SIGNATURES,
|
||||||
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
|
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
const sources = [...BUY_SOURCES, 'Kyber'];
|
const sources = [...BUY_SOURCES, 'Kyber'];
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
ORDERS,
|
||||||
|
SIGNATURES,
|
||||||
sources.map(n => allSources[n]),
|
sources.map(n => allSources[n]),
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with non-ERC20 maker asset data', async () => {
|
it('throws with non-ERC20 maker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
BUY_SOURCES.map(n => allSources[n]),
|
BUY_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with non-ERC20 taker asset data', async () => {
|
it('throws with non-ERC20 taker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
BUY_SOURCES.map(n => allSources[n]),
|
BUY_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with invalid maker asset data', async () => {
|
it('throws with invalid maker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
makerAssetData: INVALID_ASSET_DATA,
|
makerAssetData: INVALID_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
BUY_SOURCES.map(n => allSources[n]),
|
BUY_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
it('throws with invalid taker asset data', async () => {
|
it('throws with invalid taker asset data', async () => {
|
||||||
const tx = testContract
|
const tx = testContract
|
||||||
.queryOrdersAndSampleBuys(
|
.queryOrdersAndSampleBuys(
|
||||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
ORDERS.map(o => ({
|
||||||
...o,
|
...o,
|
||||||
takerAssetData: INVALID_ASSET_DATA,
|
takerAssetData: INVALID_ASSET_DATA,
|
||||||
})),
|
})),
|
||||||
|
SIGNATURES,
|
||||||
BUY_SOURCES.map(n => allSources[n]),
|
BUY_SOURCES.map(n => allSources[n]),
|
||||||
getSampleAmounts(MAKER_TOKEN),
|
getSampleAmounts(MAKER_TOKEN),
|
||||||
)
|
)
|
||||||
@@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('sampleSells()', () => {
|
describe('sampleSells()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('sampleBuys()', () => {
|
describe('sampleBuys()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sampleSellsFromKyberNetwork()', () => {
|
blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq([]);
|
expect(quotes).to.deep.eq([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return many quotes', async () => {
|
it('can quote token - token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||||
const quotes = await testContract
|
const quotes = await testContract
|
||||||
@@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote token -> ETH', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
||||||
@@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote ETH -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||||
@@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
.callAsync();
|
.callAsync();
|
||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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()', () => {
|
blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq([]);
|
expect(quotes).to.deep.eq([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return many quotes', async () => {
|
it('can quote token -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||||
const quotes = await testContract
|
const quotes = await testContract
|
||||||
@@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote token -> ETH', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||||
@@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote ETH -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||||
@@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
.callAsync();
|
.callAsync();
|
||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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()', () => {
|
blockchainTests.resets('sampleBuysFromEth2Dai()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq([]);
|
expect(quotes).to.deep.eq([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return many quotes', async () => {
|
it('can quote token -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||||
const quotes = await testContract
|
const quotes = await testContract
|
||||||
@@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote token -> ETH', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||||
@@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote ETH -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||||
@@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
.callAsync();
|
.callAsync();
|
||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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()', () => {
|
blockchainTests.resets('sampleSellsFromUniswap()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq([]);
|
expect(quotes).to.deep.eq([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return many quotes', async () => {
|
it('can quote token -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||||
const quotes = await testContract
|
const quotes = await testContract
|
||||||
@@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote token -> ETH', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||||
@@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote ETH -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||||
@@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
expect(quotes).to.deep.eq(expectedQuotes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if no exchange exists for the maker token', async () => {
|
it('returns zero if ETH -> token fails', async () => {
|
||||||
const nonExistantToken = randomAddress();
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
const tx = testContract
|
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||||
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
await enableFailTriggerAsync();
|
||||||
|
const quotes = await testContract
|
||||||
|
.sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||||
.callAsync();
|
.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 nonExistantToken = randomAddress();
|
||||||
const tx = testContract
|
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||||
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||||
|
const quotes = await testContract
|
||||||
|
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||||
.callAsync();
|
.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()', () => {
|
blockchainTests.resets('sampleBuysFromUniswap()', () => {
|
||||||
const MAKER_TOKEN = randomAddress();
|
|
||||||
const TAKER_TOKEN = randomAddress();
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||||
});
|
});
|
||||||
@@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq([]);
|
expect(quotes).to.deep.eq([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can return many quotes', async () => {
|
it('can quote token -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||||
const quotes = await testContract
|
const quotes = await testContract
|
||||||
@@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote token -> ETH', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||||
@@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
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 () => {
|
it('can quote ETH -> token', async () => {
|
||||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||||
@@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
|||||||
expect(quotes).to.deep.eq(expectedQuotes);
|
expect(quotes).to.deep.eq(expectedQuotes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if no exchange exists for the maker token', async () => {
|
it('returns zero if ETH -> token fails', async () => {
|
||||||
const nonExistantToken = randomAddress();
|
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||||
const tx = testContract
|
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||||
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
await enableFailTriggerAsync();
|
||||||
|
const quotes = await testContract
|
||||||
|
.sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||||
.callAsync();
|
.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 nonExistantToken = randomAddress();
|
||||||
const tx = testContract
|
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||||
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||||
|
const quotes = await testContract
|
||||||
|
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||||
.callAsync();
|
.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.
|
* 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/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_erc20_bridge_sampler';
|
||||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||||
export * from '../test/generated-wrappers/i_kyber_network';
|
export * from '../test/generated-wrappers/i_kyber_network';
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"generated-artifacts/ERC20BridgeSampler.json",
|
"generated-artifacts/ERC20BridgeSampler.json",
|
||||||
"generated-artifacts/IERC20BridgeSampler.json",
|
"generated-artifacts/IERC20BridgeSampler.json",
|
||||||
"test/generated-artifacts/DeploymentConstants.json",
|
|
||||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||||
|
"test/generated-artifacts/IDevUtils.json",
|
||||||
"test/generated-artifacts/IERC20BridgeSampler.json",
|
"test/generated-artifacts/IERC20BridgeSampler.json",
|
||||||
"test/generated-artifacts/IEth2Dai.json",
|
"test/generated-artifacts/IEth2Dai.json",
|
||||||
"test/generated-artifacts/IKyberNetwork.json",
|
"test/generated-artifacts/IKyberNetwork.json",
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "3.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v3.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v3.0.1 - _December 9, 2019_
|
## v3.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-erc20",
|
"name": "@0x/contracts-erc20",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -51,18 +51,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1"
|
"@0x/base-contract": "^6.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@@ -3,6 +3,9 @@ export {
|
|||||||
DummyMultipleReturnERC20TokenContract,
|
DummyMultipleReturnERC20TokenContract,
|
||||||
DummyNoReturnERC20TokenContract,
|
DummyNoReturnERC20TokenContract,
|
||||||
WETH9Contract,
|
WETH9Contract,
|
||||||
|
WETH9Events,
|
||||||
|
WETH9DepositEventArgs,
|
||||||
|
WETH9TransferEventArgs,
|
||||||
ZRXTokenContract,
|
ZRXTokenContract,
|
||||||
DummyERC20TokenTransferEventArgs,
|
DummyERC20TokenTransferEventArgs,
|
||||||
ERC20TokenEventArgs,
|
ERC20TokenEventArgs,
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "3.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v3.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v3.0.1 - _December 9, 2019_
|
## v3.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-erc721",
|
"name": "@0x/contracts-erc721",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,18 +52,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1"
|
"@0x/base-contract": "^6.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "4.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v4.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v4.0.1 - _December 9, 2019_
|
## v4.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-exchange-forwarder",
|
"name": "@0x/contracts-exchange-forwarder",
|
||||||
"version": "4.0.1",
|
"version": "4.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,24 +52,24 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-erc721": "^3.0.1",
|
"@0x/contracts-erc721": "^3.0.2",
|
||||||
"@0x/contracts-exchange": "^3.0.1",
|
"@0x/contracts-exchange": "^3.0.2",
|
||||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"ethereum-types": "^3.0.0"
|
"ethereum-types": "^3.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "4.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v4.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v4.0.1 - _December 9, 2019_
|
## v4.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-exchange-libs",
|
"name": "@0x/contracts-exchange-libs",
|
||||||
"version": "4.0.1",
|
"version": "4.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,15 +52,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/libs/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/libs/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/subproviders": "^6.0.1",
|
"@0x/subproviders": "^6.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -81,12 +81,12 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"ethereum-types": "^3.0.0"
|
"ethereum-types": "^3.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -103,7 +103,7 @@ export function calculateFillResults(
|
|||||||
order.takerAssetAmount,
|
order.takerAssetAmount,
|
||||||
order.makerAssetAmount,
|
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);
|
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
|
||||||
return {
|
return {
|
||||||
makerAssetFilledAmount,
|
makerAssetFilledAmount,
|
||||||
@@ -113,3 +113,30 @@ export function calculateFillResults(
|
|||||||
protocolFeePaid: safeMul(protocolFeeMultiplier, gasPrice),
|
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,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "3.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v3.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v3.0.1 - _December 9, 2019_
|
## v3.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-exchange",
|
"name": "@0x/contracts-exchange",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,21 +52,21 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-multisig": "^4.0.1",
|
"@0x/contracts-multisig": "^4.0.2",
|
||||||
"@0x/contracts-staking": "^2.0.1",
|
"@0x/contracts-staking": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -88,13 +88,13 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-erc1155": "^2.0.1",
|
"@0x/contracts-erc1155": "^2.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-erc721": "^3.0.1",
|
"@0x/contracts-erc721": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -107,23 +107,6 @@ describe('Reference functions', () => {
|
|||||||
).to.throw(expectedError.message);
|
).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', () => {
|
it('reverts if `order.takerAssetAmount` is 0', () => {
|
||||||
const order = makeOrder({
|
const order = makeOrder({
|
||||||
makerAssetAmount: ONE_ETHER,
|
makerAssetAmount: ONE_ETHER,
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "5.1.1",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v5.1.1 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v5.1.0 - _December 9, 2019_
|
## v5.1.0 - _December 9, 2019_
|
||||||
|
|
||||||
* Export function `encodeDutchAuctionAssetData` (#2373)
|
* Export function `encodeDutchAuctionAssetData` (#2373)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-extensions",
|
"name": "@0x/contracts-extensions",
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -52,24 +52,24 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-erc721": "^3.0.1",
|
"@0x/contracts-erc721": "^3.0.2",
|
||||||
"@0x/contracts-exchange": "^3.0.1",
|
"@0x/contracts-exchange": "^3.0.2",
|
||||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/order-utils": "^10.0.1",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -91,8 +91,8 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"ethereum-types": "^3.0.0"
|
"ethereum-types": "^3.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "2.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v2.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v2.0.1 - _December 9, 2019_
|
## v2.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-integrations",
|
"name": "@0x/contracts-integrations",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
"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 100000 --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",
|
"compile": "sol-compiler",
|
||||||
"watch": "sol-compiler -w",
|
"watch": "sol-compiler -w",
|
||||||
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
|
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
|
||||||
@@ -50,25 +51,27 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contract-addresses": "^4.0.0",
|
"@0x/contract-addresses": "^4.1.0",
|
||||||
"@0x/contracts-coordinator": "^3.0.1",
|
"@0x/contract-wrappers": "^13.2.0",
|
||||||
"@0x/contracts-dev-utils": "^1.0.1",
|
"@0x/contracts-coordinator": "^3.0.2",
|
||||||
"@0x/contracts-exchange-forwarder": "^4.0.1",
|
"@0x/contracts-dev-utils": "^1.0.2",
|
||||||
"@0x/contracts-exchange-libs": "^4.0.1",
|
"@0x/contracts-exchange-forwarder": "^4.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-exchange-libs": "^4.0.2",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/coordinator-server": "^1.0.4",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/coordinator-server": "^1.0.5",
|
||||||
"@0x/migrations": "^5.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/order-utils": "^10.0.0",
|
"@0x/migrations": "^5.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/order-utils": "^10.0.1",
|
||||||
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@azure/core-asynciterator-polyfill": "^1.0.0",
|
"@azure/core-asynciterator-polyfill": "^1.0.0",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
"@types/seedrandom": "^2.4.28",
|
||||||
"chai": "^4.0.1",
|
"chai": "^4.0.1",
|
||||||
"chai-as-promised": "^7.1.0",
|
"chai-as-promised": "^7.1.0",
|
||||||
"chai-bignumber": "^3.0.0",
|
"chai-bignumber": "^3.0.0",
|
||||||
@@ -78,6 +81,7 @@
|
|||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.0",
|
||||||
"nock": "^10.0.6",
|
"nock": "^10.0.6",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
|
"seedrandom": "^3.0.5",
|
||||||
"shx": "^0.2.2",
|
"shx": "^0.2.2",
|
||||||
"solhint": "^1.4.1",
|
"solhint": "^1.4.1",
|
||||||
"truffle": "^5.0.32",
|
"truffle": "^5.0.32",
|
||||||
@@ -85,18 +89,18 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-erc1155": "^2.0.1",
|
"@0x/contracts-erc1155": "^2.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-erc721": "^3.0.1",
|
"@0x/contracts-erc721": "^3.0.2",
|
||||||
"@0x/contracts-exchange": "^3.0.1",
|
"@0x/contracts-exchange": "^3.0.2",
|
||||||
"@0x/contracts-multisig": "^4.0.1",
|
"@0x/contracts-multisig": "^4.0.2",
|
||||||
"@0x/contracts-staking": "^2.0.1",
|
"@0x/contracts-staking": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"ethereum-types": "^3.0.0",
|
"ethereum-types": "^3.0.0",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
|
@@ -19,7 +19,7 @@ const coordinatorEndpoint = 'http://localhost:';
|
|||||||
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
// tslint:disable:custom-no-magic-numbers
|
||||||
blockchainTests.skip('Coordinator Client', env => {
|
blockchainTests('Coordinator Client', env => {
|
||||||
const takerTokenFillAmount = new BigNumber(0);
|
const takerTokenFillAmount = new BigNumber(0);
|
||||||
const chainId = 1337;
|
const chainId = 1337;
|
||||||
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
||||||
|
@@ -33,7 +33,7 @@ import { AssetProxyId } from '@0x/types';
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TxData } from 'ethereum-types';
|
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
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests('Deployment and Configuration End to End Tests', env => {
|
blockchainTests('Deployment and Configuration End to End Tests', env => {
|
||||||
|
@@ -15,7 +15,6 @@ export type Constructor<T = {}> = new (...args: any[]) => T;
|
|||||||
export interface ActorConfig {
|
export interface ActorConfig {
|
||||||
name?: string;
|
name?: string;
|
||||||
deployment: DeploymentManager;
|
deployment: DeploymentManager;
|
||||||
simulationEnvironment?: SimulationEnvironment;
|
|
||||||
[mixinProperty: string]: any;
|
[mixinProperty: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +24,11 @@ export class Actor {
|
|||||||
public readonly name: string;
|
public readonly name: string;
|
||||||
public readonly privateKey: Buffer;
|
public readonly privateKey: Buffer;
|
||||||
public readonly deployment: DeploymentManager;
|
public readonly deployment: DeploymentManager;
|
||||||
public readonly simulationEnvironment?: SimulationEnvironment;
|
public simulationEnvironment?: SimulationEnvironment;
|
||||||
public simulationActions: {
|
public simulationActions: {
|
||||||
[action: string]: AsyncIterableIterator<AssertionResult | void>;
|
[action: string]: AsyncIterableIterator<AssertionResult | void>;
|
||||||
} = {};
|
} = {};
|
||||||
|
public mixins: string[] = [];
|
||||||
protected readonly _transactionFactory: TransactionFactory;
|
protected readonly _transactionFactory: TransactionFactory;
|
||||||
|
|
||||||
public static reset(): void {
|
public static reset(): void {
|
||||||
@@ -47,7 +47,6 @@ export class Actor {
|
|||||||
this.name = config.name || this.address;
|
this.name = config.name || this.address;
|
||||||
this.deployment = config.deployment;
|
this.deployment = config.deployment;
|
||||||
this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)];
|
this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)];
|
||||||
this.simulationEnvironment = config.simulationEnvironment;
|
|
||||||
this._transactionFactory = new TransactionFactory(
|
this._transactionFactory = new TransactionFactory(
|
||||||
this.privateKey,
|
this.privateKey,
|
||||||
config.deployment.exchange.address,
|
config.deployment.exchange.address,
|
||||||
@@ -123,7 +122,6 @@ export class Actor {
|
|||||||
if (logs.length !== 1) {
|
if (logs.length !== 1) {
|
||||||
throw new Error('Invalid number of `TransferSingle` logs');
|
throw new Error('Invalid number of `TransferSingle` logs');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = logs[0];
|
const { id } = logs[0];
|
||||||
|
|
||||||
// Mint the token
|
// Mint the token
|
||||||
|
@@ -18,7 +18,7 @@ export interface FeeRecipientInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with fee recipients within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with fee recipients within the 0x ecosystem.
|
||||||
* As of writing, the only extra functionality provided is signing Coordinator approvals.
|
* As of writing, the only extra functionality provided is signing Coordinator approvals.
|
||||||
*/
|
*/
|
||||||
export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<FeeRecipientInterface> {
|
export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<FeeRecipientInterface> {
|
||||||
@@ -35,6 +35,7 @@ export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase): TBase
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('FeeRecipient');
|
||||||
|
|
||||||
const { verifyingContract } = args[0] as FeeRecipientConfig;
|
const { verifyingContract } = args[0] as FeeRecipientConfig;
|
||||||
if (verifyingContract !== undefined) {
|
if (verifyingContract !== undefined) {
|
||||||
|
@@ -1,8 +1,17 @@
|
|||||||
import { IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs, TestStakingEvents } from '@0x/contracts-staking';
|
import {
|
||||||
|
AggregatedStats,
|
||||||
|
IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
|
||||||
|
TestStakingEvents,
|
||||||
|
} from '@0x/contracts-staking';
|
||||||
import { filterLogsToArguments, web3Wrapper } from '@0x/contracts-test-utils';
|
import { filterLogsToArguments, web3Wrapper } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { BlockParamLiteral, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
import { BlockParamLiteral, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { validEndEpochAssertion } from '../assertions/endEpoch';
|
||||||
|
import { validFinalizePoolAssertion } from '../assertions/finalizePool';
|
||||||
|
import { AssertionResult } from '../assertions/function_assertion';
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
import { Actor, Constructor } from './base';
|
import { Actor, Constructor } from './base';
|
||||||
|
|
||||||
export interface KeeperInterface {
|
export interface KeeperInterface {
|
||||||
@@ -11,7 +20,7 @@ export interface KeeperInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with keepers within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with keepers within the 0x ecosystem.
|
||||||
* This includes ending epochs sand finalizing pools in the staking system.
|
* This includes ending epochs sand finalizing pools in the staking system.
|
||||||
*/
|
*/
|
||||||
export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<KeeperInterface> {
|
export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<KeeperInterface> {
|
||||||
@@ -27,6 +36,14 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('Keeper');
|
||||||
|
|
||||||
|
// Register this mixin's assertion generators
|
||||||
|
this.actor.simulationActions = {
|
||||||
|
...this.actor.simulationActions,
|
||||||
|
validFinalizePool: this._validFinalizePool(),
|
||||||
|
validEndEpoch: this._validEndEpoch(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,14 +52,7 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
public async endEpochAsync(shouldFastForward: boolean = true): Promise<TransactionReceiptWithDecodedLogs> {
|
public async endEpochAsync(shouldFastForward: boolean = true): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
const { stakingWrapper } = this.actor.deployment.staking;
|
const { stakingWrapper } = this.actor.deployment.staking;
|
||||||
if (shouldFastForward) {
|
if (shouldFastForward) {
|
||||||
// increase timestamp of next block by how many seconds we need to
|
await this._fastForwardToNextEpochAsync();
|
||||||
// get to the next epoch.
|
|
||||||
const epochEndTime = await stakingWrapper.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
|
|
||||||
const lastBlockTime = await web3Wrapper.getBlockTimestampAsync('latest');
|
|
||||||
const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
|
|
||||||
await web3Wrapper.increaseTimeAsync(dt);
|
|
||||||
// mine next block
|
|
||||||
await web3Wrapper.mineBlockAsync();
|
|
||||||
}
|
}
|
||||||
return stakingWrapper.endEpoch().awaitTransactionSuccessAsync({ from: this.actor.address });
|
return stakingWrapper.endEpoch().awaitTransactionSuccessAsync({ from: this.actor.address });
|
||||||
}
|
}
|
||||||
@@ -75,6 +85,51 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async *_validFinalizePool(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
|
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||||
|
const assertion = validFinalizePoolAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
|
||||||
|
while (true) {
|
||||||
|
// Finalize a random pool, or do nothing if there are no pools in the simulation yet.
|
||||||
|
const poolId = Pseudorandom.sample(Object.keys(stakingPools));
|
||||||
|
if (poolId === undefined) {
|
||||||
|
yield;
|
||||||
|
} else {
|
||||||
|
yield assertion.executeAsync([poolId], { from: this.actor.address });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async *_validEndEpoch(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
|
const assertion = validEndEpochAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
|
||||||
|
const { stakingWrapper } = this.actor.deployment.staking;
|
||||||
|
while (true) {
|
||||||
|
const { currentEpoch } = this.actor.simulationEnvironment!;
|
||||||
|
const aggregatedStats = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch.minus(1)).callAsync(),
|
||||||
|
);
|
||||||
|
if (aggregatedStats.numPoolsToFinalize.isGreaterThan(0)) {
|
||||||
|
// Can't end the epoch if the previous epoch is not fully finalized.
|
||||||
|
yield;
|
||||||
|
} else {
|
||||||
|
await this._fastForwardToNextEpochAsync();
|
||||||
|
yield assertion.executeAsync([], { from: this.actor.address });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fastForwardToNextEpochAsync(): Promise<void> {
|
||||||
|
const { stakingWrapper } = this.actor.deployment.staking;
|
||||||
|
|
||||||
|
// increase timestamp of next block by how many seconds we need to
|
||||||
|
// get to the next epoch.
|
||||||
|
const epochEndTime = await stakingWrapper.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
|
||||||
|
const lastBlockTime = await web3Wrapper.getBlockTimestampAsync('latest');
|
||||||
|
const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
|
||||||
|
await web3Wrapper.increaseTimeAsync(dt);
|
||||||
|
// mine next block
|
||||||
|
await web3Wrapper.mineBlockAsync();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
|
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||||
import { constants, OrderFactory } from '@0x/contracts-test-utils';
|
import { constants, OrderFactory } from '@0x/contracts-test-utils';
|
||||||
import { Order, SignedOrder } from '@0x/types';
|
import { Order, SignedOrder } from '@0x/types';
|
||||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { AssertionResult } from '../assertions/function_assertion';
|
import { AssertionResult } from '../assertions/function_assertion';
|
||||||
import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool';
|
import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool';
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
import { Actor, ActorConfig, Constructor } from './base';
|
import { Actor, ActorConfig, Constructor } from './base';
|
||||||
|
|
||||||
@@ -18,10 +19,11 @@ export interface MakerInterface {
|
|||||||
signOrderAsync: (customOrderParams?: Partial<Order>) => Promise<SignedOrder>;
|
signOrderAsync: (customOrderParams?: Partial<Order>) => Promise<SignedOrder>;
|
||||||
cancelOrderAsync: (order: SignedOrder) => Promise<TransactionReceiptWithDecodedLogs>;
|
cancelOrderAsync: (order: SignedOrder) => Promise<TransactionReceiptWithDecodedLogs>;
|
||||||
joinStakingPoolAsync: (poolId: string) => Promise<TransactionReceiptWithDecodedLogs>;
|
joinStakingPoolAsync: (poolId: string) => Promise<TransactionReceiptWithDecodedLogs>;
|
||||||
|
createFillableOrderAsync: (taker: Actor) => Promise<SignedOrder>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with makers within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with makers within the 0x ecosystem.
|
||||||
* This includes signing and canceling orders, as well as joining a staking pool as a maker.
|
* This includes signing and canceling orders, as well as joining a staking pool as a maker.
|
||||||
*/
|
*/
|
||||||
export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<MakerInterface> {
|
export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<MakerInterface> {
|
||||||
@@ -39,6 +41,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('Maker');
|
||||||
|
|
||||||
const { orderConfig } = args[0] as MakerConfig;
|
const { orderConfig } = args[0] as MakerConfig;
|
||||||
const defaultOrderParams = {
|
const defaultOrderParams = {
|
||||||
@@ -84,13 +87,64 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async createFillableOrderAsync(taker: Actor): Promise<SignedOrder> {
|
||||||
|
const { actors, balanceStore } = this.actor.simulationEnvironment!;
|
||||||
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
|
|
||||||
|
// Choose the assets for the order
|
||||||
|
const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
|
||||||
|
this.actor.deployment.tokens.erc20,
|
||||||
|
4, // tslint:disable-line:custom-no-magic-numbers
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maker and taker set balances/allowances to guarantee that the fill succeeds.
|
||||||
|
// Amounts are chosen to be within each actor's balance (divided by 2, in case
|
||||||
|
// e.g. makerAsset = makerFeeAsset)
|
||||||
|
const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all(
|
||||||
|
[
|
||||||
|
[this.actor, makerToken],
|
||||||
|
[this.actor, makerFeeToken],
|
||||||
|
[taker, takerToken],
|
||||||
|
[taker, takerFeeToken],
|
||||||
|
].map(async ([owner, token]) => {
|
||||||
|
let balance = balanceStore.balances.erc20[owner.address][token.address];
|
||||||
|
await (owner as Actor).configureERC20TokenAsync(token as DummyERC20TokenContract);
|
||||||
|
balance = balanceStore.balances.erc20[owner.address][token.address] =
|
||||||
|
constants.INITIAL_ERC20_BALANCE;
|
||||||
|
return Pseudorandom.integer(balance.dividedToIntegerBy(2));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Encode asset data
|
||||||
|
const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [
|
||||||
|
makerToken,
|
||||||
|
makerFeeToken,
|
||||||
|
takerToken,
|
||||||
|
takerFeeToken,
|
||||||
|
].map(token =>
|
||||||
|
this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maker signs the order
|
||||||
|
return this.signOrderAsync({
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData,
|
||||||
|
makerFeeAssetData,
|
||||||
|
takerFeeAssetData,
|
||||||
|
makerAssetAmount,
|
||||||
|
takerAssetAmount,
|
||||||
|
makerFee,
|
||||||
|
takerFee,
|
||||||
|
feeRecipientAddress: Pseudorandom.sample(actors)!.address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async *_validJoinStakingPool(): AsyncIterableIterator<AssertionResult | void> {
|
private async *_validJoinStakingPool(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
const { stakingPools } = this.actor.simulationEnvironment!;
|
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validJoinStakingPoolAssertion(this.actor.deployment);
|
const assertion = validJoinStakingPoolAssertion(this.actor.deployment);
|
||||||
while (true) {
|
while (true) {
|
||||||
const poolId = _.sample(Object.keys(stakingPools));
|
const poolId = Pseudorandom.sample(Object.keys(stakingPools));
|
||||||
if (poolId === undefined) {
|
if (poolId === undefined) {
|
||||||
yield undefined;
|
yield;
|
||||||
} else {
|
} else {
|
||||||
yield assertion.executeAsync([poolId], { from: this.actor.address });
|
yield assertion.executeAsync([poolId], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { constants, StakingPoolById } from '@0x/contracts-staking';
|
import { constants, StakingPoolById } from '@0x/contracts-staking';
|
||||||
import { getRandomInteger } from '@0x/contracts-test-utils';
|
|
||||||
import '@azure/core-asynciterator-polyfill';
|
import '@azure/core-asynciterator-polyfill';
|
||||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@@ -7,6 +6,7 @@ import * as _ from 'lodash';
|
|||||||
import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
|
import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
|
||||||
import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare';
|
import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare';
|
||||||
import { AssertionResult } from '../assertions/function_assertion';
|
import { AssertionResult } from '../assertions/function_assertion';
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
import { Actor, Constructor } from './base';
|
import { Actor, Constructor } from './base';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export interface PoolOperatorInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with pool operators within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with pool operators within the 0x ecosystem.
|
||||||
* This includes creating staking pools and decreasing the operator share of a pool.
|
* This includes creating staking pools and decreasing the operator share of a pool.
|
||||||
*/
|
*/
|
||||||
export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<PoolOperatorInterface> {
|
export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<PoolOperatorInterface> {
|
||||||
@@ -35,6 +35,7 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('PoolOperator');
|
||||||
|
|
||||||
// Register this mixin's assertion generators
|
// Register this mixin's assertion generators
|
||||||
this.actor.simulationActions = {
|
this.actor.simulationActions = {
|
||||||
@@ -80,10 +81,9 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
|
private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
|
||||||
const { stakingPools } = this.actor.simulationEnvironment!;
|
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
|
||||||
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const operatorShare = getRandomInteger(0, constants.PPM).toNumber();
|
const operatorShare = Pseudorandom.integer(constants.PPM).toNumber();
|
||||||
yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
|
yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,11 +92,11 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
|
|||||||
const { stakingPools } = this.actor.simulationEnvironment!;
|
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
|
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
|
||||||
while (true) {
|
while (true) {
|
||||||
const poolId = _.sample(this._getOperatorPoolIds(stakingPools));
|
const poolId = Pseudorandom.sample(this._getOperatorPoolIds(stakingPools));
|
||||||
if (poolId === undefined) {
|
if (poolId === undefined) {
|
||||||
yield undefined;
|
yield undefined;
|
||||||
} else {
|
} else {
|
||||||
const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare).toNumber();
|
const operatorShare = Pseudorandom.integer(stakingPools[poolId].operatorShare).toNumber();
|
||||||
yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
|
yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
||||||
import { getRandomInteger } from '@0x/contracts-test-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import '@azure/core-asynciterator-polyfill';
|
import '@azure/core-asynciterator-polyfill';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@@ -8,6 +7,8 @@ import { AssertionResult } from '../assertions/function_assertion';
|
|||||||
import { validMoveStakeAssertion } from '../assertions/moveStake';
|
import { validMoveStakeAssertion } from '../assertions/moveStake';
|
||||||
import { validStakeAssertion } from '../assertions/stake';
|
import { validStakeAssertion } from '../assertions/stake';
|
||||||
import { validUnstakeAssertion } from '../assertions/unstake';
|
import { validUnstakeAssertion } from '../assertions/unstake';
|
||||||
|
import { validWithdrawDelegatorRewardsAssertion } from '../assertions/withdrawDelegatorRewards';
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
import { Actor, Constructor } from './base';
|
import { Actor, Constructor } from './base';
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ export interface StakerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with stakers within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with stakers within the 0x ecosystem.
|
||||||
* This includes staking ZRX (and optionally delegating it to a specific pool).
|
* This includes staking ZRX (and optionally delegating it to a specific pool).
|
||||||
*/
|
*/
|
||||||
export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<StakerInterface> {
|
export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<StakerInterface> {
|
||||||
@@ -33,6 +34,8 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('Staker');
|
||||||
|
|
||||||
this.stake = {
|
this.stake = {
|
||||||
[StakeStatus.Undelegated]: new StoredBalance(),
|
[StakeStatus.Undelegated]: new StoredBalance(),
|
||||||
[StakeStatus.Delegated]: { total: new StoredBalance() },
|
[StakeStatus.Delegated]: { total: new StoredBalance() },
|
||||||
@@ -44,6 +47,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
validStake: this._validStake(),
|
validStake: this._validStake(),
|
||||||
validUnstake: this._validUnstake(),
|
validUnstake: this._validUnstake(),
|
||||||
validMoveStake: this._validMoveStake(),
|
validMoveStake: this._validMoveStake(),
|
||||||
|
validWithdrawDelegatorRewards: this._validWithdrawDelegatorRewards(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,21 +73,21 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
|
|
||||||
private async *_validStake(): AsyncIterableIterator<AssertionResult> {
|
private async *_validStake(): AsyncIterableIterator<AssertionResult> {
|
||||||
const { zrx } = this.actor.deployment.tokens;
|
const { zrx } = this.actor.deployment.tokens;
|
||||||
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
|
const { deployment, balanceStore } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake);
|
const assertion = validStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await balanceStore.updateErc20BalancesAsync();
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
|
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
|
||||||
const amount = getRandomInteger(0, zrxBalance);
|
const amount = Pseudorandom.integer(zrxBalance);
|
||||||
yield assertion.executeAsync([amount], { from: this.actor.address });
|
yield assertion.executeAsync([amount], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
|
private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
|
||||||
const { stakingWrapper } = this.actor.deployment.staking;
|
const { stakingWrapper } = this.actor.deployment.staking;
|
||||||
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
|
const { deployment, balanceStore } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake);
|
const assertion = validUnstakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await balanceStore.updateErc20BalancesAsync();
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
@@ -94,39 +98,71 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
|||||||
undelegatedStake.currentEpochBalance,
|
undelegatedStake.currentEpochBalance,
|
||||||
undelegatedStake.nextEpochBalance,
|
undelegatedStake.nextEpochBalance,
|
||||||
);
|
);
|
||||||
const amount = getRandomInteger(0, withdrawableStake);
|
const amount = Pseudorandom.integer(withdrawableStake);
|
||||||
yield assertion.executeAsync([amount], { from: this.actor.address });
|
yield assertion.executeAsync([amount], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
|
private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
|
||||||
const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!;
|
const { deployment, stakingPools } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools);
|
const assertion = validMoveStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])));
|
const { currentEpoch } = this.actor.simulationEnvironment!;
|
||||||
|
// Pick a random pool that this staker has delegated to (undefined if no such pools exist)
|
||||||
|
const fromPoolId = Pseudorandom.sample(
|
||||||
|
Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])),
|
||||||
|
);
|
||||||
|
// The `from` status must be Undelegated if the staker isn't delegated to any pools
|
||||||
|
// at the moment, or if the chosen pool is unfinalized
|
||||||
const fromStatus =
|
const fromStatus =
|
||||||
fromPoolId === undefined
|
fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
|
||||||
? StakeStatus.Undelegated
|
? StakeStatus.Undelegated
|
||||||
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
|
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
|
||||||
const from = new StakeInfo(fromStatus, fromPoolId);
|
const from = new StakeInfo(fromStatus, fromPoolId);
|
||||||
|
|
||||||
const toPoolId = _.sample(Object.keys(stakingPools));
|
// Pick a random pool to move the stake to
|
||||||
|
const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
|
||||||
|
// The `from` status must be Undelegated if no pools exist in the simulation yet,
|
||||||
|
// or if the chosen pool is unfinalized
|
||||||
const toStatus =
|
const toStatus =
|
||||||
toPoolId === undefined
|
toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
|
||||||
? StakeStatus.Undelegated
|
? StakeStatus.Undelegated
|
||||||
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
|
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
|
||||||
const to = new StakeInfo(toStatus, toPoolId);
|
const to = new StakeInfo(toStatus, toPoolId);
|
||||||
|
|
||||||
|
// The next epoch balance of the `from` stake is the amount that can be moved
|
||||||
const moveableStake =
|
const moveableStake =
|
||||||
from.status === StakeStatus.Undelegated
|
from.status === StakeStatus.Undelegated
|
||||||
? this.stake[StakeStatus.Undelegated].nextEpochBalance
|
? this.stake[StakeStatus.Undelegated].nextEpochBalance
|
||||||
: this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance;
|
: this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance;
|
||||||
const amount = getRandomInteger(0, moveableStake);
|
const amount = Pseudorandom.integer(moveableStake);
|
||||||
|
|
||||||
yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
|
yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async *_validWithdrawDelegatorRewards(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
|
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||||
|
const assertion = validWithdrawDelegatorRewardsAssertion(
|
||||||
|
this.actor.deployment,
|
||||||
|
this.actor.simulationEnvironment!,
|
||||||
|
);
|
||||||
|
while (true) {
|
||||||
|
const prevEpoch = this.actor.simulationEnvironment!.currentEpoch.minus(1);
|
||||||
|
// Pick a finalized pool
|
||||||
|
const poolId = Pseudorandom.sample(
|
||||||
|
Object.keys(stakingPools).filter(id =>
|
||||||
|
stakingPools[id].lastFinalized.isGreaterThanOrEqualTo(prevEpoch),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (poolId === undefined) {
|
||||||
|
yield;
|
||||||
|
} else {
|
||||||
|
yield assertion.executeAsync([poolId], { from: this.actor.address });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
import { constants, getRandomInteger } from '@0x/contracts-test-utils';
|
|
||||||
import { SignedOrder } from '@0x/types';
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder';
|
import { validFillOrderAssertion } from '../assertions/fillOrder';
|
||||||
import { AssertionResult } from '../assertions/function_assertion';
|
import { AssertionResult } from '../assertions/function_assertion';
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
import { Actor, Constructor } from './base';
|
import { Actor, Constructor } from './base';
|
||||||
|
import { Maker } from './maker';
|
||||||
|
import { filterActorsByRole } from './utils';
|
||||||
|
|
||||||
export interface TakerInterface {
|
export interface TakerInterface {
|
||||||
fillOrderAsync: (
|
fillOrderAsync: (
|
||||||
@@ -19,7 +20,7 @@ export interface TakerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin encapsulates functionaltiy associated with takers within the 0x ecosystem.
|
* This mixin encapsulates functionality associated with takers within the 0x ecosystem.
|
||||||
* As of writing, the only extra functionality provided is a utility wrapper around `fillOrder`,
|
* As of writing, the only extra functionality provided is a utility wrapper around `fillOrder`,
|
||||||
*/
|
*/
|
||||||
export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<TakerInterface> {
|
export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<TakerInterface> {
|
||||||
@@ -35,11 +36,12 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
|||||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||||
super(...args);
|
super(...args);
|
||||||
this.actor = (this as any) as Actor;
|
this.actor = (this as any) as Actor;
|
||||||
|
this.actor.mixins.push('Taker');
|
||||||
|
|
||||||
// Register this mixin's assertion generators
|
// Register this mixin's assertion generators
|
||||||
this.actor.simulationActions = {
|
this.actor.simulationActions = {
|
||||||
...this.actor.simulationActions,
|
...this.actor.simulationActions,
|
||||||
validFillOrderCompleteFill: this._validFillOrderCompleteFill(),
|
validFillOrder: this._validFillOrder(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,32 +63,24 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async *_validFillOrderCompleteFill(): AsyncIterableIterator<AssertionResult | void> {
|
private async *_validFillOrder(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
const { marketMakers } = this.actor.simulationEnvironment!;
|
const { actors } = this.actor.simulationEnvironment!;
|
||||||
const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment);
|
const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
|
||||||
while (true) {
|
while (true) {
|
||||||
const maker = _.sample(marketMakers);
|
// Choose a maker to be the other side of the order
|
||||||
|
const maker = Pseudorandom.sample(filterActorsByRole(actors, Maker));
|
||||||
if (maker === undefined) {
|
if (maker === undefined) {
|
||||||
yield undefined;
|
yield;
|
||||||
} else {
|
} else {
|
||||||
// Configure the maker's token balances so that the order will definitely be fillable.
|
// Maker creates and signs a fillable order
|
||||||
await Promise.all([
|
const order = await maker.createFillableOrderAsync(this.actor);
|
||||||
...this.actor.deployment.tokens.erc20.map(async token => maker.configureERC20TokenAsync(token)),
|
// Taker fills the order by a random amount (up to the order's takerAssetAmount)
|
||||||
...this.actor.deployment.tokens.erc20.map(async token =>
|
const fillAmount = Pseudorandom.integer(order.takerAssetAmount);
|
||||||
this.actor.configureERC20TokenAsync(token),
|
// Taker executes the fill with a random msg.value, so that sometimes the
|
||||||
),
|
// protocol fee is paid in ETH and other times it's paid in WETH.
|
||||||
this.actor.configureERC20TokenAsync(
|
yield assertion.executeAsync([order, fillAmount, order.signature], {
|
||||||
this.actor.deployment.tokens.weth,
|
|
||||||
this.actor.deployment.staking.stakingProxy.address,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const order = await maker.signOrderAsync({
|
|
||||||
makerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE),
|
|
||||||
takerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE),
|
|
||||||
});
|
|
||||||
yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], {
|
|
||||||
from: this.actor.address,
|
from: this.actor.address,
|
||||||
|
value: Pseudorandom.integer(DeploymentManager.protocolFee.times(2)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ObjectMap } from '@0x/types';
|
import { ObjectMap } from '@0x/types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { Actor } from './base';
|
import { Actor, Constructor } from './base';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to convert Actors into an object mapping readable names to addresses.
|
* Utility function to convert Actors into an object mapping readable names to addresses.
|
||||||
@@ -10,3 +10,14 @@ import { Actor } from './base';
|
|||||||
export function actorAddressesByName(actors: Actor[]): ObjectMap<string> {
|
export function actorAddressesByName(actors: Actor[]): ObjectMap<string> {
|
||||||
return _.zipObject(actors.map(actor => actor.name), actors.map(actor => actor.address));
|
return _.zipObject(actors.map(actor => actor.name), actors.map(actor => actor.address));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the given actors by role, specified by the class exported by an actor mixin file,
|
||||||
|
* e.g, 'Maker', 'Taker', etc.
|
||||||
|
*/
|
||||||
|
export function filterActorsByRole<TClass extends Constructor>(
|
||||||
|
actors: Actor[],
|
||||||
|
role: TClass,
|
||||||
|
): Array<InstanceType<typeof role>> {
|
||||||
|
return actors.filter(actor => actor.mixins.includes(role.name)) as InstanceType<typeof role>;
|
||||||
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import { StakingPoolById, StoredBalance } from '@0x/contracts-staking';
|
import { StoredBalance } from '@0x/contracts-staking';
|
||||||
import { expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
@@ -16,45 +17,44 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
|
|||||||
/* tslint:disable:no-non-null-assertion */
|
/* tslint:disable:no-non-null-assertion */
|
||||||
export function validCreateStakingPoolAssertion(
|
export function validCreateStakingPoolAssertion(
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
pools: StakingPoolById,
|
simulationEnvironment: SimulationEnvironment,
|
||||||
): FunctionAssertion<[number, boolean], string, string> {
|
): FunctionAssertion<[number, boolean], string, string> {
|
||||||
const { stakingWrapper } = deployment.staking;
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion<[number, boolean], string, string>(
|
return new FunctionAssertion<[number, boolean], string, string>(stakingWrapper, 'createStakingPool', {
|
||||||
stakingWrapper.createStakingPool.bind(stakingWrapper),
|
// Returns the expected ID of th created pool
|
||||||
{
|
before: async () => {
|
||||||
// Returns the expected ID of th created pool
|
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
|
||||||
before: async () => {
|
// Effectively the last poolId + 1, but as a bytestring
|
||||||
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
|
return `0x${new BigNumber(lastPoolId)
|
||||||
// Effectively the last poolId + 1, but as a bytestring
|
.plus(1)
|
||||||
return `0x${new BigNumber(lastPoolId)
|
.toString(16)
|
||||||
.plus(1)
|
.padStart(64, '0')}`;
|
||||||
.toString(16)
|
|
||||||
.padStart(64, '0')}`;
|
|
||||||
},
|
|
||||||
after: async (
|
|
||||||
expectedPoolId: string,
|
|
||||||
result: FunctionResult,
|
|
||||||
args: [number, boolean],
|
|
||||||
txData: Partial<TxData>,
|
|
||||||
) => {
|
|
||||||
const [operatorShare, shouldAddMakerAsOperator] = args;
|
|
||||||
|
|
||||||
logUtils.log(`createStakingPool(${operatorShare}, ${shouldAddMakerAsOperator}) => ${expectedPoolId}`);
|
|
||||||
|
|
||||||
// Checks the logs for the new poolId, verifies that it is as expected
|
|
||||||
const log = result.receipt!.logs[0];
|
|
||||||
const actualPoolId = (log as any).args.poolId;
|
|
||||||
expect(actualPoolId).to.equal(expectedPoolId);
|
|
||||||
|
|
||||||
// Adds the new pool to local state
|
|
||||||
pools[actualPoolId] = {
|
|
||||||
operator: txData.from!,
|
|
||||||
operatorShare,
|
|
||||||
delegatedStake: new StoredBalance(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
after: async (
|
||||||
|
expectedPoolId: string,
|
||||||
|
result: FunctionResult,
|
||||||
|
args: [number, boolean],
|
||||||
|
txData: Partial<TxData>,
|
||||||
|
) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
|
const [operatorShare] = args;
|
||||||
|
|
||||||
|
// Checks the logs for the new poolId, verifies that it is as expected
|
||||||
|
const log = result.receipt!.logs[0];
|
||||||
|
const actualPoolId = (log as any).args.poolId;
|
||||||
|
expect(actualPoolId).to.equal(expectedPoolId);
|
||||||
|
|
||||||
|
// Adds the new pool to local state
|
||||||
|
simulationEnvironment.stakingPools[actualPoolId] = {
|
||||||
|
operator: txData.from!,
|
||||||
|
operatorShare,
|
||||||
|
delegatedStake: new StoredBalance(),
|
||||||
|
lastFinalized: simulationEnvironment.currentEpoch,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-non-null-assertion*/
|
/* tslint:enable:no-non-null-assertion*/
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { StakingPoolById } from '@0x/contracts-staking';
|
import { StakingPoolById } from '@0x/contracts-staking';
|
||||||
import { expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { logUtils } from '@0x/utils';
|
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
@@ -17,21 +16,19 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
|
|||||||
): FunctionAssertion<[string, number], {}, void> {
|
): FunctionAssertion<[string, number], {}, void> {
|
||||||
const { stakingWrapper } = deployment.staking;
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion<[string, number], {}, void>(
|
return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', {
|
||||||
stakingWrapper.decreaseStakingPoolOperatorShare.bind(stakingWrapper),
|
after: async (_beforeInfo, result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => {
|
||||||
{
|
// Ensure that the tx succeeded.
|
||||||
after: async (_beforeInfo, _result: FunctionResult, args: [string, number], txData: Partial<TxData>) => {
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
const [poolId, expectedOperatorShare] = args;
|
|
||||||
|
|
||||||
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
|
const [poolId, expectedOperatorShare] = args;
|
||||||
|
|
||||||
// Checks that the on-chain pool's operator share has been updated.
|
// Checks that the on-chain pool's operator share has been updated.
|
||||||
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
|
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
|
||||||
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
|
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
|
||||||
|
|
||||||
// Updates the pool in local state.
|
// Updates the pool in local state.
|
||||||
pools[poolId].operatorShare = operatorShare;
|
pools[poolId].operatorShare = operatorShare;
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
119
contracts/integrations/test/framework/assertions/endEpoch.ts
Normal file
119
contracts/integrations/test/framework/assertions/endEpoch.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { WETH9DepositEventArgs, WETH9Events } from '@0x/contracts-erc20';
|
||||||
|
import {
|
||||||
|
AggregatedStats,
|
||||||
|
StakingEpochEndedEventArgs,
|
||||||
|
StakingEpochFinalizedEventArgs,
|
||||||
|
StakingEvents,
|
||||||
|
} from '@0x/contracts-staking';
|
||||||
|
import { constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { TxData } from 'ethereum-types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
|
interface EndEpochBeforeInfo {
|
||||||
|
wethReservedForPoolRewards: BigNumber;
|
||||||
|
aggregatedStatsBefore: AggregatedStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a FunctionAssertion for `endEpoch` which assumes valid input is provided. It checks
|
||||||
|
* that the staking proxy contract wrapped its ETH balance, aggregated stats were updated, and
|
||||||
|
* EpochFinalized/EpochEnded events were emitted.
|
||||||
|
*/
|
||||||
|
export function validEndEpochAssertion(
|
||||||
|
deployment: DeploymentManager,
|
||||||
|
simulationEnvironment: SimulationEnvironment,
|
||||||
|
): FunctionAssertion<[], EndEpochBeforeInfo, void> {
|
||||||
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
const { balanceStore } = simulationEnvironment;
|
||||||
|
|
||||||
|
return new FunctionAssertion(stakingWrapper, 'endEpoch', {
|
||||||
|
before: async () => {
|
||||||
|
await balanceStore.updateEthBalancesAsync();
|
||||||
|
const aggregatedStatsBefore = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(simulationEnvironment.currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
|
||||||
|
return { wethReservedForPoolRewards, aggregatedStatsBefore };
|
||||||
|
},
|
||||||
|
after: async (beforeInfo: EndEpochBeforeInfo, result: FunctionResult, _args: [], _txData: Partial<TxData>) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
|
const { currentEpoch } = simulationEnvironment;
|
||||||
|
const logs = result.receipt!.logs; // tslint:disable-line
|
||||||
|
|
||||||
|
// Check WETH deposit event
|
||||||
|
const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT;
|
||||||
|
const expectedDepositEvents = previousEthBalance.isGreaterThan(0)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
_owner: deployment.staking.stakingProxy.address,
|
||||||
|
_value: previousEthBalance,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
verifyEventsFromLogs<WETH9DepositEventArgs>(logs, expectedDepositEvents, WETH9Events.Deposit);
|
||||||
|
|
||||||
|
// Check that the aggregated stats were updated
|
||||||
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
|
const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo;
|
||||||
|
const expectedAggregatedStats = {
|
||||||
|
...aggregatedStatsBefore,
|
||||||
|
rewardsAvailable: _.get(
|
||||||
|
balanceStore.balances,
|
||||||
|
['erc20', stakingWrapper.address, deployment.tokens.weth.address],
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
).minus(wethReservedForPoolRewards),
|
||||||
|
};
|
||||||
|
const aggregatedStatsAfter = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats);
|
||||||
|
|
||||||
|
// Check that an EpochEnded event was emitted
|
||||||
|
verifyEventsFromLogs<StakingEpochEndedEventArgs>(
|
||||||
|
logs,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
epoch: currentEpoch,
|
||||||
|
numPoolsToFinalize: aggregatedStatsAfter.numPoolsToFinalize,
|
||||||
|
rewardsAvailable: aggregatedStatsAfter.rewardsAvailable,
|
||||||
|
totalFeesCollected: aggregatedStatsAfter.totalFeesCollected,
|
||||||
|
totalWeightedStake: aggregatedStatsAfter.totalWeightedStake,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
StakingEvents.EpochEnded,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there are no more pools to finalize, an EpochFinalized event should've been emitted
|
||||||
|
const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero()
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
epoch: currentEpoch,
|
||||||
|
rewardsPaid: constants.ZERO_AMOUNT,
|
||||||
|
rewardsRemaining: aggregatedStatsAfter.rewardsAvailable,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
|
||||||
|
logs,
|
||||||
|
expectedEpochFinalizedEvents,
|
||||||
|
StakingEvents.EpochFinalized,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The function returns the remaining number of unfinalized pools for the epoch
|
||||||
|
expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal(
|
||||||
|
aggregatedStatsAfter.numPoolsToFinalize,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update currentEpoch locally
|
||||||
|
simulationEnvironment.currentEpoch = currentEpoch.plus(1);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@@ -1,21 +1,41 @@
|
|||||||
import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
|
import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
|
||||||
import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange';
|
import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange';
|
||||||
import { constants, expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils';
|
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
|
||||||
|
import {
|
||||||
|
AggregatedStats,
|
||||||
|
constants as stakingConstants,
|
||||||
|
PoolStats,
|
||||||
|
StakingEvents,
|
||||||
|
StakingStakingPoolEarnedRewardsInEpochEventArgs,
|
||||||
|
} from '@0x/contracts-staking';
|
||||||
|
import { expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils';
|
||||||
import { FillResults, Order } from '@0x/types';
|
import { FillResults, Order } from '@0x/types';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { Maker } from '../actors/maker';
|
||||||
|
import { filterActorsByRole } from '../actors/utils';
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
function verifyFillEvents(
|
function verifyFillEvents(
|
||||||
takerAddress: string,
|
txData: Partial<TxData>,
|
||||||
order: Order,
|
order: Order,
|
||||||
receipt: TransactionReceiptWithDecodedLogs,
|
receipt: TransactionReceiptWithDecodedLogs,
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
|
takerAssetFillAmount: BigNumber,
|
||||||
): void {
|
): void {
|
||||||
|
const fillResults = ReferenceFunctions.calculateFillResults(
|
||||||
|
order,
|
||||||
|
takerAssetFillAmount,
|
||||||
|
DeploymentManager.protocolFeeMultiplier,
|
||||||
|
DeploymentManager.gasPrice,
|
||||||
|
);
|
||||||
|
const takerAddress = txData.from as string;
|
||||||
|
const value = new BigNumber(txData.value || 0);
|
||||||
// Ensure that the fill event was correct.
|
// Ensure that the fill event was correct.
|
||||||
verifyEvents<ExchangeFillEventArgs>(
|
verifyEvents<ExchangeFillEventArgs>(
|
||||||
receipt,
|
receipt,
|
||||||
@@ -30,38 +50,54 @@ function verifyFillEvents(
|
|||||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||||
takerAddress,
|
takerAddress,
|
||||||
senderAddress: takerAddress,
|
senderAddress: takerAddress,
|
||||||
makerAssetFilledAmount: order.makerAssetAmount,
|
...fillResults,
|
||||||
takerAssetFilledAmount: order.takerAssetAmount,
|
|
||||||
makerFeePaid: constants.ZERO_AMOUNT,
|
|
||||||
takerFeePaid: constants.ZERO_AMOUNT,
|
|
||||||
protocolFeePaid: DeploymentManager.protocolFee,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ExchangeEvents.Fill,
|
ExchangeEvents.Fill,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const expectedTransferEvents = [
|
||||||
|
{
|
||||||
|
_from: takerAddress,
|
||||||
|
_to: order.makerAddress,
|
||||||
|
_value: fillResults.takerAssetFilledAmount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_from: order.makerAddress,
|
||||||
|
_to: takerAddress,
|
||||||
|
_value: fillResults.makerAssetFilledAmount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_from: takerAddress,
|
||||||
|
_to: order.feeRecipientAddress,
|
||||||
|
_value: fillResults.takerFeePaid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_from: order.makerAddress,
|
||||||
|
_to: order.feeRecipientAddress,
|
||||||
|
_value: fillResults.makerFeePaid,
|
||||||
|
},
|
||||||
|
].filter(event => event._value.isGreaterThan(0));
|
||||||
|
|
||||||
|
// If not enough wei is sent to cover the protocol fee, there will be an additional WETH transfer event
|
||||||
|
if (value.isLessThan(DeploymentManager.protocolFee)) {
|
||||||
|
expectedTransferEvents.push({
|
||||||
|
_from: takerAddress,
|
||||||
|
_to: deployment.staking.stakingProxy.address,
|
||||||
|
_value: DeploymentManager.protocolFee,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the transfer events were correctly emitted.
|
// Ensure that the transfer events were correctly emitted.
|
||||||
verifyEvents<ERC20TokenTransferEventArgs>(
|
verifyEvents<ERC20TokenTransferEventArgs>(receipt, expectedTransferEvents, ERC20TokenEvents.Transfer);
|
||||||
receipt,
|
}
|
||||||
[
|
|
||||||
{
|
interface FillOrderBeforeInfo {
|
||||||
_from: takerAddress,
|
poolStats: PoolStats;
|
||||||
_to: order.makerAddress,
|
aggregatedStats: AggregatedStats;
|
||||||
_value: order.takerAssetAmount,
|
poolStake: BigNumber;
|
||||||
},
|
operatorStake: BigNumber;
|
||||||
{
|
poolId: string;
|
||||||
_from: order.makerAddress,
|
|
||||||
_to: takerAddress,
|
|
||||||
_value: order.makerAssetAmount,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_from: takerAddress,
|
|
||||||
_to: deployment.staking.stakingProxy.address,
|
|
||||||
_value: DeploymentManager.protocolFee,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
ERC20TokenEvents.Transfer,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,31 +105,117 @@ function verifyFillEvents(
|
|||||||
*/
|
*/
|
||||||
/* tslint:disable:no-unnecessary-type-assertion */
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
/* tslint:disable:no-non-null-assertion */
|
/* tslint:disable:no-non-null-assertion */
|
||||||
export function validFillOrderCompleteFillAssertion(
|
export function validFillOrderAssertion(
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> {
|
simulationEnvironment: SimulationEnvironment,
|
||||||
const exchange = deployment.exchange;
|
): FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults> {
|
||||||
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
const { actors } = simulationEnvironment;
|
||||||
|
|
||||||
return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange.fillOrder.bind(exchange), {
|
return new FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults>(
|
||||||
after: async (
|
deployment.exchange,
|
||||||
_beforeInfo,
|
'fillOrder',
|
||||||
result: FunctionResult,
|
{
|
||||||
args: [Order, BigNumber, string],
|
before: async (args: [Order, BigNumber, string]) => {
|
||||||
txData: Partial<TxData>,
|
const [order] = args;
|
||||||
) => {
|
const { currentEpoch } = simulationEnvironment;
|
||||||
const [order] = args;
|
const maker = filterActorsByRole(actors, Maker).find(actor => actor.address === order.makerAddress);
|
||||||
|
|
||||||
// Ensure that the tx succeeded.
|
const poolId = maker!.makerPoolId;
|
||||||
expect(result.success).to.be.true();
|
if (poolId === undefined) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const poolStats = PoolStats.fromArray(
|
||||||
|
await stakingWrapper.poolStatsByEpoch(poolId, currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
const aggregatedStats = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
const { currentEpochBalance: poolStake } = await stakingWrapper
|
||||||
|
.getTotalStakeDelegatedToPool(poolId)
|
||||||
|
.callAsync();
|
||||||
|
const { currentEpochBalance: operatorStake } = await stakingWrapper
|
||||||
|
.getStakeDelegatedToPoolByOwner(simulationEnvironment.stakingPools[poolId].operator, poolId)
|
||||||
|
.callAsync();
|
||||||
|
return { poolStats, aggregatedStats, poolStake, poolId, operatorStake };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after: async (
|
||||||
|
beforeInfo: FillOrderBeforeInfo | void,
|
||||||
|
result: FunctionResult,
|
||||||
|
args: [Order, BigNumber, string],
|
||||||
|
txData: Partial<TxData>,
|
||||||
|
) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
// Ensure that the correct events were emitted.
|
const [order, fillAmount] = args;
|
||||||
verifyFillEvents(txData.from!, order, result.receipt!, deployment);
|
const { currentEpoch } = simulationEnvironment;
|
||||||
|
|
||||||
logUtils.log(`Order filled by ${txData.from}`);
|
// Ensure that the correct events were emitted.
|
||||||
|
verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount);
|
||||||
|
|
||||||
// TODO: Add validation for on-chain state (like balances)
|
// If the maker is not in a staking pool, there's nothing to check
|
||||||
|
if (beforeInfo === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedPoolStats = { ...beforeInfo.poolStats };
|
||||||
|
const expectedAggregatedStats = { ...beforeInfo.aggregatedStats };
|
||||||
|
const expectedEvents = [];
|
||||||
|
|
||||||
|
// Refer to `payProtocolFee`
|
||||||
|
if (beforeInfo.poolStake.isGreaterThanOrEqualTo(stakingConstants.DEFAULT_PARAMS.minimumPoolStake)) {
|
||||||
|
if (beforeInfo.poolStats.feesCollected.isZero()) {
|
||||||
|
const membersStakeInPool = beforeInfo.poolStake.minus(beforeInfo.operatorStake);
|
||||||
|
const weightedStakeInPool = beforeInfo.operatorStake.plus(
|
||||||
|
ReferenceFunctions.getPartialAmountFloor(
|
||||||
|
stakingConstants.DEFAULT_PARAMS.rewardDelegatedStakeWeight,
|
||||||
|
new BigNumber(stakingConstants.PPM),
|
||||||
|
membersStakeInPool,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expectedPoolStats.membersStake = membersStakeInPool;
|
||||||
|
expectedPoolStats.weightedStake = weightedStakeInPool;
|
||||||
|
expectedAggregatedStats.totalWeightedStake = beforeInfo.aggregatedStats.totalWeightedStake.plus(
|
||||||
|
weightedStakeInPool,
|
||||||
|
);
|
||||||
|
expectedAggregatedStats.numPoolsToFinalize = beforeInfo.aggregatedStats.numPoolsToFinalize.plus(
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
// StakingPoolEarnedRewardsInEpoch event emitted
|
||||||
|
expectedEvents.push({
|
||||||
|
epoch: currentEpoch,
|
||||||
|
poolId: beforeInfo.poolId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Credit a protocol fee to the maker's staking pool
|
||||||
|
expectedPoolStats.feesCollected = beforeInfo.poolStats.feesCollected.plus(
|
||||||
|
DeploymentManager.protocolFee,
|
||||||
|
);
|
||||||
|
// Update aggregated stats
|
||||||
|
expectedAggregatedStats.totalFeesCollected = beforeInfo.aggregatedStats.totalFeesCollected.plus(
|
||||||
|
DeploymentManager.protocolFee,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for updated stats and event
|
||||||
|
const poolStats = PoolStats.fromArray(
|
||||||
|
await stakingWrapper.poolStatsByEpoch(beforeInfo.poolId, currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
const aggregatedStats = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
expect(poolStats).to.deep.equal(expectedPoolStats);
|
||||||
|
expect(aggregatedStats).to.deep.equal(expectedAggregatedStats);
|
||||||
|
verifyEvents<StakingStakingPoolEarnedRewardsInEpochEventArgs>(
|
||||||
|
result.receipt!,
|
||||||
|
expectedEvents,
|
||||||
|
StakingEvents.StakingPoolEarnedRewardsInEpoch,
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-non-null-assertion */
|
/* tslint:enable:no-non-null-assertion */
|
||||||
/* tslint:enable:no-unnecessary-type-assertion */
|
/* tslint:enable:no-unnecessary-type-assertion */
|
||||||
|
217
contracts/integrations/test/framework/assertions/finalizePool.ts
Normal file
217
contracts/integrations/test/framework/assertions/finalizePool.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { WETH9Events, WETH9TransferEventArgs } from '@0x/contracts-erc20';
|
||||||
|
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
|
||||||
|
import {
|
||||||
|
AggregatedStats,
|
||||||
|
constants as stakingConstants,
|
||||||
|
PoolStats,
|
||||||
|
StakingEpochFinalizedEventArgs,
|
||||||
|
StakingEvents,
|
||||||
|
StakingRewardsPaidEventArgs,
|
||||||
|
} from '@0x/contracts-staking';
|
||||||
|
import {
|
||||||
|
assertRoughlyEquals,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
filterLogsToArguments,
|
||||||
|
toDecimal,
|
||||||
|
verifyEventsFromLogs,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
|
const PRECISION = 15;
|
||||||
|
const COBB_DOUGLAS_ALPHA = toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator).dividedBy(
|
||||||
|
toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaDenominator),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reference function for Cobb-Douglas
|
||||||
|
function cobbDouglas(poolStats: PoolStats, aggregatedStats: AggregatedStats): BigNumber {
|
||||||
|
const { feesCollected, weightedStake } = poolStats;
|
||||||
|
const { rewardsAvailable, totalFeesCollected, totalWeightedStake } = aggregatedStats;
|
||||||
|
|
||||||
|
const feeRatio = toDecimal(feesCollected).dividedBy(toDecimal(totalFeesCollected));
|
||||||
|
const stakeRatio = toDecimal(weightedStake).dividedBy(toDecimal(totalWeightedStake));
|
||||||
|
// totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)
|
||||||
|
return new BigNumber(
|
||||||
|
feeRatio
|
||||||
|
.pow(COBB_DOUGLAS_ALPHA)
|
||||||
|
.times(stakeRatio.pow(toDecimal(1).minus(COBB_DOUGLAS_ALPHA)))
|
||||||
|
.times(toDecimal(rewardsAvailable))
|
||||||
|
.toFixed(0, BigNumber.ROUND_FLOOR),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FinalizePoolBeforeInfo {
|
||||||
|
poolStats: PoolStats;
|
||||||
|
aggregatedStats: AggregatedStats;
|
||||||
|
poolRewards: BigNumber;
|
||||||
|
cumulativeRewardsLastStored: BigNumber;
|
||||||
|
mostRecentCumulativeRewards: {
|
||||||
|
numerator: BigNumber;
|
||||||
|
denominator: BigNumber;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a FunctionAssertion for `finalizePool` which assumes valid input is provided. The `after`
|
||||||
|
* callback below is annotated with the solidity source of `finalizePool`.
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
|
export function validFinalizePoolAssertion(
|
||||||
|
deployment: DeploymentManager,
|
||||||
|
simulationEnvironment: SimulationEnvironment,
|
||||||
|
): FunctionAssertion<[string], FinalizePoolBeforeInfo, void> {
|
||||||
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
|
return new FunctionAssertion<[string], FinalizePoolBeforeInfo, void>(stakingWrapper, 'finalizePool', {
|
||||||
|
before: async (args: [string]) => {
|
||||||
|
const [poolId] = args;
|
||||||
|
const { currentEpoch } = simulationEnvironment;
|
||||||
|
const prevEpoch = currentEpoch.minus(1);
|
||||||
|
|
||||||
|
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
|
||||||
|
const aggregatedStats = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||||
|
const [
|
||||||
|
mostRecentCumulativeRewards,
|
||||||
|
cumulativeRewardsLastStored,
|
||||||
|
] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
|
||||||
|
return {
|
||||||
|
poolStats,
|
||||||
|
aggregatedStats,
|
||||||
|
poolRewards,
|
||||||
|
cumulativeRewardsLastStored,
|
||||||
|
mostRecentCumulativeRewards,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
after: async (beforeInfo: FinalizePoolBeforeInfo, result: FunctionResult, args: [string]) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
|
const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion
|
||||||
|
const { stakingPools, currentEpoch } = simulationEnvironment;
|
||||||
|
const prevEpoch = currentEpoch.minus(1);
|
||||||
|
const [poolId] = args;
|
||||||
|
const pool = stakingPools[poolId];
|
||||||
|
|
||||||
|
// finalizePool noops if there are no pools to finalize or
|
||||||
|
// the pool did not earn rewards or already finalized (has no fees).
|
||||||
|
if (beforeInfo.aggregatedStats.numPoolsToFinalize.isZero() || beforeInfo.poolStats.feesCollected.isZero()) {
|
||||||
|
expect(logs.length, 'Expect no events to be emitted').to.equal(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should have cleared the pool stats for prevEpoch
|
||||||
|
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
|
||||||
|
expect(poolStats).to.deep.equal({
|
||||||
|
feesCollected: constants.ZERO_AMOUNT,
|
||||||
|
weightedStake: constants.ZERO_AMOUNT,
|
||||||
|
membersStake: constants.ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
|
||||||
|
// uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats);
|
||||||
|
const rewards = BigNumber.min(
|
||||||
|
cobbDouglas(beforeInfo.poolStats, beforeInfo.aggregatedStats),
|
||||||
|
beforeInfo.aggregatedStats.rewardsAvailable.minus(beforeInfo.aggregatedStats.totalRewardsFinalized),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that a RewardsPaid event was emitted
|
||||||
|
const events = filterLogsToArguments<StakingRewardsPaidEventArgs>(logs, StakingEvents.RewardsPaid);
|
||||||
|
expect(events.length, 'Number of RewardsPaid events emitted').to.equal(1);
|
||||||
|
const [rewardsPaidEvent] = events;
|
||||||
|
expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.equal(poolId);
|
||||||
|
expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch);
|
||||||
|
|
||||||
|
// Pull the operator and members' reward from the event
|
||||||
|
const { operatorReward, membersReward } = rewardsPaidEvent;
|
||||||
|
const totalReward = operatorReward.plus(membersReward);
|
||||||
|
// Should be approximately equal to the rewards compute using the Cobb-Douglas reference function
|
||||||
|
assertRoughlyEquals(totalReward, rewards, PRECISION);
|
||||||
|
|
||||||
|
// Operator takes their share of the rewards
|
||||||
|
if (beforeInfo.poolStats.membersStake.isZero()) {
|
||||||
|
expect(
|
||||||
|
operatorReward,
|
||||||
|
"operatorReward should equal totalReward if pool's membersStake is 0",
|
||||||
|
).to.bignumber.equal(totalReward);
|
||||||
|
} else {
|
||||||
|
expect(operatorReward).to.bignumber.equal(
|
||||||
|
ReferenceFunctions.getPartialAmountCeil(
|
||||||
|
new BigNumber(pool.operatorShare),
|
||||||
|
new BigNumber(stakingConstants.PPM),
|
||||||
|
totalReward,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pays the operator in WETH if the operator's reward is non-zero
|
||||||
|
const expectedTransferEvents = operatorReward.isGreaterThan(0)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
_from: deployment.staking.stakingProxy.address,
|
||||||
|
_to: pool.operator,
|
||||||
|
_value: operatorReward,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Check for WETH transfer event emitted when paying out operator's reward.
|
||||||
|
verifyEventsFromLogs<WETH9TransferEventArgs>(logs, expectedTransferEvents, WETH9Events.Transfer);
|
||||||
|
// Check that pool rewards have increased.
|
||||||
|
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||||
|
expect(poolRewards).to.bignumber.equal(beforeInfo.poolRewards.plus(membersReward));
|
||||||
|
// Check that cumulative rewards have increased.
|
||||||
|
const [
|
||||||
|
mostRecentCumulativeRewards,
|
||||||
|
cumulativeRewardsLastStored,
|
||||||
|
] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
|
||||||
|
expect(cumulativeRewardsLastStored).to.bignumber.equal(currentEpoch);
|
||||||
|
let [numerator, denominator] = ReferenceFunctions.LibFractions.add(
|
||||||
|
beforeInfo.mostRecentCumulativeRewards.numerator,
|
||||||
|
beforeInfo.mostRecentCumulativeRewards.denominator,
|
||||||
|
membersReward,
|
||||||
|
beforeInfo.poolStats.membersStake,
|
||||||
|
);
|
||||||
|
[numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator);
|
||||||
|
expect(mostRecentCumulativeRewards).to.deep.equal({ numerator, denominator });
|
||||||
|
|
||||||
|
// Check that aggregated stats have been updated
|
||||||
|
const aggregatedStats = AggregatedStats.fromArray(
|
||||||
|
await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(),
|
||||||
|
);
|
||||||
|
expect(aggregatedStats).to.deep.equal({
|
||||||
|
...beforeInfo.aggregatedStats,
|
||||||
|
totalRewardsFinalized: beforeInfo.aggregatedStats.totalRewardsFinalized.plus(totalReward),
|
||||||
|
numPoolsToFinalize: beforeInfo.aggregatedStats.numPoolsToFinalize.minus(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there are no more unfinalized pools remaining, the epoch is finalized.
|
||||||
|
const expectedEpochFinalizedEvents = aggregatedStats.numPoolsToFinalize.isZero()
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
epoch: prevEpoch,
|
||||||
|
rewardsPaid: aggregatedStats.totalRewardsFinalized,
|
||||||
|
rewardsRemaining: aggregatedStats.rewardsAvailable.minus(
|
||||||
|
aggregatedStats.totalRewardsFinalized,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
|
||||||
|
logs,
|
||||||
|
expectedEpochFinalizedEvents,
|
||||||
|
StakingEvents.EpochFinalized,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
pool.lastFinalized = prevEpoch;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -1,7 +1,9 @@
|
|||||||
import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
import { BaseContract, ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
// tslint:disable:max-classes-per-file
|
// tslint:disable:max-classes-per-file
|
||||||
export type GenericContractFunction<T> = (...args: any[]) => ContractFunctionObj<T>;
|
export type GenericContractFunction<T> = (...args: any[]) => ContractFunctionObj<T>;
|
||||||
|
|
||||||
@@ -48,29 +50,22 @@ export interface AssertionResult<TBefore = unknown> {
|
|||||||
*/
|
*/
|
||||||
export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> implements Assertion<TArgs> {
|
export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> implements Assertion<TArgs> {
|
||||||
// A condition that will be applied to `wrapperFunction`.
|
// A condition that will be applied to `wrapperFunction`.
|
||||||
public condition: Condition<TArgs, TBefore>;
|
public readonly condition: Condition<TArgs, TBefore>;
|
||||||
|
|
||||||
// The wrapper function that will be wrapped in assertions.
|
|
||||||
public wrapperFunction: (
|
|
||||||
...args: TArgs // tslint:disable-line:trailing-comma
|
|
||||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
wrapperFunction: (
|
private readonly _contractWrapper: BaseContract,
|
||||||
...args: TArgs // tslint:disable-line:trailing-comma
|
private readonly _functionName: string,
|
||||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>,
|
|
||||||
condition: Partial<Condition<TArgs, TBefore>> = {},
|
condition: Partial<Condition<TArgs, TBefore>> = {},
|
||||||
) {
|
) {
|
||||||
this.condition = {
|
this.condition = {
|
||||||
before: async (args: TArgs, txData: Partial<TxData>) => {
|
before: async (_args: TArgs, _txData: Partial<TxData>) => {
|
||||||
return ({} as any) as TBefore;
|
return ({} as any) as TBefore;
|
||||||
},
|
},
|
||||||
after: async (beforeInfo: TBefore, result: FunctionResult, args: TArgs, txData: Partial<TxData>) => {
|
after: async (_beforeInfo: TBefore, _result: FunctionResult, _args: TArgs, _txData: Partial<TxData>) => {
|
||||||
return ({} as any) as TBefore;
|
return ({} as any) as TBefore;
|
||||||
},
|
},
|
||||||
...condition,
|
...condition,
|
||||||
};
|
};
|
||||||
this.wrapperFunction = wrapperFunction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,10 +79,15 @@ export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> imp
|
|||||||
// Initialize the callResult so that the default success value is true.
|
// Initialize the callResult so that the default success value is true.
|
||||||
const callResult: FunctionResult = { success: true };
|
const callResult: FunctionResult = { success: true };
|
||||||
|
|
||||||
|
// Log function name, arguments, and txData
|
||||||
|
logger.logFunctionAssertion(this._functionName, args, txData);
|
||||||
|
|
||||||
// Try to make the call to the function. If it is successful, pass the
|
// Try to make the call to the function. If it is successful, pass the
|
||||||
// result and receipt to the after condition.
|
// result and receipt to the after condition.
|
||||||
try {
|
try {
|
||||||
const functionWithArgs = this.wrapperFunction(...args) as ContractTxFunctionObj<ReturnDataType>;
|
const functionWithArgs = (this._contractWrapper as any)[this._functionName](
|
||||||
|
...args,
|
||||||
|
) as ContractTxFunctionObj<ReturnDataType>;
|
||||||
callResult.data = await functionWithArgs.callAsync(txData);
|
callResult.data = await functionWithArgs.callAsync(txData);
|
||||||
callResult.receipt =
|
callResult.receipt =
|
||||||
functionWithArgs.awaitTransactionSuccessAsync !== undefined
|
functionWithArgs.awaitTransactionSuccessAsync !== undefined
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { StakingEvents, StakingMakerStakingPoolSetEventArgs } from '@0x/contracts-staking';
|
import { StakingEvents, StakingMakerStakingPoolSetEventArgs } from '@0x/contracts-staking';
|
||||||
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||||
import { logUtils } from '@0x/utils';
|
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
@@ -12,16 +11,18 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
|
|||||||
*/
|
*/
|
||||||
/* tslint:disable:no-unnecessary-type-assertion */
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
/* tslint:disable:no-non-null-assertion */
|
/* tslint:disable:no-non-null-assertion */
|
||||||
export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> {
|
export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], void, void> {
|
||||||
const { stakingWrapper } = deployment.staking;
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion<[string], {}, void>(stakingWrapper.joinStakingPoolAsMaker.bind(stakingWrapper), {
|
return new FunctionAssertion<[string], void, void>(stakingWrapper, 'joinStakingPoolAsMaker', {
|
||||||
after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial<TxData>) => {
|
after: async (_beforeInfo: void, result: FunctionResult, args: [string], txData: Partial<TxData>) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
const [poolId] = args;
|
const [poolId] = args;
|
||||||
|
|
||||||
expect(_result.success).to.be.true();
|
// Verify a MakerStakingPoolSet event was emitted
|
||||||
|
const logs = result.receipt!.logs;
|
||||||
const logs = _result.receipt!.logs;
|
|
||||||
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
|
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
|
||||||
logs,
|
logs,
|
||||||
StakingEvents.MakerStakingPoolSet,
|
StakingEvents.MakerStakingPoolSet,
|
||||||
@@ -32,10 +33,9 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu
|
|||||||
poolId,
|
poolId,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
// Verify that the maker's pool id has been updated in storage
|
||||||
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
|
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
|
||||||
expect(joinedPoolId).to.be.eq(poolId);
|
expect(joinedPoolId).to.be.eq(poolId);
|
||||||
|
|
||||||
logUtils.log(`Pool ${poolId} joined by ${txData.from}`);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,146 +1,199 @@
|
|||||||
import {
|
import {
|
||||||
GlobalStakeByStatus,
|
decreaseNextBalance,
|
||||||
|
increaseNextBalance,
|
||||||
|
loadCurrentBalance,
|
||||||
OwnerStakeByStatus,
|
OwnerStakeByStatus,
|
||||||
StakeInfo,
|
StakeInfo,
|
||||||
StakeStatus,
|
StakeStatus,
|
||||||
StakingPoolById,
|
|
||||||
StoredBalance,
|
StoredBalance,
|
||||||
} from '@0x/contracts-staking';
|
} from '@0x/contracts-staking';
|
||||||
import { constants, expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void {
|
|
||||||
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void {
|
|
||||||
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNextEpochBalances(
|
function updateNextEpochBalances(
|
||||||
globalStake: GlobalStakeByStatus,
|
|
||||||
ownerStake: OwnerStakeByStatus,
|
ownerStake: OwnerStakeByStatus,
|
||||||
pools: StakingPoolById,
|
|
||||||
from: StakeInfo,
|
from: StakeInfo,
|
||||||
to: StakeInfo,
|
to: StakeInfo,
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
|
simulationEnvironment: SimulationEnvironment,
|
||||||
): string[] {
|
): string[] {
|
||||||
|
const { globalStake, stakingPools, currentEpoch } = simulationEnvironment;
|
||||||
|
|
||||||
// The on-chain state of these updated pools will be verified in the `after` of the assertion.
|
// The on-chain state of these updated pools will be verified in the `after` of the assertion.
|
||||||
const updatedPools = [];
|
const updatedPools = [];
|
||||||
|
|
||||||
// Decrement next epoch balances associated with the `from` stake
|
// Decrement next epoch balances associated with the `from` stake
|
||||||
if (from.status === StakeStatus.Undelegated) {
|
if (from.status === StakeStatus.Undelegated) {
|
||||||
// Decrement owner undelegated stake
|
// Decrement owner undelegated stake
|
||||||
decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
|
ownerStake[StakeStatus.Undelegated] = decreaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Decrement global undelegated stake
|
// Decrement global undelegated stake
|
||||||
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
|
globalStake[StakeStatus.Undelegated] = decreaseNextBalance(
|
||||||
|
globalStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
} else if (from.status === StakeStatus.Delegated) {
|
} else if (from.status === StakeStatus.Delegated) {
|
||||||
// Decrement owner's delegated stake to this pool
|
// Decrement owner's delegated stake to this pool
|
||||||
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount);
|
ownerStake[StakeStatus.Delegated][from.poolId] = decreaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Delegated][from.poolId],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Decrement owner's total delegated stake
|
// Decrement owner's total delegated stake
|
||||||
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
|
ownerStake[StakeStatus.Delegated].total = decreaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Delegated].total,
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Decrement global delegated stake
|
// Decrement global delegated stake
|
||||||
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
|
globalStake[StakeStatus.Delegated] = decreaseNextBalance(
|
||||||
|
globalStake[StakeStatus.Delegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Decrement pool's delegated stake
|
// Decrement pool's delegated stake
|
||||||
decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount);
|
stakingPools[from.poolId].delegatedStake = decreaseNextBalance(
|
||||||
|
stakingPools[from.poolId].delegatedStake,
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
updatedPools.push(from.poolId);
|
updatedPools.push(from.poolId);
|
||||||
|
|
||||||
|
// TODO: Check that delegator rewards have been withdrawn/synced
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment next epoch balances associated with the `to` stake
|
// Increment next epoch balances associated with the `to` stake
|
||||||
if (to.status === StakeStatus.Undelegated) {
|
if (to.status === StakeStatus.Undelegated) {
|
||||||
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
|
// Increment owner undelegated stake
|
||||||
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
|
ownerStake[StakeStatus.Undelegated] = increaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
|
// Increment global undelegated stake
|
||||||
|
globalStake[StakeStatus.Undelegated] = increaseNextBalance(
|
||||||
|
globalStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
} else if (to.status === StakeStatus.Delegated) {
|
} else if (to.status === StakeStatus.Delegated) {
|
||||||
// Initializes the balance for this pool if the user has not previously delegated to it
|
// Initializes the balance for this pool if the user has not previously delegated to it
|
||||||
_.defaults(ownerStake[StakeStatus.Delegated], {
|
_.defaults(ownerStake[StakeStatus.Delegated], {
|
||||||
[to.poolId]: new StoredBalance(),
|
[to.poolId]: new StoredBalance(),
|
||||||
});
|
});
|
||||||
// Increment owner's delegated stake to this pool
|
// Increment owner's delegated stake to this pool
|
||||||
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount);
|
ownerStake[StakeStatus.Delegated][to.poolId] = increaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Delegated][to.poolId],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Increment owner's total delegated stake
|
// Increment owner's total delegated stake
|
||||||
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
|
ownerStake[StakeStatus.Delegated].total = increaseNextBalance(
|
||||||
|
ownerStake[StakeStatus.Delegated].total,
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Increment global delegated stake
|
// Increment global delegated stake
|
||||||
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
|
globalStake[StakeStatus.Delegated] = increaseNextBalance(
|
||||||
|
globalStake[StakeStatus.Delegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
// Increment pool's delegated stake
|
// Increment pool's delegated stake
|
||||||
incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount);
|
stakingPools[to.poolId].delegatedStake = increaseNextBalance(
|
||||||
|
stakingPools[to.poolId].delegatedStake,
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
updatedPools.push(to.poolId);
|
updatedPools.push(to.poolId);
|
||||||
|
|
||||||
|
// TODO: Check that delegator rewards have been withdrawn/synced
|
||||||
}
|
}
|
||||||
return updatedPools;
|
return updatedPools;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
|
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. Checks that
|
||||||
* FunctionAssertion checks that the staker's
|
* the owner's stake and global stake by status get updated correctly.
|
||||||
*/
|
*/
|
||||||
/* tslint:disable:no-unnecessary-type-assertion */
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
export function validMoveStakeAssertion(
|
export function validMoveStakeAssertion(
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
globalStake: GlobalStakeByStatus,
|
simulationEnvironment: SimulationEnvironment,
|
||||||
ownerStake: OwnerStakeByStatus,
|
ownerStake: OwnerStakeByStatus,
|
||||||
pools: StakingPoolById,
|
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
|
||||||
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> {
|
|
||||||
const { stakingWrapper } = deployment.staking;
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(
|
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', {
|
||||||
stakingWrapper.moveStake.bind(stakingWrapper),
|
after: async (
|
||||||
{
|
_beforeInfo: void,
|
||||||
after: async (
|
result: FunctionResult,
|
||||||
_beforeInfo: {},
|
args: [StakeInfo, StakeInfo, BigNumber],
|
||||||
_result: FunctionResult,
|
txData: Partial<TxData>,
|
||||||
args: [StakeInfo, StakeInfo, BigNumber],
|
) => {
|
||||||
txData: Partial<TxData>,
|
// Ensure that the tx succeeded.
|
||||||
) => {
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
const [from, to, amount] = args;
|
|
||||||
|
|
||||||
logUtils.log(
|
const [from, to, amount] = args;
|
||||||
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
|
const { stakingPools, globalStake, currentEpoch } = simulationEnvironment;
|
||||||
StakeStatus[to.status]
|
|
||||||
}, poolId: ${to.poolId} }, ${amount})`,
|
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
||||||
|
|
||||||
|
// Update local balances to match the expected result of this `moveStake` operation
|
||||||
|
const updatedPools = updateNextEpochBalances(ownerStake, from, to, amount, simulationEnvironment);
|
||||||
|
|
||||||
|
// Fetches on-chain owner stake balances and checks against local balances
|
||||||
|
const ownerUndelegatedStake = {
|
||||||
|
...new StoredBalance(),
|
||||||
|
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()),
|
||||||
|
};
|
||||||
|
const ownerDelegatedStake = {
|
||||||
|
...new StoredBalance(),
|
||||||
|
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
|
||||||
|
};
|
||||||
|
expect(ownerUndelegatedStake).to.deep.equal(
|
||||||
|
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch),
|
||||||
|
);
|
||||||
|
expect(ownerDelegatedStake).to.deep.equal(
|
||||||
|
loadCurrentBalance(ownerStake[StakeStatus.Delegated].total, currentEpoch),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetches on-chain global stake balances and checks against local balances
|
||||||
|
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync();
|
||||||
|
const globalUndelegatedStake = await stakingWrapper
|
||||||
|
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||||
|
.callAsync();
|
||||||
|
expect(globalDelegatedStake).to.deep.equal(
|
||||||
|
loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch),
|
||||||
|
);
|
||||||
|
expect(globalUndelegatedStake).to.deep.equal(
|
||||||
|
loadCurrentBalance(globalStake[StakeStatus.Undelegated], currentEpoch),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetches on-chain pool stake balances and checks against local balances
|
||||||
|
for (const poolId of updatedPools) {
|
||||||
|
const stakeDelegatedByOwner = await stakingWrapper
|
||||||
|
.getStakeDelegatedToPoolByOwner(owner, poolId)
|
||||||
|
.callAsync();
|
||||||
|
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
|
||||||
|
expect(stakeDelegatedByOwner).to.deep.equal(
|
||||||
|
loadCurrentBalance(ownerStake[StakeStatus.Delegated][poolId], currentEpoch),
|
||||||
);
|
);
|
||||||
|
expect(totalStakeDelegated).to.deep.equal(
|
||||||
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
loadCurrentBalance(stakingPools[poolId].delegatedStake, currentEpoch),
|
||||||
|
);
|
||||||
// Update local balances to match the expected result of this `moveStake` operation
|
}
|
||||||
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
|
|
||||||
|
|
||||||
// Fetches on-chain owner stake balances and checks against local balances
|
|
||||||
const ownerUndelegatedStake = {
|
|
||||||
...new StoredBalance(),
|
|
||||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()),
|
|
||||||
};
|
|
||||||
const ownerDelegatedStake = {
|
|
||||||
...new StoredBalance(),
|
|
||||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
|
|
||||||
};
|
|
||||||
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
|
|
||||||
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total);
|
|
||||||
|
|
||||||
// Fetches on-chain global stake balances and checks against local balances
|
|
||||||
const globalUndelegatedStake = await stakingWrapper
|
|
||||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
|
||||||
.callAsync();
|
|
||||||
const globalDelegatedStake = await stakingWrapper
|
|
||||||
.getGlobalStakeByStatus(StakeStatus.Delegated)
|
|
||||||
.callAsync();
|
|
||||||
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
|
|
||||||
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
|
|
||||||
|
|
||||||
// Fetches on-chain pool stake balances and checks against local balances
|
|
||||||
for (const poolId of updatedPools) {
|
|
||||||
const stakeDelegatedByOwner = await stakingWrapper
|
|
||||||
.getStakeDelegatedToPoolByOwner(owner, poolId)
|
|
||||||
.callAsync();
|
|
||||||
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
|
|
||||||
expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]);
|
|
||||||
expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-unnecessary-type-assertion */
|
/* tslint:enable:no-unnecessary-type-assertion */
|
||||||
|
@@ -1,25 +1,14 @@
|
|||||||
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
import { increaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking';
|
||||||
import { expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
|
|
||||||
import { LocalBalanceStore } from '../balances/local_balance_store';
|
import { LocalBalanceStore } from '../balances/local_balance_store';
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
function expectedUndelegatedStake(
|
|
||||||
initStake: OwnerStakeByStatus | GlobalStakeByStatus,
|
|
||||||
amount: BigNumber,
|
|
||||||
): StoredBalance {
|
|
||||||
return {
|
|
||||||
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch,
|
|
||||||
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.plus(amount),
|
|
||||||
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.plus(amount),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a FunctionAssertion for `stake` which assumes valid input is provided. The
|
* Returns a FunctionAssertion for `stake` which assumes valid input is provided. The
|
||||||
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX decrease and increase,
|
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX decrease and increase,
|
||||||
@@ -28,20 +17,20 @@ function expectedUndelegatedStake(
|
|||||||
/* tslint:disable:no-unnecessary-type-assertion */
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
export function validStakeAssertion(
|
export function validStakeAssertion(
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
balanceStore: BlockchainBalanceStore,
|
simulationEnvironment: SimulationEnvironment,
|
||||||
globalStake: GlobalStakeByStatus,
|
|
||||||
ownerStake: OwnerStakeByStatus,
|
ownerStake: OwnerStakeByStatus,
|
||||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion(stakingWrapper.stake.bind(stakingWrapper), {
|
return new FunctionAssertion(stakingWrapper, 'stake', {
|
||||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||||
const [amount] = args;
|
const [amount] = args;
|
||||||
|
const { balanceStore } = simulationEnvironment;
|
||||||
|
|
||||||
// Simulates the transfer of ZRX from staker to vault
|
// Simulates the transfer of ZRX from staker to vault
|
||||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||||
expectedBalances.transferAsset(
|
expectedBalances.transferAsset(
|
||||||
txData.from!, // tslint:disable-line:no-non-null-assertion
|
txData.from as string,
|
||||||
zrxVault.address,
|
zrxVault.address,
|
||||||
amount,
|
amount,
|
||||||
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
||||||
@@ -50,35 +39,45 @@ export function validStakeAssertion(
|
|||||||
},
|
},
|
||||||
after: async (
|
after: async (
|
||||||
expectedBalances: LocalBalanceStore,
|
expectedBalances: LocalBalanceStore,
|
||||||
_result: FunctionResult,
|
result: FunctionResult,
|
||||||
args: [BigNumber],
|
args: [BigNumber],
|
||||||
txData: Partial<TxData>,
|
txData: Partial<TxData>,
|
||||||
) => {
|
) => {
|
||||||
const [amount] = args;
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
logUtils.log(`stake(${amount})`);
|
const [amount] = args;
|
||||||
|
const { balanceStore, currentEpoch, globalStake } = simulationEnvironment;
|
||||||
|
|
||||||
// Checks that the ZRX transfer updated balances as expected.
|
// Checks that the ZRX transfer updated balances as expected.
|
||||||
await balanceStore.updateErc20BalancesAsync();
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
balanceStore.assertEquals(expectedBalances);
|
balanceStore.assertEquals(expectedBalances);
|
||||||
|
|
||||||
|
// _increaseCurrentAndNextBalance
|
||||||
|
ownerStake[StakeStatus.Undelegated] = increaseCurrentAndNextBalance(
|
||||||
|
ownerStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
|
globalStake[StakeStatus.Undelegated] = increaseCurrentAndNextBalance(
|
||||||
|
globalStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
|
|
||||||
// Checks that the owner's undelegated stake has increased by the stake amount
|
// Checks that the owner's undelegated stake has increased by the stake amount
|
||||||
const ownerUndelegatedStake = await stakingWrapper
|
const ownerUndelegatedStake = await stakingWrapper
|
||||||
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) // tslint:disable-line:no-non-null-assertion
|
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
|
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
|
||||||
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
|
|
||||||
// Updates local state accordingly
|
|
||||||
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
|
|
||||||
|
|
||||||
// Checks that the global undelegated stake has also increased by the stake amount
|
// Checks that the global undelegated stake has also increased by the stake amount
|
||||||
const globalUndelegatedStake = await stakingWrapper
|
const globalUndelegatedStake = await stakingWrapper
|
||||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
|
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
|
||||||
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
|
globalStake[StakeStatus.Undelegated],
|
||||||
// Updates local state accordingly
|
);
|
||||||
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,49 +1,37 @@
|
|||||||
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
import { decreaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking';
|
||||||
import { expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
|
|
||||||
import { LocalBalanceStore } from '../balances/local_balance_store';
|
import { LocalBalanceStore } from '../balances/local_balance_store';
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
function expectedUndelegatedStake(
|
|
||||||
initStake: OwnerStakeByStatus | GlobalStakeByStatus,
|
|
||||||
amount: BigNumber,
|
|
||||||
): StoredBalance {
|
|
||||||
return {
|
|
||||||
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch,
|
|
||||||
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.minus(amount),
|
|
||||||
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.minus(amount),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a FunctionAssertion for `unstake` which assumes valid input is provided. The
|
* Returns a FunctionAssertion for `unstake` which assumes valid input is provided. The
|
||||||
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX increase and decrease,
|
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX increase and decrease,
|
||||||
* respectively, by the input amount.
|
* respectively, by the input amount.
|
||||||
*/
|
*/
|
||||||
/* tslint:disable:no-unnecessary-type-assertion */
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
/* tslint:disable:no-non-null-assertion */
|
|
||||||
export function validUnstakeAssertion(
|
export function validUnstakeAssertion(
|
||||||
deployment: DeploymentManager,
|
deployment: DeploymentManager,
|
||||||
balanceStore: BlockchainBalanceStore,
|
simulationEnvironment: SimulationEnvironment,
|
||||||
globalStake: GlobalStakeByStatus,
|
|
||||||
ownerStake: OwnerStakeByStatus,
|
ownerStake: OwnerStakeByStatus,
|
||||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||||
|
|
||||||
return new FunctionAssertion(stakingWrapper.unstake.bind(stakingWrapper), {
|
return new FunctionAssertion(stakingWrapper, 'unstake', {
|
||||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||||
const [amount] = args;
|
const [amount] = args;
|
||||||
|
const { balanceStore } = simulationEnvironment;
|
||||||
|
|
||||||
// Simulates the transfer of ZRX from vault to staker
|
// Simulates the transfer of ZRX from vault to staker
|
||||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||||
expectedBalances.transferAsset(
|
expectedBalances.transferAsset(
|
||||||
zrxVault.address,
|
zrxVault.address,
|
||||||
txData.from!,
|
txData.from as string,
|
||||||
amount,
|
amount,
|
||||||
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
||||||
);
|
);
|
||||||
@@ -51,37 +39,46 @@ export function validUnstakeAssertion(
|
|||||||
},
|
},
|
||||||
after: async (
|
after: async (
|
||||||
expectedBalances: LocalBalanceStore,
|
expectedBalances: LocalBalanceStore,
|
||||||
_result: FunctionResult,
|
result: FunctionResult,
|
||||||
args: [BigNumber],
|
args: [BigNumber],
|
||||||
txData: Partial<TxData>,
|
txData: Partial<TxData>,
|
||||||
) => {
|
) => {
|
||||||
const [amount] = args;
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
logUtils.log(`unstake(${amount})`);
|
const [amount] = args;
|
||||||
|
const { balanceStore, currentEpoch, globalStake } = simulationEnvironment;
|
||||||
|
|
||||||
// Checks that the ZRX transfer updated balances as expected.
|
// Checks that the ZRX transfer updated balances as expected.
|
||||||
await balanceStore.updateErc20BalancesAsync();
|
await balanceStore.updateErc20BalancesAsync();
|
||||||
balanceStore.assertEquals(expectedBalances);
|
balanceStore.assertEquals(expectedBalances);
|
||||||
|
|
||||||
|
// _decreaseCurrentAndNextBalance
|
||||||
|
ownerStake[StakeStatus.Undelegated] = decreaseCurrentAndNextBalance(
|
||||||
|
ownerStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
|
globalStake[StakeStatus.Undelegated] = decreaseCurrentAndNextBalance(
|
||||||
|
globalStake[StakeStatus.Undelegated],
|
||||||
|
amount,
|
||||||
|
currentEpoch,
|
||||||
|
);
|
||||||
|
|
||||||
// Checks that the owner's undelegated stake has decreased by the stake amount
|
// Checks that the owner's undelegated stake has decreased by the stake amount
|
||||||
const ownerUndelegatedStake = await stakingWrapper
|
const ownerUndelegatedStake = await stakingWrapper
|
||||||
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated)
|
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
|
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
|
||||||
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
|
|
||||||
// Updates local state accordingly
|
|
||||||
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
|
|
||||||
|
|
||||||
// Checks that the global undelegated stake has also decreased by the stake amount
|
// Checks that the global undelegated stake has also increased by the stake amount
|
||||||
const globalUndelegatedStake = await stakingWrapper
|
const globalUndelegatedStake = await stakingWrapper
|
||||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||||
.callAsync();
|
.callAsync();
|
||||||
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
|
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
|
||||||
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
|
globalStake[StakeStatus.Undelegated],
|
||||||
// Updates local state accordingly
|
);
|
||||||
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-non-null-assertion */
|
|
||||||
/* tslint:enable:no-unnecessary-type-assertion */
|
/* tslint:enable:no-unnecessary-type-assertion */
|
||||||
|
@@ -0,0 +1,76 @@
|
|||||||
|
import { WETH9Events, WETH9TransferEventArgs } from '@0x/contracts-erc20';
|
||||||
|
import { loadCurrentBalance, StoredBalance } from '@0x/contracts-staking';
|
||||||
|
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
|
import { SimulationEnvironment } from '../simulation';
|
||||||
|
|
||||||
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||||
|
|
||||||
|
interface WithdrawDelegatorRewardsBeforeInfo {
|
||||||
|
delegatorStake: StoredBalance;
|
||||||
|
poolRewards: BigNumber;
|
||||||
|
wethReservedForPoolRewards: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a FunctionAssertion for `withdrawDelegatorRewards` which assumes valid input is provided.
|
||||||
|
* It checks that the delegator's stake gets synced and pool rewards are updated to reflect the
|
||||||
|
* amount withdrawn.
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-unnecessary-type-assertion */
|
||||||
|
export function validWithdrawDelegatorRewardsAssertion(
|
||||||
|
deployment: DeploymentManager,
|
||||||
|
simulationEnvironment: SimulationEnvironment,
|
||||||
|
): FunctionAssertion<[string], WithdrawDelegatorRewardsBeforeInfo, void> {
|
||||||
|
const { stakingWrapper } = deployment.staking;
|
||||||
|
|
||||||
|
return new FunctionAssertion(stakingWrapper, 'withdrawDelegatorRewards', {
|
||||||
|
before: async (args: [string], txData: Partial<TxData>) => {
|
||||||
|
const [poolId] = args;
|
||||||
|
|
||||||
|
const delegatorStake = await stakingWrapper
|
||||||
|
.getStakeDelegatedToPoolByOwner(txData.from as string, poolId)
|
||||||
|
.callAsync();
|
||||||
|
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||||
|
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
|
||||||
|
return { delegatorStake, poolRewards, wethReservedForPoolRewards };
|
||||||
|
},
|
||||||
|
after: async (
|
||||||
|
beforeInfo: WithdrawDelegatorRewardsBeforeInfo,
|
||||||
|
result: FunctionResult,
|
||||||
|
args: [string],
|
||||||
|
txData: Partial<TxData>,
|
||||||
|
) => {
|
||||||
|
// Ensure that the tx succeeded.
|
||||||
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||||
|
|
||||||
|
const [poolId] = args;
|
||||||
|
const { currentEpoch } = simulationEnvironment;
|
||||||
|
|
||||||
|
// Check that delegator stake has been synced
|
||||||
|
const expectedDelegatorStake = loadCurrentBalance(beforeInfo.delegatorStake, currentEpoch);
|
||||||
|
const delegatorStake = await stakingWrapper
|
||||||
|
.getStakeDelegatedToPoolByOwner(txData.from as string, poolId)
|
||||||
|
.callAsync();
|
||||||
|
expect(delegatorStake).to.deep.equal(expectedDelegatorStake);
|
||||||
|
|
||||||
|
// Check that pool rewards have been updated to reflect the amount withdrawn.
|
||||||
|
const transferEvents = filterLogsToArguments<WETH9TransferEventArgs>(
|
||||||
|
result.receipt!.logs, // tslint:disable-line:no-non-null-assertion
|
||||||
|
WETH9Events.Transfer,
|
||||||
|
);
|
||||||
|
const expectedPoolRewards =
|
||||||
|
transferEvents.length > 0
|
||||||
|
? beforeInfo.poolRewards.minus(transferEvents[0]._value)
|
||||||
|
: beforeInfo.poolRewards;
|
||||||
|
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||||
|
expect(poolRewards).to.bignumber.equal(expectedPoolRewards);
|
||||||
|
|
||||||
|
// TODO: Check CR
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -1,5 +1,5 @@
|
|||||||
import { BaseContract } from '@0x/base-contract';
|
import { BaseContract } from '@0x/base-contract';
|
||||||
import { constants, expect, TokenBalances } from '@0x/contracts-test-utils';
|
import { constants, expect, replaceKeysDeep, TokenBalances } from '@0x/contracts-test-utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { TokenAddresses, TokenContractsByName, TokenOwnersByName } from './types';
|
import { TokenAddresses, TokenContractsByName, TokenOwnersByName } from './types';
|
||||||
@@ -68,6 +68,14 @@ export class BalanceStore {
|
|||||||
this._addressNames = _.cloneDeep(balanceStore._addressNames);
|
this._addressNames = _.cloneDeep(balanceStore._addressNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a version of balances where keys are replaced with their readable counterparts, if
|
||||||
|
* they exist.
|
||||||
|
*/
|
||||||
|
public toReadable(): _.Dictionary<{}> {
|
||||||
|
return replaceKeysDeep(this.balances, this._readableAddressName.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the human-readable name for the given address, if it exists.
|
* Returns the human-readable name for the given address, if it exists.
|
||||||
* @param address The address to get the name for.
|
* @param address The address to get the name for.
|
||||||
|
@@ -25,7 +25,7 @@ import { BigNumber } from '@0x/utils';
|
|||||||
import { TxData } from 'ethereum-types';
|
import { TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { AssetProxyDispatcher, Authorizable, Ownable } from './wrapper_interfaces';
|
import { AssetProxyDispatcher, Authorizable, Ownable } from './utils/wrapper_interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a batch of authorities to a list of authorizable contracts.
|
* Adds a batch of authorities to a list of authorizable contracts.
|
||||||
@@ -393,7 +393,16 @@ export class DeploymentManager {
|
|||||||
stakingLogic.address,
|
stakingLogic.address,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stakingWrapper = new TestStakingContract(stakingProxy.address, environment.provider, txDefaults);
|
const logDecoderDependencies = _.mapValues(
|
||||||
|
{ ...stakingArtifacts, ...ERC20Artifacts },
|
||||||
|
v => v.compilerOutput.abi,
|
||||||
|
);
|
||||||
|
const stakingWrapper = new TestStakingContract(
|
||||||
|
stakingProxy.address,
|
||||||
|
environment.provider,
|
||||||
|
txDefaults,
|
||||||
|
logDecoderDependencies,
|
||||||
|
);
|
||||||
|
|
||||||
// Add the zrx vault and the weth contract to the staking proxy.
|
// Add the zrx vault and the weth contract to the staking proxy.
|
||||||
await stakingWrapper.setWethContract(tokens.weth.address).awaitTransactionSuccessAsync({ from: owner });
|
await stakingWrapper.setWethContract(tokens.weth.address).awaitTransactionSuccessAsync({ from: owner });
|
||||||
|
@@ -1,10 +1,17 @@
|
|||||||
import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking';
|
import {
|
||||||
import * as _ from 'lodash';
|
constants as stakingConstants,
|
||||||
|
GlobalStakeByStatus,
|
||||||
|
StakeStatus,
|
||||||
|
StakingPoolById,
|
||||||
|
StoredBalance,
|
||||||
|
} from '@0x/contracts-staking';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { Maker } from './actors/maker';
|
import { Actor } from './actors/base';
|
||||||
import { AssertionResult } from './assertions/function_assertion';
|
import { AssertionResult } from './assertions/function_assertion';
|
||||||
import { BlockchainBalanceStore } from './balances/blockchain_balance_store';
|
import { BlockchainBalanceStore } from './balances/blockchain_balance_store';
|
||||||
import { DeploymentManager } from './deployment_manager';
|
import { DeploymentManager } from './deployment_manager';
|
||||||
|
import { logger } from './utils/logger';
|
||||||
|
|
||||||
// tslint:disable:max-classes-per-file
|
// tslint:disable:max-classes-per-file
|
||||||
|
|
||||||
@@ -14,12 +21,29 @@ export class SimulationEnvironment {
|
|||||||
[StakeStatus.Delegated]: new StoredBalance(),
|
[StakeStatus.Delegated]: new StoredBalance(),
|
||||||
};
|
};
|
||||||
public stakingPools: StakingPoolById = {};
|
public stakingPools: StakingPoolById = {};
|
||||||
|
public currentEpoch: BigNumber = stakingConstants.INITIAL_EPOCH;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly deployment: DeploymentManager,
|
public readonly deployment: DeploymentManager,
|
||||||
public balanceStore: BlockchainBalanceStore,
|
public balanceStore: BlockchainBalanceStore,
|
||||||
public marketMakers: Maker[] = [],
|
public readonly actors: Actor[] = [],
|
||||||
) {}
|
) {
|
||||||
|
for (const actor of actors) {
|
||||||
|
// Set the actor's simulation environment
|
||||||
|
actor.simulationEnvironment = this;
|
||||||
|
// Register each actor in the balance store
|
||||||
|
this.balanceStore.registerTokenOwner(actor.address, actor.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public state(): any {
|
||||||
|
return {
|
||||||
|
globalStake: this.globalStake,
|
||||||
|
stakingPools: this.stakingPools,
|
||||||
|
balanceStore: this.balanceStore.toReadable(),
|
||||||
|
currentEpoch: this.currentEpoch,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Simulation {
|
export abstract class Simulation {
|
||||||
@@ -30,14 +54,23 @@ export abstract class Simulation {
|
|||||||
public async fuzzAsync(steps?: number): Promise<void> {
|
public async fuzzAsync(steps?: number): Promise<void> {
|
||||||
if (steps !== undefined) {
|
if (steps !== undefined) {
|
||||||
for (let i = 0; i < steps; i++) {
|
for (let i = 0; i < steps; i++) {
|
||||||
await this.generator.next();
|
await this._stepAsync();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (true) {
|
while (true) {
|
||||||
await this.generator.next();
|
await this._stepAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract _assertionGenerator(): AsyncIterableIterator<AssertionResult | void>;
|
protected abstract _assertionGenerator(): AsyncIterableIterator<AssertionResult | void>;
|
||||||
|
|
||||||
|
private async _stepAsync(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.generator.next();
|
||||||
|
} catch (error) {
|
||||||
|
logger.logFailure(error, this.environment.state());
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { constants as stakingConstants } from '@0x/contracts-staking';
|
|||||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||||
|
|
||||||
import { DeploymentManager } from '../deployment_manager';
|
import { DeploymentManager } from '../deployment_manager';
|
||||||
import { Authorizable, Ownable } from '../wrapper_interfaces';
|
import { Authorizable, Ownable } from '../utils/wrapper_interfaces';
|
||||||
|
|
||||||
blockchainTests('Deployment Manager', env => {
|
blockchainTests('Deployment Manager', env => {
|
||||||
let owner: string;
|
let owner: string;
|
||||||
|
@@ -23,14 +23,11 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
describe('executeAsync', () => {
|
describe('executeAsync', () => {
|
||||||
it('should call the before function with the provided arguments', async () => {
|
it('should call the before function with the provided arguments', async () => {
|
||||||
let sideEffectTarget = ZERO_AMOUNT;
|
let sideEffectTarget = ZERO_AMOUNT;
|
||||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||||
exampleContract.returnInteger.bind(exampleContract),
|
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||||
{
|
sideEffectTarget = randomInput;
|
||||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
|
||||||
sideEffectTarget = randomInput;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.executeAsync([randomInput], {});
|
await assertion.executeAsync([randomInput], {});
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
@@ -38,26 +35,23 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
|
|
||||||
it('should call the after function with the provided arguments', async () => {
|
it('should call the after function with the provided arguments', async () => {
|
||||||
let sideEffectTarget = ZERO_AMOUNT;
|
let sideEffectTarget = ZERO_AMOUNT;
|
||||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||||
exampleContract.returnInteger.bind(exampleContract),
|
after: async (
|
||||||
{
|
_beforeInfo: any,
|
||||||
after: async (
|
_result: FunctionResult,
|
||||||
_beforeInfo: any,
|
args: [BigNumber],
|
||||||
_result: FunctionResult,
|
txData: Partial<TxData>,
|
||||||
args: [BigNumber],
|
) => {
|
||||||
txData: Partial<TxData>,
|
[sideEffectTarget] = args;
|
||||||
) => {
|
|
||||||
[sideEffectTarget] = args;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.executeAsync([randomInput], {});
|
await assertion.executeAsync([randomInput], {});
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fail immediately if the wrapped function fails', async () => {
|
it('should not fail immediately if the wrapped function fails', async () => {
|
||||||
const assertion = new FunctionAssertion<[], {}, void>(exampleContract.emptyRevert.bind(exampleContract));
|
const assertion = new FunctionAssertion<[], {}, void>(exampleContract, 'emptyRevert');
|
||||||
await assertion.executeAsync([], {});
|
await assertion.executeAsync([], {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +59,8 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
let sideEffectTarget = ZERO_AMOUNT;
|
let sideEffectTarget = ZERO_AMOUNT;
|
||||||
const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>(
|
const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>(
|
||||||
exampleContract.returnInteger.bind(exampleContract),
|
exampleContract,
|
||||||
|
'returnInteger',
|
||||||
{
|
{
|
||||||
before: async (_args: [BigNumber], _txData: Partial<TxData>) => {
|
before: async (_args: [BigNumber], _txData: Partial<TxData>) => {
|
||||||
return randomInput;
|
return randomInput;
|
||||||
@@ -86,19 +81,16 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
|
|
||||||
it('should pass the result from the function call to "after"', async () => {
|
it('should pass the result from the function call to "after"', async () => {
|
||||||
let sideEffectTarget = ZERO_AMOUNT;
|
let sideEffectTarget = ZERO_AMOUNT;
|
||||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||||
exampleContract.returnInteger.bind(exampleContract),
|
after: async (
|
||||||
{
|
_beforeInfo: any,
|
||||||
after: async (
|
result: FunctionResult,
|
||||||
_beforeInfo: any,
|
_args: [BigNumber],
|
||||||
result: FunctionResult,
|
_txData: Partial<TxData>,
|
||||||
_args: [BigNumber],
|
) => {
|
||||||
_txData: Partial<TxData>,
|
sideEffectTarget = result.data;
|
||||||
) => {
|
|
||||||
sideEffectTarget = result.data;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.executeAsync([randomInput], {});
|
await assertion.executeAsync([randomInput], {});
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
@@ -106,21 +98,13 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
|
|
||||||
it('should pass the receipt from the function call to "after"', async () => {
|
it('should pass the receipt from the function call to "after"', async () => {
|
||||||
let sideEffectTarget: TransactionReceiptWithDecodedLogs;
|
let sideEffectTarget: TransactionReceiptWithDecodedLogs;
|
||||||
const assertion = new FunctionAssertion<[string], void, void>(
|
const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'emitEvent', {
|
||||||
exampleContract.emitEvent.bind(exampleContract),
|
after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial<TxData>) => {
|
||||||
{
|
if (result.receipt) {
|
||||||
after: async (
|
sideEffectTarget = result.receipt;
|
||||||
_beforeInfo: any,
|
}
|
||||||
result: FunctionResult,
|
|
||||||
_args: [string],
|
|
||||||
_txData: Partial<TxData>,
|
|
||||||
) => {
|
|
||||||
if (result.receipt) {
|
|
||||||
sideEffectTarget = result.receipt;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
const input = 'emitted data';
|
const input = 'emitted data';
|
||||||
await assertion.executeAsync([input], {});
|
await assertion.executeAsync([input], {});
|
||||||
@@ -135,19 +119,11 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
|
|
||||||
it('should pass the error to "after" if the function call fails', async () => {
|
it('should pass the error to "after" if the function call fails', async () => {
|
||||||
let sideEffectTarget: Error;
|
let sideEffectTarget: Error;
|
||||||
const assertion = new FunctionAssertion<[string], void, void>(
|
const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'stringRevert', {
|
||||||
exampleContract.stringRevert.bind(exampleContract),
|
after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial<TxData>) => {
|
||||||
{
|
sideEffectTarget = result.data;
|
||||||
after: async (
|
|
||||||
_beforeInfo: any,
|
|
||||||
result: FunctionResult,
|
|
||||||
_args: [string],
|
|
||||||
_txData: Partial<TxData>,
|
|
||||||
) => {
|
|
||||||
sideEffectTarget = result.data;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
const message = 'error message';
|
const message = 'error message';
|
||||||
await assertion.executeAsync([message], {});
|
await assertion.executeAsync([message], {});
|
||||||
|
|
||||||
|
55
contracts/integrations/test/framework/utils/logger.ts
Normal file
55
contracts/integrations/test/framework/utils/logger.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { TxData } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { Pseudorandom } from '../utils/pseudorandom';
|
||||||
|
|
||||||
|
// tslint:disable:no-console
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
private _step = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.warn(
|
||||||
|
JSON.stringify({
|
||||||
|
level: 'info',
|
||||||
|
time: new Date(),
|
||||||
|
msg: `Pseudorandom seed: ${Pseudorandom.seed}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Logs the name of the function executed, the arguments and transaction data it was
|
||||||
|
* called with, and the current step of the simulation.
|
||||||
|
*/
|
||||||
|
public logFunctionAssertion(functionName: string, functionArgs: any[], txData: Partial<TxData>): void {
|
||||||
|
console.warn(
|
||||||
|
JSON.stringify({
|
||||||
|
level: 'info',
|
||||||
|
time: new Date(),
|
||||||
|
msg: `Function called: ${functionName}(${functionArgs
|
||||||
|
.map(arg => JSON.stringify(arg).replace(/"/g, "'"))
|
||||||
|
.join(', ')})`,
|
||||||
|
step: ++this._step,
|
||||||
|
txData,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Logs information about a assertion failure. Dumps the error thrown and arbitrary data from
|
||||||
|
* the calling context.
|
||||||
|
*/
|
||||||
|
public logFailure(error: Error, data: string): void {
|
||||||
|
console.warn(
|
||||||
|
JSON.stringify({
|
||||||
|
level: 'error',
|
||||||
|
time: new Date(),
|
||||||
|
step: this._step,
|
||||||
|
error,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = new Logger();
|
54
contracts/integrations/test/framework/utils/pseudorandom.ts
Normal file
54
contracts/integrations/test/framework/utils/pseudorandom.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Numberish } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as seedrandom from 'seedrandom';
|
||||||
|
|
||||||
|
class PRNGWrapper {
|
||||||
|
public readonly seed = process.env.SEED || Math.random().toString();
|
||||||
|
private readonly _rng = seedrandom(this.seed);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pseudorandom version of _.sample. Picks an element of the given array with uniform probability.
|
||||||
|
* Return undefined if the array is empty.
|
||||||
|
*/
|
||||||
|
public sample<T>(arr: T[]): T | undefined {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const index = Math.abs(this._rng.int32()) % arr.length;
|
||||||
|
return arr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pseudorandom version of _.sampleSize. Returns an array of `n` samples from the given array
|
||||||
|
* (with replacement), chosen with uniform probability. Return undefined if the array is empty.
|
||||||
|
*/
|
||||||
|
public sampleSize<T>(arr: T[], n: number): T[] | undefined {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const samples = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
samples.push(this.sample(arr) as T);
|
||||||
|
}
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable:unified-signatures
|
||||||
|
/*
|
||||||
|
* Pseudorandom version of getRandomPortion/getRandomInteger. If two arguments are provided,
|
||||||
|
* treats those arguments as the min and max (inclusive) of the desired range. If only one
|
||||||
|
* argument is provided, picks an integer between 0 and the argument.
|
||||||
|
*/
|
||||||
|
public integer(max: Numberish): BigNumber;
|
||||||
|
public integer(min: Numberish, max: Numberish): BigNumber;
|
||||||
|
public integer(a: Numberish, b?: Numberish): BigNumber {
|
||||||
|
if (b === undefined) {
|
||||||
|
return new BigNumber(this._rng()).times(a).integerValue(BigNumber.ROUND_HALF_UP);
|
||||||
|
} else {
|
||||||
|
const range = new BigNumber(b).minus(a);
|
||||||
|
return this.integer(range).plus(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Pseudorandom = new PRNGWrapper();
|
@@ -1,34 +1,36 @@
|
|||||||
import { blockchainTests } from '@0x/contracts-test-utils';
|
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { Actor } from '../framework/actors/base';
|
import { Actor } from '../framework/actors/base';
|
||||||
import { PoolOperator } from '../framework/actors/pool_operator';
|
import { PoolOperator } from '../framework/actors/pool_operator';
|
||||||
|
import { filterActorsByRole } from '../framework/actors/utils';
|
||||||
import { AssertionResult } from '../framework/assertions/function_assertion';
|
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||||
import { DeploymentManager } from '../framework/deployment_manager';
|
import { DeploymentManager } from '../framework/deployment_manager';
|
||||||
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||||
|
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||||
|
|
||||||
export class PoolManagementSimulation extends Simulation {
|
export class PoolManagementSimulation extends Simulation {
|
||||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
const { deployment } = this.environment;
|
const { actors } = this.environment;
|
||||||
const operator = new PoolOperator({
|
const operators = filterActorsByRole(actors, PoolOperator);
|
||||||
name: 'Operator',
|
|
||||||
deployment,
|
|
||||||
simulationEnvironment: this.environment,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
operator.simulationActions.validCreateStakingPool,
|
...operators.map(operator => operator.simulationActions.validCreateStakingPool),
|
||||||
operator.simulationActions.validDecreaseStakingPoolOperatorShare,
|
...operators.map(operator => operator.simulationActions.validDecreaseStakingPoolOperatorShare),
|
||||||
];
|
];
|
||||||
while (true) {
|
while (true) {
|
||||||
const action = _.sample(actions);
|
const action = Pseudorandom.sample(actions);
|
||||||
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockchainTests.skip('Pool management fuzz test', env => {
|
blockchainTests('Pool management fuzz test', env => {
|
||||||
|
before(function(): void {
|
||||||
|
if (process.env.FUZZ_TEST !== 'pool_management') {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
});
|
||||||
after(async () => {
|
after(async () => {
|
||||||
Actor.reset();
|
Actor.reset();
|
||||||
});
|
});
|
||||||
@@ -41,8 +43,12 @@ blockchainTests.skip('Pool management fuzz test', env => {
|
|||||||
});
|
});
|
||||||
const balanceStore = new BlockchainBalanceStore({}, {});
|
const balanceStore = new BlockchainBalanceStore({}, {});
|
||||||
|
|
||||||
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
|
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, [
|
||||||
const simulation = new PoolManagementSimulation(simulationEnv);
|
new PoolOperator({ deployment, name: 'Operator 1' }),
|
||||||
|
new PoolOperator({ deployment, name: 'Operator 2' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const simulation = new PoolManagementSimulation(simulationEnvironment);
|
||||||
return simulation.fuzzAsync();
|
return simulation.fuzzAsync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,76 +1,58 @@
|
|||||||
import { blockchainTests, constants } from '@0x/contracts-test-utils';
|
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
|
import { Actor } from '../framework/actors/base';
|
||||||
import { MakerTaker } from '../framework/actors/hybrids';
|
import { MakerTaker } from '../framework/actors/hybrids';
|
||||||
import { Maker } from '../framework/actors/maker';
|
import { Maker } from '../framework/actors/maker';
|
||||||
|
import { PoolOperator } from '../framework/actors/pool_operator';
|
||||||
|
import { Taker } from '../framework/actors/taker';
|
||||||
|
import { filterActorsByRole } from '../framework/actors/utils';
|
||||||
import { AssertionResult } from '../framework/assertions/function_assertion';
|
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||||
import { DeploymentManager } from '../framework/deployment_manager';
|
import { DeploymentManager } from '../framework/deployment_manager';
|
||||||
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||||
|
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||||
|
|
||||||
import { PoolManagementSimulation } from './pool_management_test';
|
import { PoolManagementSimulation } from './pool_management_test';
|
||||||
|
|
||||||
class PoolMembershipSimulation extends Simulation {
|
export class PoolMembershipSimulation extends Simulation {
|
||||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
const { deployment } = this.environment;
|
const { actors } = this.environment;
|
||||||
|
const makers = filterActorsByRole(actors, Maker);
|
||||||
|
const takers = filterActorsByRole(actors, Taker);
|
||||||
|
|
||||||
const poolManagement = new PoolManagementSimulation(this.environment);
|
const poolManagement = new PoolManagementSimulation(this.environment);
|
||||||
|
|
||||||
const member = new MakerTaker({
|
|
||||||
name: 'member',
|
|
||||||
deployment,
|
|
||||||
simulationEnvironment: this.environment,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
member.simulationActions.validJoinStakingPool,
|
...makers.map(maker => maker.simulationActions.validJoinStakingPool),
|
||||||
member.simulationActions.validFillOrderCompleteFill,
|
...takers.map(taker => taker.simulationActions.validFillOrder),
|
||||||
poolManagement.generator,
|
poolManagement.generator,
|
||||||
];
|
];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const action = _.sample(actions);
|
const action = Pseudorandom.sample(actions);
|
||||||
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockchainTests.skip('pool membership fuzz test', env => {
|
blockchainTests('pool membership fuzz test', env => {
|
||||||
let deployment: DeploymentManager;
|
before(async function(): Promise<void> {
|
||||||
let maker: Maker;
|
if (process.env.FUZZ_TEST !== 'pool_membership') {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
before(async () => {
|
after(async () => {
|
||||||
deployment = await DeploymentManager.deployAsync(env, {
|
Actor.reset();
|
||||||
numErc20TokensToDeploy: 2,
|
});
|
||||||
|
|
||||||
|
it('fuzz', async () => {
|
||||||
|
const deployment = await DeploymentManager.deployAsync(env, {
|
||||||
|
numErc20TokensToDeploy: 4,
|
||||||
numErc721TokensToDeploy: 0,
|
numErc721TokensToDeploy: 0,
|
||||||
numErc1155TokensToDeploy: 0,
|
numErc1155TokensToDeploy: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const makerToken = deployment.tokens.erc20[0];
|
|
||||||
const takerToken = deployment.tokens.erc20[1];
|
|
||||||
|
|
||||||
const orderConfig = {
|
|
||||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
|
||||||
makerAssetData: deployment.assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(),
|
|
||||||
takerAssetData: deployment.assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(),
|
|
||||||
makerFeeAssetData: deployment.assetDataEncoder
|
|
||||||
.ERC20Token(makerToken.address)
|
|
||||||
.getABIEncodedTransactionData(),
|
|
||||||
takerFeeAssetData: deployment.assetDataEncoder
|
|
||||||
.ERC20Token(takerToken.address)
|
|
||||||
.getABIEncodedTransactionData(),
|
|
||||||
makerFee: constants.ZERO_AMOUNT,
|
|
||||||
takerFee: constants.ZERO_AMOUNT,
|
|
||||||
};
|
|
||||||
|
|
||||||
maker = new Maker({
|
|
||||||
name: 'maker',
|
|
||||||
deployment,
|
|
||||||
orderConfig,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fuzz', async () => {
|
|
||||||
const balanceStore = new BlockchainBalanceStore(
|
const balanceStore = new BlockchainBalanceStore(
|
||||||
{
|
{
|
||||||
StakingProxy: deployment.staking.stakingProxy.address,
|
StakingProxy: deployment.staking.stakingProxy.address,
|
||||||
@@ -79,8 +61,24 @@ blockchainTests.skip('pool membership fuzz test', env => {
|
|||||||
{ erc20: { ZRX: deployment.tokens.zrx } },
|
{ erc20: { ZRX: deployment.tokens.zrx } },
|
||||||
);
|
);
|
||||||
|
|
||||||
const simulationEnv = new SimulationEnvironment(deployment, balanceStore, [maker]);
|
const actors = [
|
||||||
const simulation = new PoolMembershipSimulation(simulationEnv);
|
new Maker({ deployment, name: 'Maker 1' }),
|
||||||
|
new Maker({ deployment, name: 'Maker 2' }),
|
||||||
|
new Taker({ deployment, name: 'Taker 1' }),
|
||||||
|
new Taker({ deployment, name: 'Taker 2' }),
|
||||||
|
new MakerTaker({ deployment, name: 'Maker/Taker' }),
|
||||||
|
new PoolOperator({ deployment, name: 'Operator 1' }),
|
||||||
|
new PoolOperator({ deployment, name: 'Operator 2' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, actors);
|
||||||
|
|
||||||
|
const takers = filterActorsByRole(actors, Taker);
|
||||||
|
for (const taker of takers) {
|
||||||
|
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
const simulation = new PoolMembershipSimulation(simulationEnvironment);
|
||||||
return simulation.fuzzAsync();
|
return simulation.fuzzAsync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,38 +1,45 @@
|
|||||||
import { blockchainTests } from '@0x/contracts-test-utils';
|
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { Actor } from '../framework/actors/base';
|
import { Actor } from '../framework/actors/base';
|
||||||
|
import { StakerOperator } from '../framework/actors/hybrids';
|
||||||
|
import { PoolOperator } from '../framework/actors/pool_operator';
|
||||||
import { Staker } from '../framework/actors/staker';
|
import { Staker } from '../framework/actors/staker';
|
||||||
|
import { filterActorsByRole } from '../framework/actors/utils';
|
||||||
import { AssertionResult } from '../framework/assertions/function_assertion';
|
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||||
import { DeploymentManager } from '../framework/deployment_manager';
|
import { DeploymentManager } from '../framework/deployment_manager';
|
||||||
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||||
|
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||||
|
|
||||||
import { PoolManagementSimulation } from './pool_management_test';
|
import { PoolManagementSimulation } from './pool_management_test';
|
||||||
|
|
||||||
export class StakeManagementSimulation extends Simulation {
|
export class StakeManagementSimulation extends Simulation {
|
||||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
const { deployment, balanceStore } = this.environment;
|
const { actors } = this.environment;
|
||||||
|
const stakers = filterActorsByRole(actors, Staker);
|
||||||
|
|
||||||
const poolManagement = new PoolManagementSimulation(this.environment);
|
const poolManagement = new PoolManagementSimulation(this.environment);
|
||||||
|
|
||||||
const staker = new Staker({ name: 'Staker', deployment, simulationEnvironment: this.environment });
|
|
||||||
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
|
|
||||||
balanceStore.registerTokenOwner(staker.address, staker.name);
|
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
staker.simulationActions.validStake,
|
...stakers.map(staker => staker.simulationActions.validStake),
|
||||||
staker.simulationActions.validUnstake,
|
...stakers.map(staker => staker.simulationActions.validUnstake),
|
||||||
staker.simulationActions.validMoveStake,
|
...stakers.map(staker => staker.simulationActions.validMoveStake),
|
||||||
poolManagement.generator,
|
poolManagement.generator,
|
||||||
];
|
];
|
||||||
while (true) {
|
while (true) {
|
||||||
const action = _.sample(actions);
|
const action = Pseudorandom.sample(actions);
|
||||||
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockchainTests.skip('Stake management fuzz test', env => {
|
blockchainTests('Stake management fuzz test', env => {
|
||||||
|
before(function(): void {
|
||||||
|
if (process.env.FUZZ_TEST !== 'stake_management') {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
Actor.reset();
|
Actor.reset();
|
||||||
});
|
});
|
||||||
@@ -51,8 +58,21 @@ blockchainTests.skip('Stake management fuzz test', env => {
|
|||||||
{ erc20: { ZRX: deployment.tokens.zrx } },
|
{ erc20: { ZRX: deployment.tokens.zrx } },
|
||||||
);
|
);
|
||||||
|
|
||||||
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
|
const actors = [
|
||||||
const simulation = new StakeManagementSimulation(simulationEnv);
|
new Staker({ name: 'Staker 1', deployment }),
|
||||||
|
new Staker({ name: 'Staker 2', deployment }),
|
||||||
|
new StakerOperator({ name: 'Staker/Operator', deployment }),
|
||||||
|
new PoolOperator({ name: 'Operator', deployment }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, actors);
|
||||||
|
|
||||||
|
const stakers = filterActorsByRole(actors, Staker);
|
||||||
|
for (const staker of stakers) {
|
||||||
|
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
|
||||||
|
}
|
||||||
|
|
||||||
|
const simulation = new StakeManagementSimulation(simulationEnvironment);
|
||||||
return simulation.fuzzAsync();
|
return simulation.fuzzAsync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
120
contracts/integrations/test/fuzz_tests/staking_rewards_test.ts
Normal file
120
contracts/integrations/test/fuzz_tests/staking_rewards_test.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||||
|
|
||||||
|
import { Actor } from '../framework/actors/base';
|
||||||
|
import {
|
||||||
|
MakerTaker,
|
||||||
|
OperatorStakerMaker,
|
||||||
|
StakerKeeper,
|
||||||
|
StakerMaker,
|
||||||
|
StakerOperator,
|
||||||
|
} from '../framework/actors/hybrids';
|
||||||
|
import { Keeper } from '../framework/actors/keeper';
|
||||||
|
import { Maker } from '../framework/actors/maker';
|
||||||
|
import { PoolOperator } from '../framework/actors/pool_operator';
|
||||||
|
import { Staker } from '../framework/actors/staker';
|
||||||
|
import { Taker } from '../framework/actors/taker';
|
||||||
|
import { filterActorsByRole } from '../framework/actors/utils';
|
||||||
|
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||||
|
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||||
|
import { DeploymentManager } from '../framework/deployment_manager';
|
||||||
|
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||||
|
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||||
|
|
||||||
|
import { PoolMembershipSimulation } from './pool_membership_test';
|
||||||
|
import { StakeManagementSimulation } from './stake_management_test';
|
||||||
|
|
||||||
|
export class StakingRewardsSimulation extends Simulation {
|
||||||
|
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||||
|
const { actors } = this.environment;
|
||||||
|
const stakers = filterActorsByRole(actors, Staker);
|
||||||
|
const keepers = filterActorsByRole(actors, Keeper);
|
||||||
|
|
||||||
|
const poolMembership = new PoolMembershipSimulation(this.environment);
|
||||||
|
const stakeManagement = new StakeManagementSimulation(this.environment);
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
...stakers.map(staker => staker.simulationActions.validWithdrawDelegatorRewards),
|
||||||
|
...keepers.map(keeper => keeper.simulationActions.validFinalizePool),
|
||||||
|
...keepers.map(keeper => keeper.simulationActions.validEndEpoch),
|
||||||
|
poolMembership.generator,
|
||||||
|
stakeManagement.generator,
|
||||||
|
];
|
||||||
|
while (true) {
|
||||||
|
const action = Pseudorandom.sample(actions);
|
||||||
|
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockchainTests('Staking rewards fuzz test', env => {
|
||||||
|
before(function(): void {
|
||||||
|
if (process.env.FUZZ_TEST !== 'staking_rewards') {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
Actor.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fuzz', async () => {
|
||||||
|
// Deploy contracts
|
||||||
|
const deployment = await DeploymentManager.deployAsync(env, {
|
||||||
|
numErc20TokensToDeploy: 4,
|
||||||
|
numErc721TokensToDeploy: 0,
|
||||||
|
numErc1155TokensToDeploy: 0,
|
||||||
|
});
|
||||||
|
const [ERC20TokenA, ERC20TokenB, ERC20TokenC, ERC20TokenD] = deployment.tokens.erc20;
|
||||||
|
|
||||||
|
// Set up balance store
|
||||||
|
const balanceStore = new BlockchainBalanceStore(
|
||||||
|
{
|
||||||
|
StakingProxy: deployment.staking.stakingProxy.address,
|
||||||
|
ZRXVault: deployment.staking.zrxVault.address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
erc20: {
|
||||||
|
ZRX: deployment.tokens.zrx,
|
||||||
|
WETH: deployment.tokens.weth,
|
||||||
|
ERC20TokenA,
|
||||||
|
ERC20TokenB,
|
||||||
|
ERC20TokenC,
|
||||||
|
ERC20TokenD,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Spin up actors
|
||||||
|
const actors = [
|
||||||
|
new Maker({ deployment, name: 'Maker 1' }),
|
||||||
|
new Maker({ deployment, name: 'Maker 2' }),
|
||||||
|
new Taker({ deployment, name: 'Taker 1' }),
|
||||||
|
new Taker({ deployment, name: 'Taker 2' }),
|
||||||
|
new MakerTaker({ deployment, name: 'Maker/Taker' }),
|
||||||
|
new Staker({ deployment, name: 'Staker 1' }),
|
||||||
|
new Staker({ deployment, name: 'Staker 2' }),
|
||||||
|
new Keeper({ deployment, name: 'Keeper' }),
|
||||||
|
new StakerKeeper({ deployment, name: 'Staker/Keeper' }),
|
||||||
|
new StakerMaker({ deployment, name: 'Staker/Maker' }),
|
||||||
|
new PoolOperator({ deployment, name: 'Pool Operator' }),
|
||||||
|
new StakerOperator({ deployment, name: 'Staker/Operator' }),
|
||||||
|
new OperatorStakerMaker({ deployment, name: 'Operator/Staker/Maker' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set up simulation environment
|
||||||
|
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, actors);
|
||||||
|
|
||||||
|
// Takers need to set a WETH allowance for the staking proxy in case they pay the protocol fee in WETH
|
||||||
|
const takers = filterActorsByRole(actors, Taker);
|
||||||
|
for (const taker of takers) {
|
||||||
|
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
|
||||||
|
}
|
||||||
|
// Stakers need to set a ZRX allowance to deposit their ZRX into the zrxVault
|
||||||
|
const stakers = filterActorsByRole(actors, Staker);
|
||||||
|
for (const staker of stakers) {
|
||||||
|
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
|
||||||
|
}
|
||||||
|
const simulation = new StakingRewardsSimulation(simulationEnvironment);
|
||||||
|
return simulation.fuzzAsync();
|
||||||
|
});
|
||||||
|
});
|
6
contracts/integrations/test/fuzz_tests/tslint.json
Normal file
6
contracts/integrations/test/fuzz_tests/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": ["@0x/tslint-config"],
|
||||||
|
"rules": {
|
||||||
|
"no-invalid-this": false
|
||||||
|
}
|
||||||
|
}
|
200
contracts/integrations/test/mainnet_configs_test.ts
Normal file
200
contracts/integrations/test/mainnet_configs_test.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { ERC20ProxyContract, MultiAssetProxyContract } from '@0x/contracts-asset-proxy';
|
||||||
|
import { StakingProxyContract, ZrxVaultContract } from '@0x/contracts-staking';
|
||||||
|
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
||||||
|
import { AssetProxyId } from '@0x/types';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
import { contractAddresses, contractWrappers } from './mainnet_fork_utils';
|
||||||
|
|
||||||
|
blockchainTests.resets.fork('Mainnet configs tests', env => {
|
||||||
|
describe('Exchange', () => {
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await contractWrappers.exchange.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('ERC20Proxy should be registered', async () => {
|
||||||
|
const erc20Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||||
|
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||||
|
});
|
||||||
|
it('ERC721Proxy should be registered', async () => {
|
||||||
|
const erc721Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC721).callAsync();
|
||||||
|
expect(erc721Proxy).to.eq(contractAddresses.erc721Proxy);
|
||||||
|
});
|
||||||
|
it('ERC1155Proxy should be registered', async () => {
|
||||||
|
const erc1155Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC1155).callAsync();
|
||||||
|
expect(erc1155Proxy).to.eq(contractAddresses.erc1155Proxy);
|
||||||
|
});
|
||||||
|
it('ERC20BridgeProxy should be registered', async () => {
|
||||||
|
const erc20BridgeProxy = await contractWrappers.exchange
|
||||||
|
.getAssetProxy(AssetProxyId.ERC20Bridge)
|
||||||
|
.callAsync();
|
||||||
|
expect(erc20BridgeProxy).to.eq(contractAddresses.erc20BridgeProxy);
|
||||||
|
});
|
||||||
|
it('MultiAssetProxy should be registered', async () => {
|
||||||
|
const multiAssetProxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.MultiAsset).callAsync();
|
||||||
|
expect(multiAssetProxy).to.eq(contractAddresses.multiAssetProxy);
|
||||||
|
});
|
||||||
|
it('StaticCallProxy should be registered', async () => {
|
||||||
|
const staticCallProxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.StaticCall).callAsync();
|
||||||
|
expect(staticCallProxy).to.eq(contractAddresses.staticCallProxy);
|
||||||
|
});
|
||||||
|
it('StakingProxy should be attached', async () => {
|
||||||
|
const stakingProxy = await contractWrappers.exchange.protocolFeeCollector().callAsync();
|
||||||
|
expect(stakingProxy).to.eq(contractAddresses.stakingProxy);
|
||||||
|
});
|
||||||
|
it('should have the correct protocol fee multiplier', async () => {
|
||||||
|
const protocolFeeMultiplier = await contractWrappers.exchange.protocolFeeMultiplier().callAsync();
|
||||||
|
expect(protocolFeeMultiplier).to.bignumber.eq(150000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ERC20Proxy', () => {
|
||||||
|
const erc20Proxy = new ERC20ProxyContract(contractAddresses.erc20Proxy, env.provider);
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await erc20Proxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await erc20Proxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(4);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.zrxVault), 'ZrxVault');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ERC721Proxy', () => {
|
||||||
|
const erc721Proxy = new ERC20ProxyContract(contractAddresses.erc721Proxy, env.provider);
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await erc721Proxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await erc721Proxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(3);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ERC1155Proxy', () => {
|
||||||
|
const erc1155Proxy = new ERC20ProxyContract(contractAddresses.erc1155Proxy, env.provider);
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await erc1155Proxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await erc1155Proxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(3);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ERC20BridgeProxy', () => {
|
||||||
|
const erc20BridgeProxy = new ERC20ProxyContract(contractAddresses.erc20BridgeProxy, env.provider);
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await erc20BridgeProxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await erc20BridgeProxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(2);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('MultiAssetProxy', () => {
|
||||||
|
const multiAssetProxy = new MultiAssetProxyContract(contractAddresses.multiAssetProxy, env.provider);
|
||||||
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
|
const owner = await multiAssetProxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await multiAssetProxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(2);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||||
|
});
|
||||||
|
it('ERC20Proxy should be registered', async () => {
|
||||||
|
const erc20Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||||
|
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||||
|
});
|
||||||
|
it('ERC721Proxy should be registered', async () => {
|
||||||
|
const erc721Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC721).callAsync();
|
||||||
|
expect(erc721Proxy).to.eq(contractAddresses.erc721Proxy);
|
||||||
|
});
|
||||||
|
it('ERC1155Proxy should be registered', async () => {
|
||||||
|
const erc1155Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC1155).callAsync();
|
||||||
|
expect(erc1155Proxy).to.eq(contractAddresses.erc1155Proxy);
|
||||||
|
});
|
||||||
|
it('ERC20BridgeProxy should be registered', async () => {
|
||||||
|
const erc20BridgeProxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC20Bridge).callAsync();
|
||||||
|
expect(erc20BridgeProxy).to.eq(contractAddresses.erc20BridgeProxy);
|
||||||
|
});
|
||||||
|
it('StaticCallProxy should be registered', async () => {
|
||||||
|
const staticCallProxy = await multiAssetProxy.getAssetProxy(AssetProxyId.StaticCall).callAsync();
|
||||||
|
expect(staticCallProxy).to.eq(contractAddresses.staticCallProxy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('StakingProxy', () => {
|
||||||
|
const stakingProxy = new StakingProxyContract(contractAddresses.stakingProxy, env.provider);
|
||||||
|
it('should be owned by ZeroExGovernor', async () => {
|
||||||
|
const owner = await stakingProxy.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('Staking contract should be attached', async () => {
|
||||||
|
const staking = await stakingProxy.stakingContract().callAsync();
|
||||||
|
expect(staking).to.eq(contractAddresses.staking);
|
||||||
|
});
|
||||||
|
it('Exchange should be registered', async () => {
|
||||||
|
const isRegistered = await contractWrappers.staking.validExchanges(contractAddresses.exchange).callAsync();
|
||||||
|
expect(isRegistered).to.be.true();
|
||||||
|
});
|
||||||
|
it('ZrxVault should be set', async () => {
|
||||||
|
const zrxVault = await contractWrappers.staking.getZrxVault().callAsync();
|
||||||
|
expect(zrxVault).to.eq(contractAddresses.zrxVault);
|
||||||
|
});
|
||||||
|
it('WETH should be set', async () => {
|
||||||
|
const weth = await contractWrappers.staking.getWethContract().callAsync();
|
||||||
|
expect(weth).to.eq(contractAddresses.etherToken);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await stakingProxy.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(1);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.zeroExGovernor), 'ZeroExGovernor');
|
||||||
|
});
|
||||||
|
it('should have the correct params set', async () => {
|
||||||
|
const params = await contractWrappers.staking.getParams().callAsync();
|
||||||
|
const epochDurationInSeconds = 10 * 24 * 60 * 60;
|
||||||
|
const rewardDelegatedStakeWeight = 10 ** 6 * 0.9;
|
||||||
|
const mimimumPoolStake = new BigNumber(10).pow(18).times(100);
|
||||||
|
const cobbDouglasAlphaNumerator = 2;
|
||||||
|
const cobbDouglasAlphaDenominator = 3;
|
||||||
|
expect(params[0]).to.bignumber.eq(epochDurationInSeconds, 'epochDurationInSeconds');
|
||||||
|
expect(params[1]).to.bignumber.eq(rewardDelegatedStakeWeight, 'rewardDelegatedStakeWeight');
|
||||||
|
expect(params[2]).to.bignumber.eq(mimimumPoolStake, 'mimimumPoolStake');
|
||||||
|
expect(params[3]).to.bignumber.eq(cobbDouglasAlphaNumerator, 'cobbDouglasAlphaNumerator');
|
||||||
|
expect(params[4]).to.bignumber.eq(cobbDouglasAlphaDenominator, 'cobbDouglasAlphaDenominator');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ZrxVault', () => {
|
||||||
|
const zrxVault = new ZrxVaultContract(contractAddresses.zrxVault, env.provider);
|
||||||
|
it('should be owned by ZeroExGovernor', async () => {
|
||||||
|
const owner = await zrxVault.owner().callAsync();
|
||||||
|
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||||
|
});
|
||||||
|
it('should have the correct authorized addresses', async () => {
|
||||||
|
const authorizedAddresses = await zrxVault.getAuthorizedAddresses().callAsync();
|
||||||
|
expect(authorizedAddresses.length).to.eq(1);
|
||||||
|
expect(authorizedAddresses.includes(contractAddresses.zeroExGovernor), 'ZeroExGovernor');
|
||||||
|
});
|
||||||
|
it('ERC20Proxy should be set', async () => {
|
||||||
|
const erc20Proxy = await zrxVault.zrxAssetProxy().callAsync();
|
||||||
|
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||||
|
});
|
||||||
|
it('StakingProxy should be set', async () => {
|
||||||
|
const stakingProxy = await zrxVault.stakingProxyAddress().callAsync();
|
||||||
|
expect(stakingProxy).to.eq(contractAddresses.stakingProxy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
9
contracts/integrations/test/mainnet_fork_utils.ts
Normal file
9
contracts/integrations/test/mainnet_fork_utils.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
|
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||||
|
import { provider } from '@0x/contracts-test-utils';
|
||||||
|
|
||||||
|
const chainId = 1;
|
||||||
|
const contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
||||||
|
const contractWrappers = new ContractWrappers(provider, { chainId, contractAddresses });
|
||||||
|
|
||||||
|
export { contractAddresses, contractWrappers };
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "4.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v4.0.2 - _December 17, 2019_
|
||||||
|
|
||||||
|
* Dependencies updated
|
||||||
|
|
||||||
## v4.0.1 - _December 9, 2019_
|
## v4.0.1 - _December 9, 2019_
|
||||||
|
|
||||||
* Dependencies updated
|
* Dependencies updated
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/contracts-multisig",
|
"name": "@0x/contracts-multisig",
|
||||||
"version": "4.0.1",
|
"version": "4.0.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -49,18 +49,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/multisig/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/multisig/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.0.1",
|
"@0x/abi-gen": "^5.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.1",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-erc20": "^3.0.1",
|
"@0x/contracts-erc20": "^3.0.2",
|
||||||
"@0x/contracts-gen": "^2.0.1",
|
"@0x/contracts-gen": "^2.0.2",
|
||||||
"@0x/contracts-test-utils": "^5.0.0",
|
"@0x/contracts-test-utils": "^5.0.1",
|
||||||
"@0x/contracts-utils": "^4.0.1",
|
"@0x/contracts-utils": "^4.0.2",
|
||||||
"@0x/dev-utils": "^3.0.1",
|
"@0x/dev-utils": "^3.0.2",
|
||||||
"@0x/sol-compiler": "^4.0.1",
|
"@0x/sol-compiler": "^4.0.2",
|
||||||
"@0x/tslint-config": "^4.0.0",
|
"@0x/tslint-config": "^4.0.0",
|
||||||
"@0x/types": "^3.1.0",
|
"@0x/types": "^3.1.1",
|
||||||
"@0x/utils": "^5.1.0",
|
"@0x/utils": "^5.1.1",
|
||||||
"@0x/web3-wrapper": "^7.0.1",
|
"@0x/web3-wrapper": "^7.0.2",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.0.1",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/typescript-typings": "^5.0.0",
|
"@0x/typescript-typings": "^5.0.1",
|
||||||
"ethereum-types": "^3.0.0"
|
"ethereum-types": "^3.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 1576540892,
|
||||||
|
"version": "2.0.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Dependencies updated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1575931811,
|
"timestamp": 1575931811,
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user