Compare commits

...

44 Commits

Author SHA1 Message Date
xianny
51ca3109eb Publish
- @0x/contracts-asset-proxy@3.0.2
 - @0x/contracts-coordinator@3.0.2
 - @0x/contracts-dev-utils@1.0.2
 - @0x/contracts-erc1155@2.0.2
 - @0x/contracts-erc20-bridge-sampler@1.0.2
 - @0x/contracts-erc20@3.0.2
 - @0x/contracts-erc721@3.0.2
 - @0x/contracts-exchange-forwarder@4.0.2
 - @0x/contracts-exchange-libs@4.0.2
 - @0x/contracts-exchange@3.0.2
 - @0x/contracts-extensions@5.1.1
 - @0x/contracts-integrations@2.0.2
 - @0x/contracts-multisig@4.0.2
 - @0x/contracts-staking@2.0.2
 - @0x/contracts-test-utils@5.0.1
 - @0x/contracts-tests@0.0.8
 - @0x/contracts-utils@4.0.2
 - 0x.js@9.0.2
 - @0x/abi-gen@5.0.2
 - @0x/assert@3.0.2
 - @0x/asset-swapper@3.0.2
 - @0x/base-contract@6.0.2
 - @0x/connect@6.0.2
 - @0x/contract-addresses@4.1.0
 - @0x/contract-artifacts@3.2.0
 - @0x/contract-wrappers-test@12.2.3
 - @0x/contract-wrappers@13.2.0
 - @0x/contracts-gen@2.0.2
 - @0x/dev-utils@3.0.2
 - @0x/instant@1.0.39
 - @0x/json-schemas@5.0.2
 - @0x/migrations@5.0.2
 - @0x/monorepo-scripts@1.0.45
 - @0x/order-utils@10.0.1
 - @0x/orderbook@2.0.0
 - @0x/sol-compiler@4.0.2
 - @0x/sol-coverage@4.0.2
 - @0x/sol-doc@3.0.2
 - @0x/sol-profiler@4.0.2
 - @0x/sol-resolver@3.0.2
 - @0x/sol-trace@3.0.2
 - @0x/sol-tracing-utils@7.0.2
 - @0x/sra-spec@3.0.2
 - @0x/subproviders@6.0.2
 - @0x/types@3.1.1
 - @0x/typescript-typings@5.0.1
 - @0x/utils@5.1.1
 - @0x/web3-wrapper@7.0.2
2019-12-16 16:05:16 -08:00
xianny
2bcb79dc44 Updated CHANGELOGS & MD docs 2019-12-16 16:05:03 -08:00
xianny
ecec985649 pin python regex version 2019-12-16 14:22:48 -08:00
Lawrence Forman
994908549d Asset-swapper aggregator utils (#2353)
* `@0x/asset-swapper`: Add ERC20Bridge aggregator library.

* `@0x/asset-swapper`: Finish off `aggregate.ts`.

* `@0x/types`: Add `OrderWithoutDomain` type.

* `@0x/asset-swapper`: Add testing infra for sampler/aggregator.

* `@0x/types`: Add `SignedOrderWithoutDomain` type.

* `@0x/asset-swapper`: Update aggregator to take and return orders with signatures.

* `@0x/asset-swapper`: Fix broken aggregator tests.

* `@0x/asset-swapper`: Pass the sampler contract into aggregator entry points.

* `@0x/contract-artifacts`: Add `IERC20BridgeSampler` artifact.

* `@0x/contract-wrappers`: Add `IERC20BridgeSampler` wrapper.

* `@0x/asset-swapper`: Address review comments.

* fixed testing

* refactored aggregate.ts and embeded into asset-swapper

* added adjusted rates for taker and maker fees

* remove PrunedSignedOrders

* updated contract-addresses and addressed some other todos

* streamlined logic

* patched in lawrences changes

* renamed aggregator utils and removed market_utils.ts

* added ack heartbeats

* fixed bug

* patches

* added dummy order things

* Dummy with valid sig

* Tweak gas price calculation to wei

* added test coverage and fixed bugs

* fixed migrations

* Fix CHANGELOGs and types export

* Deploy latest ERC20BridgeSampler on Mainnet

* `@0x/types` Revert CHANGELOG.

* `@0x/asset-swapper`: Address review comments.
`@0x/contract-addresses`: Make kyber lowercase.

* made protocol fee multiplier async

* `@0x/asset-swapper: Fix build errors and do some code cleanup.

* use assetDataUtils where possible
2019-12-16 12:35:58 -08:00
Greg Hysz
6808e0d531 Merge pull request #2365 from 0xProject/feat/asset-proxy/DyDxBridge
dYdX Bridge
2019-12-13 13:46:06 -08:00
Greg Hysen
410c95308a Updated dydx account encoding to assume that all actions are on partial balances 2019-12-13 12:02:23 -08:00
Greg Hysen
bec1f23616 Fixed merge conflicts 2019-12-13 11:06:16 -08:00
Greg Hysen
34596b7f83 Use safeGetPartialAmountFloor 2019-12-13 10:58:22 -08:00
Greg Hysen
5ca7169ee5 Reverted to version of dydx bridge that only allows from to be the account owner 2019-12-13 10:58:22 -08:00
Greg Hysen
3300aaa1b9 Refactored so that deposits are done from taker asset data and withdrawals from maker asset data. 2019-12-13 10:58:22 -08:00
Greg Hysen
54afc8a4a1 Fixed merge conflicts 2019-12-13 10:58:22 -08:00
Greg Hysen
f19f4310f4 Updating bridge to work w/o MultiAssetProxy 2019-12-13 10:57:57 -08:00
Greg Hysen
444125a7e1 Simplified the dydx bridge implememtation that does not use the bridge as the maker. 2019-12-13 10:57:57 -08:00
Greg Hysen
56cbb69401 DyDx bridge implementation using contract as maker with signature validation. 2019-12-13 10:56:54 -08:00
Lawrence Forman
70870ffcd2 Swallow reverts in ERC20BridgeSampler (#2395)
* `@0x/erc20-bridge-sampler`: Do not query empty/unsigned orders. Swallow revets on DEX quotes.

* `@0x/contracts-utils`: Add `DEV_UTILS_ADDRESS` and `KYBER_ETH_ADDRESS` to `DeploymentConstants`.

* `@0x/contracts-erc20-bridge-sampler`: Address review comments.
2019-12-13 10:53:25 -08:00
mzhu25
a556d91673 Merge pull request #2387 from 0xProject/feature/fuzz/staking-rewards
`@0x/contracts-integrations`: Staking rewards fuzz test
2019-12-12 15:43:29 -08:00
Michael Zhu
8ecbde8e1e Chagne StoredBalance functions to not mutate in place 2019-12-12 15:21:42 -08:00
Michael Zhu
a24b293818 register actors in the SimulationEnvironment constructor 2019-12-12 14:38:07 -08:00
Xianny
cab5ebf94b re-enable coordinator client tests (#2394) 2019-12-12 14:36:52 -08:00
Jacob Evans
a54b5baef2 Merge pull request #2393 from 0xProject/feature/orderbook-orderstore-async
orderbook: Make OrderStore async for use in db adapter
2019-12-12 10:30:17 -08:00
Jacob Evans
c324fe204e Make OrderStore async for use in db adapter
CHANGELOGS
2019-12-12 09:43:07 -08:00
Amir Bandeali
37d972ed9e Merge pull request #2389 from 0xProject/feat/contracts/mainnet-fork
Allow mainnet fork to be used for contract tests
2019-12-11 22:50:09 -08:00
Michael Zhu
e4a3b1cb05 fix bug in LibFractions reference function 2019-12-11 18:12:02 -08:00
Michael Zhu
49538f272e address comments 2019-12-11 16:54:48 -08:00
mzhu25
1283232144 Merge pull request #2372 from 0xProject/feature/fuzz/prng
`@0x/contracts-integrations`: Seeded RNG and simulation logging
2019-12-11 10:23:08 -08:00
Michael Zhu
2f9891f0aa fix CI failures 2019-12-10 00:32:48 -08:00
Michael Zhu
865a2b1fb0 add/update comments 2019-12-09 23:45:38 -08:00
Michael Zhu
1fde62eeb6 fix bug in finalizePool 2019-12-09 23:45:38 -08:00
Michael Zhu
6754cd48e2 refactor + fix lint 2019-12-09 23:45:38 -08:00
Michael Zhu
ccb477687a fixing bugs 2019-12-09 23:45:38 -08:00
Michael Zhu
be0e6c8925 Staking rewards simulation/fuzz test 2019-12-09 23:45:38 -08:00
Michael Zhu
1c2cb947c0 Add assertion generators to keeper, staker, taker mixins for the new function assertions 2019-12-09 23:45:38 -08:00
Michael Zhu
4663eec950 Add function assertions required for staking rewards fuzzing: withdrawDelegatorRewards, finalizePool, and endEpoch. Also adds payProtocolFee-related assertions to fillOrder 2019-12-09 23:45:37 -08:00
Michael Zhu
fff3c1eb36 update pool membership simulation to use multiple makers and takers, partial fills 2019-12-09 23:43:16 -08:00
Michael Zhu
4b7434d1e8 post-rebase lockfile update 2019-12-09 23:42:32 -08:00
Michael Zhu
8cc35a60e6 Add yarn command to run a specific fuzz test 2019-12-09 23:42:32 -08:00
Michael Zhu
130653a1aa move logger, pseudorandom, wrapper_interfaces to framework/utils/ 2019-12-09 23:42:32 -08:00
Michael Zhu
1dcbebd130 lint 2019-12-09 23:42:32 -08:00
Michael Zhu
faf306ad23 Simulation logging, hopefully address function assertion lifetime issue 2019-12-09 23:42:32 -08:00
Michael Zhu
d11cdcd5d2 Use seeded rng for simulations 2019-12-09 23:42:32 -08:00
Amir Bandeali
0e59bd0bf3 Add mainnet config tests 2019-12-09 16:16:22 -08:00
Amir Bandeali
c0c6154ec1 Add fork option to describe and blockchainTests 2019-12-09 16:14:41 -08:00
Amir Bandeali
cb5384c2fb Use fork configs if FORK_RPC_URL env var is set 2019-12-09 16:14:41 -08:00
Amir Bandeali
038c836fe5 Rename fillorder_test to fill_order_test 2019-12-09 16:14:41 -08:00
279 changed files with 9767 additions and 3648 deletions

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "3.0.1",
@@ -64,6 +73,10 @@
{
"note": "Implement `KyberBridge`.",
"pr": 2352
},
{
"note": "Implement `DydxBridge`.",
"pr": 2365
}
],
"timestamp": 1575290197

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.0.2 - _December 17, 2019_
* Dependencies updated
## v3.0.1 - _December 9, 2019_
* Dependencies updated
@@ -26,6 +30,7 @@ CHANGELOG
## v2.3.0-beta.4 - _December 2, 2019_
* Implement `KyberBridge`. (#2352)
* Implement `DydxBridge`. (#2365)
## v2.3.0-beta.3 - _November 20, 2019_

View 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''
});
}
}

View 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;
}

View File

@@ -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.
}
}

View 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;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-asset-proxy",
"version": "3.0.1",
"version": "3.0.2",
"engines": {
"node": ">=6.12"
},
@@ -38,8 +38,7 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"publicInterfaceContracts": "ERC1155Proxy,ERC20Proxy,ERC721Proxy,MultiAssetProxy,StaticCallProxy,ERC20BridgeProxy,Eth2DaiBridge,IAssetData,IAssetProxy,UniswapBridge,KyberBridge,ChaiBridge,TestStaticCallTarget",
"abis": "./test/generated-artifacts/@(ChaiBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
"abis": "./test/generated-artifacts/@(ChaiBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|UniswapBridge).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {
@@ -52,15 +51,15 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/types": "^3.1.1",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -80,15 +79,16 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-erc1155": "^2.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-erc721": "^3.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/base-contract": "^6.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-erc1155": "^2.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-erc721": "^3.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"ethereum-types": "^3.0.0",
"lodash": "^4.17.11"
},

View File

@@ -6,6 +6,7 @@
import { ContractArtifact } from 'ethereum-types';
import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json';
import * as DydxBridge from '../generated-artifacts/DydxBridge.json';
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json';
@@ -13,23 +14,62 @@ import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
import * as Eth2DaiBridge from '../generated-artifacts/Eth2DaiBridge.json';
import * as IAssetData from '../generated-artifacts/IAssetData.json';
import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json';
import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json';
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
import * as IChai from '../generated-artifacts/IChai.json';
import * as IDydx from '../generated-artifacts/IDydx.json';
import * as IDydxBridge from '../generated-artifacts/IDydxBridge.json';
import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json';
import * as IKyberNetworkProxy from '../generated-artifacts/IKyberNetworkProxy.json';
import * as IUniswapExchange from '../generated-artifacts/IUniswapExchange.json';
import * as IUniswapExchangeFactory from '../generated-artifacts/IUniswapExchangeFactory.json';
import * as KyberBridge from '../generated-artifacts/KyberBridge.json';
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
import * as TestChaiBridge from '../generated-artifacts/TestChaiBridge.json';
import * as TestDydxBridge from '../generated-artifacts/TestDydxBridge.json';
import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json';
import * as TestEth2DaiBridge from '../generated-artifacts/TestEth2DaiBridge.json';
import * as TestKyberBridge from '../generated-artifacts/TestKyberBridge.json';
import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json';
import * as TestUniswapBridge from '../generated-artifacts/TestUniswapBridge.json';
import * as UniswapBridge from '../generated-artifacts/UniswapBridge.json';
export const artifacts = {
MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact,
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
Ownable: Ownable as ContractArtifact,
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
ERC20Proxy: ERC20Proxy as ContractArtifact,
ERC721Proxy: ERC721Proxy as ContractArtifact,
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
StaticCallProxy: StaticCallProxy as ContractArtifact,
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
ChaiBridge: ChaiBridge as ContractArtifact,
DydxBridge: DydxBridge as ContractArtifact,
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
KyberBridge: KyberBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,
IAssetProxy: IAssetProxy as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
KyberBridge: KyberBridge as ContractArtifact,
ChaiBridge: ChaiBridge as ContractArtifact,
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
IAuthorizable: IAuthorizable as ContractArtifact,
IChai: IChai as ContractArtifact,
IDydx: IDydx as ContractArtifact,
IDydxBridge: IDydxBridge as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact,
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
IUniswapExchange: IUniswapExchange as ContractArtifact,
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
TestChaiBridge: TestChaiBridge as ContractArtifact,
TestDydxBridge: TestDydxBridge as ContractArtifact,
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
TestKyberBridge: TestKyberBridge as ContractArtifact,
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
};

View File

@@ -5,6 +5,7 @@ export {
ERC20ProxyContract,
ERC721ProxyContract,
Eth2DaiBridgeContract,
DydxBridgeContract,
IAssetDataContract,
IAssetProxyContract,
MultiAssetProxyContract,

View File

@@ -4,6 +4,7 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/chai_bridge';
export * from '../generated-wrappers/dydx_bridge';
export * from '../generated-wrappers/erc1155_proxy';
export * from '../generated-wrappers/erc20_bridge_proxy';
export * from '../generated-wrappers/erc20_proxy';
@@ -11,8 +12,27 @@ export * from '../generated-wrappers/erc721_proxy';
export * from '../generated-wrappers/eth2_dai_bridge';
export * from '../generated-wrappers/i_asset_data';
export * from '../generated-wrappers/i_asset_proxy';
export * from '../generated-wrappers/i_asset_proxy_dispatcher';
export * from '../generated-wrappers/i_authorizable';
export * from '../generated-wrappers/i_chai';
export * from '../generated-wrappers/i_dydx';
export * from '../generated-wrappers/i_dydx_bridge';
export * from '../generated-wrappers/i_erc20_bridge';
export * from '../generated-wrappers/i_eth2_dai';
export * from '../generated-wrappers/i_kyber_network_proxy';
export * from '../generated-wrappers/i_uniswap_exchange';
export * from '../generated-wrappers/i_uniswap_exchange_factory';
export * from '../generated-wrappers/kyber_bridge';
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
export * from '../generated-wrappers/mixin_authorizable';
export * from '../generated-wrappers/multi_asset_proxy';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/static_call_proxy';
export * from '../generated-wrappers/test_chai_bridge';
export * from '../generated-wrappers/test_dydx_bridge';
export * from '../generated-wrappers/test_erc20_bridge';
export * from '../generated-wrappers/test_eth2_dai_bridge';
export * from '../generated-wrappers/test_kyber_bridge';
export * from '../generated-wrappers/test_static_call_target';
export * from '../generated-wrappers/test_uniswap_bridge';
export * from '../generated-wrappers/uniswap_bridge';

View File

@@ -6,6 +6,7 @@
import { ContractArtifact } from 'ethereum-types';
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json';
import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.json';
@@ -16,6 +17,8 @@ import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json';
import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json';
import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json';
import * as IChai from '../test/generated-artifacts/IChai.json';
import * as IDydx from '../test/generated-artifacts/IDydx.json';
import * as IDydxBridge from '../test/generated-artifacts/IDydxBridge.json';
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
@@ -28,6 +31,7 @@ import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.js
import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json';
import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
@@ -45,6 +49,7 @@ export const artifacts = {
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
StaticCallProxy: StaticCallProxy as ContractArtifact,
ChaiBridge: ChaiBridge as ContractArtifact,
DydxBridge: DydxBridge as ContractArtifact,
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
KyberBridge: KyberBridge as ContractArtifact,
UniswapBridge: UniswapBridge as ContractArtifact,
@@ -53,12 +58,15 @@ export const artifacts = {
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
IAuthorizable: IAuthorizable as ContractArtifact,
IChai: IChai as ContractArtifact,
IDydx: IDydx as ContractArtifact,
IDydxBridge: IDydxBridge as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact,
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
IUniswapExchange: IUniswapExchange as ContractArtifact,
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
TestChaiBridge: TestChaiBridge as ContractArtifact,
TestDydxBridge: TestDydxBridge as ContractArtifact,
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
TestKyberBridge: TestKyberBridge as ContractArtifact,

View 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);
});
});
});

View File

@@ -4,6 +4,7 @@
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/chai_bridge';
export * from '../test/generated-wrappers/dydx_bridge';
export * from '../test/generated-wrappers/erc1155_proxy';
export * from '../test/generated-wrappers/erc20_bridge_proxy';
export * from '../test/generated-wrappers/erc20_proxy';
@@ -14,6 +15,8 @@ export * from '../test/generated-wrappers/i_asset_proxy';
export * from '../test/generated-wrappers/i_asset_proxy_dispatcher';
export * from '../test/generated-wrappers/i_authorizable';
export * from '../test/generated-wrappers/i_chai';
export * from '../test/generated-wrappers/i_dydx';
export * from '../test/generated-wrappers/i_dydx_bridge';
export * from '../test/generated-wrappers/i_erc20_bridge';
export * from '../test/generated-wrappers/i_eth2_dai';
export * from '../test/generated-wrappers/i_kyber_network_proxy';
@@ -26,6 +29,7 @@ export * from '../test/generated-wrappers/multi_asset_proxy';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/static_call_proxy';
export * from '../test/generated-wrappers/test_chai_bridge';
export * from '../test/generated-wrappers/test_dydx_bridge';
export * from '../test/generated-wrappers/test_erc20_bridge';
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
export * from '../test/generated-wrappers/test_kyber_bridge';

View File

@@ -4,6 +4,7 @@
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/ChaiBridge.json",
"generated-artifacts/DydxBridge.json",
"generated-artifacts/ERC1155Proxy.json",
"generated-artifacts/ERC20BridgeProxy.json",
"generated-artifacts/ERC20Proxy.json",
@@ -11,12 +12,32 @@
"generated-artifacts/Eth2DaiBridge.json",
"generated-artifacts/IAssetData.json",
"generated-artifacts/IAssetProxy.json",
"generated-artifacts/IAssetProxyDispatcher.json",
"generated-artifacts/IAuthorizable.json",
"generated-artifacts/IChai.json",
"generated-artifacts/IDydx.json",
"generated-artifacts/IDydxBridge.json",
"generated-artifacts/IERC20Bridge.json",
"generated-artifacts/IEth2Dai.json",
"generated-artifacts/IKyberNetworkProxy.json",
"generated-artifacts/IUniswapExchange.json",
"generated-artifacts/IUniswapExchangeFactory.json",
"generated-artifacts/KyberBridge.json",
"generated-artifacts/MixinAssetProxyDispatcher.json",
"generated-artifacts/MixinAuthorizable.json",
"generated-artifacts/MultiAssetProxy.json",
"generated-artifacts/Ownable.json",
"generated-artifacts/StaticCallProxy.json",
"generated-artifacts/TestChaiBridge.json",
"generated-artifacts/TestDydxBridge.json",
"generated-artifacts/TestERC20Bridge.json",
"generated-artifacts/TestEth2DaiBridge.json",
"generated-artifacts/TestKyberBridge.json",
"generated-artifacts/TestStaticCallTarget.json",
"generated-artifacts/TestUniswapBridge.json",
"generated-artifacts/UniswapBridge.json",
"test/generated-artifacts/ChaiBridge.json",
"test/generated-artifacts/DydxBridge.json",
"test/generated-artifacts/ERC1155Proxy.json",
"test/generated-artifacts/ERC20BridgeProxy.json",
"test/generated-artifacts/ERC20Proxy.json",
@@ -27,6 +48,8 @@
"test/generated-artifacts/IAssetProxyDispatcher.json",
"test/generated-artifacts/IAuthorizable.json",
"test/generated-artifacts/IChai.json",
"test/generated-artifacts/IDydx.json",
"test/generated-artifacts/IDydxBridge.json",
"test/generated-artifacts/IERC20Bridge.json",
"test/generated-artifacts/IEth2Dai.json",
"test/generated-artifacts/IKyberNetworkProxy.json",
@@ -39,6 +62,7 @@
"test/generated-artifacts/Ownable.json",
"test/generated-artifacts/StaticCallProxy.json",
"test/generated-artifacts/TestChaiBridge.json",
"test/generated-artifacts/TestDydxBridge.json",
"test/generated-artifacts/TestERC20Bridge.json",
"test/generated-artifacts/TestEth2DaiBridge.json",
"test/generated-artifacts/TestKyberBridge.json",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "3.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.0.2 - _December 17, 2019_
* Dependencies updated
## v3.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-coordinator",
"version": "3.0.1",
"version": "3.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,19 +52,19 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-exchange": "^3.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/dev-utils": "^3.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-exchange": "^3.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/dev-utils": "^3.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -84,14 +84,14 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/assert": "^3.0.1",
"@0x/base-contract": "^6.0.1",
"@0x/contract-addresses": "^4.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/json-schemas": "^5.0.1",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/assert": "^3.0.2",
"@0x/base-contract": "^6.0.2",
"@0x/contract-addresses": "^4.1.0",
"@0x/contracts-utils": "^4.0.2",
"@0x/json-schemas": "^5.0.2",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"ethereum-types": "^3.0.0",
"http-status-codes": "^1.3.2"
},

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "1.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "1.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.2 - _December 17, 2019_
* Dependencies updated
## v1.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-dev-utils",
"version": "1.0.1",
"version": "1.0.2",
"engines": {
"node": ">=6.12"
},
@@ -41,10 +41,10 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/assert": "^3.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/assert": "^3.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@types/node": "*",
@@ -59,7 +59,7 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1"
"@0x/base-contract": "^6.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "2.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "2.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v2.0.2 - _December 17, 2019_
* Dependencies updated
## v2.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc1155",
"version": "2.0.1",
"version": "2.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,15 +52,15 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -80,10 +80,10 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/base-contract": "^6.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"lodash": "^4.17.11"
},
"publishConfig": {

View File

@@ -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,
"version": "1.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
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_
* Dependencies updated

View File

@@ -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;
}
}

View File

@@ -23,11 +23,13 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "./IDevUtils.sol";
import "./IERC20BridgeSampler.sol";
import "./IEth2Dai.sol";
import "./IKyberNetwork.sol";
import "./IUniswapExchangeQuotes.sol";
import "./DeploymentConstants.sol";
contract ERC20BridgeSampler is
@@ -38,26 +40,32 @@ contract ERC20BridgeSampler is
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleSells(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources,
uint256[] memory takerTokenAmounts
)
public
view
returns (
LibOrder.OrderInfo[] memory orderInfos,
uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
)
{
require(orders.length != 0, "EMPTY_ORDERS");
orderInfos = queryOrders(orders);
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleSells(
sources,
_assetDataToTokenAddress(orders[0].takerAssetData),
@@ -68,26 +76,32 @@ contract ERC20BridgeSampler is
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleBuys(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources,
uint256[] memory makerTokenAmounts
)
public
view
returns (
LibOrder.OrderInfo[] memory orderInfos,
uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
)
{
require(orders.length != 0, "EMPTY_ORDERS");
orderInfos = queryOrders(orders);
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleBuys(
sources,
_assetDataToTokenAddress(orders[0].takerAssetData),
@@ -96,18 +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.
/// @return orderInfos Order info for each respective order.
function queryOrders(LibOrder.Order[] memory orders)
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getOrderFillableTakerAssetAmounts(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures
)
public
view
returns (LibOrder.OrderInfo[] memory orderInfos)
returns (uint256[] memory orderFillableTakerAssetAmounts)
{
uint256 numOrders = orders.length;
orderInfos = new LibOrder.OrderInfo[](numOrders);
for (uint256 i = 0; i < numOrders; i++) {
orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]);
orderFillableTakerAssetAmounts = new uint256[](orders.length);
for (uint256 i = 0; i != orders.length; i++) {
// Ignore orders with no signature or empty maker/taker amounts.
if (orderSignatures[i].length == 0 ||
orders[i].makerAssetAmount == 0 ||
orders[i].takerAssetAmount == 0) {
orderFillableTakerAssetAmounts[i] = 0;
continue;
}
(
LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount,
bool isValidSignature
) = IDevUtils(_getDevUtilsAddress()).getOrderRelevantState(
orders[i],
orderSignatures[i]
);
// The fillable amount is zero if the order is not fillable or if the
// signature is invalid.
if (orderInfo.orderStatus != 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)
{
_assertValidPair(makerToken, takerToken);
address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken;
address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken;
address _takerToken = takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken;
address _makerToken = makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken;
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(uint256 rate,) = _getKyberNetworkContract().getExpectedRate(
_takerToken,
_makerToken,
takerTokenAmounts[i]
);
(bool didSucceed, bytes memory resultData) =
_getKyberNetworkProxyAddress().staticcall(abi.encodeWithSelector(
IKyberNetwork(0).getExpectedRate.selector,
_takerToken,
_makerToken,
takerTokenAmounts[i]
));
uint256 rate = 0;
if (didSucceed) {
rate = abi.decode(resultData, (uint256));
}
makerTokenAmounts[i] =
rate *
takerTokenAmounts[i] *
@@ -227,11 +306,18 @@ contract ERC20BridgeSampler is
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount(
makerToken,
takerToken,
takerTokenAmounts[i]
);
(bool didSucceed, bytes memory resultData) =
_getEth2DaiAddress().staticcall(abi.encodeWithSelector(
IEth2Dai(0).getBuyAmount.selector,
makerToken,
takerToken,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
}
makerTokenAmounts[i] = buyAmount;
}
}
@@ -254,11 +340,18 @@ contract ERC20BridgeSampler is
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount(
takerToken,
makerToken,
makerTokenAmounts[i]
);
(bool didSucceed, bytes memory resultData) =
_getEth2DaiAddress().staticcall(abi.encodeWithSelector(
IEth2Dai(0).getPayAmount.selector,
takerToken,
makerToken,
makerTokenAmounts[i]
));
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
}
takerTokenAmounts[i] = sellAmount;
}
}
@@ -280,26 +373,38 @@ contract ERC20BridgeSampler is
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) {
makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice(
if (makerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i]
);
} else if (takerToken == _getWETHAddress()) {
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
} else if (takerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
takerTokenAmounts[i]
);
} else {
uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice(
uint256 ethBought = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i]
);
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
ethBought
);
if (ethBought != 0) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
ethBought
);
} else {
makerTokenAmounts[i] = 0;
}
}
}
}
@@ -322,26 +427,38 @@ contract ERC20BridgeSampler is
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) {
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
if (makerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
makerTokenAmounts[i]
);
} else if (takerToken == _getWETHAddress()) {
takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice(
} else if (takerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i]
);
} else {
uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice(
uint256 ethSold = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i]
);
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
ethSold
);
if (ethSold != 0) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
ethSold
);
} else {
takerTokenAmounts[i] = 0;
}
}
}
}
@@ -357,6 +474,34 @@ contract ERC20BridgeSampler is
return LibERC20Token.decimals(tokenAddress);
}
/// @dev Gracefully calls a Uniswap pricing function.
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
/// @param functionSelector Selector of the target function.
/// @param inputAmount Quantity parameter particular to the pricing function.
/// @return outputAmount The returned amount from the function call. Will be
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
function _callUniswapExchangePriceFunction(
address uniswapExchangeAddress,
bytes4 functionSelector,
uint256 inputAmount
)
private
view
returns (uint256 outputAmount)
{
if (uniswapExchangeAddress == address(0)) {
return 0;
}
(bool didSucceed, bytes memory resultData) =
uniswapExchangeAddress.staticcall(abi.encodeWithSelector(
functionSelector,
inputAmount
));
if (didSucceed) {
outputAmount = abi.decode(resultData, (uint256));
}
}
/// @dev Samples a supported sell source, defined by its address.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
@@ -373,16 +518,16 @@ contract ERC20BridgeSampler is
view
returns (uint256[] memory makerTokenAmounts)
{
if (source == address(_getEth2DaiContract())) {
if (source == _getEth2DaiAddress()) {
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
}
if (source == address(_getUniswapExchangeFactoryContract())) {
if (source == _getUniswapExchangeFactoryAddress()) {
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
}
if (source == address(_getKyberNetworkContract())) {
if (source == _getKyberNetworkProxyAddress()) {
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
}
revert("UNSUPPORTED_SOURCE");
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
}
/// @dev Samples a supported buy source, defined by its address.
@@ -401,13 +546,13 @@ contract ERC20BridgeSampler is
view
returns (uint256[] memory takerTokenAmounts)
{
if (source == address(_getEth2DaiContract())) {
if (source == _getEth2DaiAddress()) {
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
}
if (source == address(_getUniswapExchangeFactoryContract())) {
if (source == _getUniswapExchangeFactoryAddress()) {
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
}
revert("UNSUPPORTED_SOURCE");
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
}
/// @dev Retrive an existing Uniswap exchange contract.
@@ -420,9 +565,9 @@ contract ERC20BridgeSampler is
returns (IUniswapExchangeQuotes exchange)
{
exchange = IUniswapExchangeQuotes(
address(_getUniswapExchangeFactoryContract().getExchange(tokenAddress))
address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress())
.getExchange(tokenAddress))
);
require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE");
}
/// @dev Extract the token address from ERC20 proxy asset data.
@@ -433,19 +578,19 @@ contract ERC20BridgeSampler is
pure
returns (address tokenAddress)
{
require(assetData.length == 36, "INVALID_ASSET_DATA");
require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA");
bytes4 selector;
assembly {
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
tokenAddress := mload(add(assetData, 0x24))
}
require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY");
require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY");
}
function _assertValidPair(address makerToken, address takerToken)
private
pure
{
require(makerToken != takerToken, "INVALID_TOKEN_PAIR");
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
}
}

View 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
);
}

View File

@@ -19,59 +19,82 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
interface IERC20BridgeSampler {
/// @dev Query native orders and sample sell orders on multiple DEXes at once.
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
/// @param takerTokenAmounts Taker sell amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleSells(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata takerTokenAmounts
)
external
view
returns (
LibOrder.OrderInfo[] memory orderInfos,
uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
);
/// @dev Query native orders and sample buy orders on multiple DEXes at once.
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
/// @param makerTokenAmounts Maker sell amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleBuys(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata makerTokenAmounts
)
external
view
returns (
LibOrder.OrderInfo[] memory orderInfos,
uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
);
/// @dev Queries the status of several native orders.
/// @dev Queries the fillable taker asset amounts of native orders.
/// @param orders Native orders to query.
/// @return orderInfos Order info for each respective order.
function queryOrders(LibOrder.Order[] calldata orders)
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getOrderFillableTakerAssetAmounts(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures
)
external
view
returns (LibOrder.OrderInfo[] memory orderInfos);
returns (uint256[] memory orderFillableTakerAssetAmounts);
/// @dev Queries the fillable maker asset amounts of native orders.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
function getOrderFillableMakerAssetAmounts(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures
)
external
view
returns (uint256[] memory orderFillableMakerAssetAmounts);
/// @dev Sample sell quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.

View File

@@ -23,6 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../src/ERC20BridgeSampler.sol";
import "../src/IEth2Dai.sol";
import "../src/IDevUtils.sol";
import "../src/IKyberNetwork.sol";
@@ -90,9 +91,28 @@ library LibDeterministicQuotes {
}
contract FailTrigger {
// Give this address a balance to force operations to fail.
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
// Funds `FAILURE_ADDRESS`.
function enableFailTrigger() external payable {
FAILURE_ADDRESS.transfer(msg.value);
}
function _revertIfShouldFail() internal view {
if (FAILURE_ADDRESS.balance != 0) {
revert("FAIL_TRIGGERED");
}
}
}
contract TestERC20BridgeSamplerUniswapExchange is
IUniswapExchangeQuotes,
DeploymentConstants
DeploymentConstants,
FailTrigger
{
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
@@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
view
returns (uint256 tokensBought)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote(
salt,
tokenAddress,
WETH_ADDRESS,
_getWethAddress(),
ethSold
);
}
@@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
view
returns (uint256 ethSold)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
WETH_ADDRESS,
_getWethAddress(),
tokenAddress,
tokensBought
);
@@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
view
returns (uint256 ethBought)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote(
salt,
tokenAddress,
WETH_ADDRESS,
_getWethAddress(),
tokensSold
);
}
@@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
view
returns (uint256 tokensSold)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
WETH_ADDRESS,
_getWethAddress(),
tokenAddress,
ethBought
);
@@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is
contract TestERC20BridgeSamplerKyberNetwork is
IKyberNetwork,
DeploymentConstants
DeploymentConstants,
FailTrigger
{
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
@@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is
view
returns (uint256 expectedRate, uint256)
{
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken;
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken;
_revertIfShouldFail();
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
expectedRate = LibDeterministicQuotes.getDeterministicRate(
SALT,
fromToken,
@@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is
contract TestERC20BridgeSamplerEth2Dai is
IEth2Dai
IEth2Dai,
FailTrigger
{
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
@@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is
view
returns (uint256 buyAmount)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote(
SALT,
payToken,
@@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is
view
returns (uint256 payAmount)
{
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote(
SALT,
payToken,
@@ -269,7 +298,8 @@ contract TestERC20BridgeSamplerUniswapExchangeFactory is
contract TestERC20BridgeSampler is
ERC20BridgeSampler
ERC20BridgeSampler,
FailTrigger
{
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
TestERC20BridgeSamplerEth2Dai public eth2Dai;
@@ -288,18 +318,30 @@ contract TestERC20BridgeSampler is
uniswap.createTokenExchanges(tokenAddresses);
}
// `IExchange.getOrderInfo()`, overridden to return deterministic order infos.
function getOrderInfo(LibOrder.Order memory order)
// `IDevUtils.getOrderRelevantState()`, overridden to return deterministic
// states.
function getOrderRelevantState(
LibOrder.Order memory order,
bytes memory
)
public
pure
returns (LibOrder.OrderInfo memory orderInfo)
view
returns (
LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount,
bool isValidSignature
)
{
// The order hash is just the hash of the salt.
bytes32 orderHash = keccak256(abi.encode(order.salt));
// Everything else is derived from the hash.
orderInfo.orderHash = orderHash;
orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1));
orderInfo.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.
@@ -312,38 +354,38 @@ contract TestERC20BridgeSampler is
}
// Overriden to point to a this contract.
function _getExchangeContract()
function _getDevUtilsAddress()
internal
view
returns (IExchange zeroex)
returns (address devUtilAddress)
{
return IExchange(address(this));
return address(this);
}
// Overriden to point to a custom contract.
function _getEth2DaiContract()
function _getEth2DaiAddress()
internal
view
returns (IEth2Dai eth2dai_)
returns (address eth2daiAddress)
{
return eth2Dai;
return address(eth2Dai);
}
// Overriden to point to a custom contract.
function _getUniswapExchangeFactoryContract()
function _getUniswapExchangeFactoryAddress()
internal
view
returns (IUniswapExchangeFactory uniswap_)
returns (address uniswapAddress)
{
return uniswap;
return address(uniswap);
}
// Overriden to point to a custom contract.
function _getKyberNetworkContract()
function _getKyberNetworkProxyAddress()
internal
view
returns (IKyberNetwork kyber_)
returns (address kyberAddress)
{
return kyber;
return address(kyber);
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc20-bridge-sampler",
"version": "1.0.1",
"version": "1.0.2",
"engines": {
"node": ">=6.12"
},
@@ -38,7 +38,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
},
"repository": {
"type": "git",
@@ -50,18 +50,18 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-exchange": "^3.0.1",
"@0x/contracts-exchange-libs": "^4.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-exchange": "^3.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/tslint-config": "^4.0.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -79,10 +79,10 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/base-contract": "^6.0.2",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"ethereum-types": "^3.0.0",
"lodash": "^4.17.11"
},

View File

@@ -5,16 +5,16 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json';
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
export const artifacts = {
DeploymentConstants: DeploymentConstants as ContractArtifact,
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
IDevUtils: IDevUtils as ContractArtifact,
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact,
IKyberNetwork: IKyberNetwork as ContractArtifact,

View File

@@ -6,7 +6,7 @@ import {
getRandomPortion,
randomAddress,
} from '@0x/contracts-test-utils';
import { Order, OrderInfo } from '@0x/types';
import { Order } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
@@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => {
const INVALID_ASSET_DATA = hexUtils.random(37);
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS';
const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY';
const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA';
const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE';
const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE';
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR';
const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => {
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
@@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => {
return quotes;
}
function getDeterministicOrderInfo(order: Order): OrderInfo {
const hash = getPackedHash(hexUtils.leftPad(order.salt, 32));
return {
orderHash: hash,
orderStatus: new BigNumber(hash).mod(255).toNumber(),
orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount),
};
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
const orderStatus = new BigNumber(hash).mod(255).toNumber();
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
if (orderStatus !== 3 || !isValidSignature) {
return constants.ZERO_AMOUNT;
}
return order.takerAssetAmount.minus(new BigNumber(hash).mod(order.takerAssetAmount));
}
function getDeterministicFillableMakerAssetAmount(order: Order): BigNumber {
const takerAmount = getDeterministicFillableTakerAssetAmount(order);
return order.makerAssetAmount
.times(takerAmount)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_DOWN);
}
function getERC20AssetData(tokenAddress: string): string {
@@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => {
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
}
describe('queryOrders()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
async function enableFailTriggerAsync(): Promise<void> {
await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
}
it('returns the results of `getOrderInfo()` for each order', async () => {
describe('getOrderFillableTakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const expected = orders.map(getDeterministicOrderInfo);
const actual = await testContract.queryOrders(orders).callAsync();
const signatures: string[] = _.times(orders.length, hexUtils.random);
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected);
});
it('returns empty for no orders', async () => {
const actual = await testContract.queryOrders([]).callAsync();
const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync();
expect(actual).to.deep.eq([]);
});
it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with an empty signature', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
});
describe('getOrderFillableMakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const signatures: string[] = _.times(orders.length, hexUtils.random);
const expected = orders.map(getDeterministicFillableMakerAssetAmount);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected);
});
it('returns empty for no orders', async () => {
const actual = await testContract.getOrderFillableMakerAssetAmounts([], []).callAsync();
expect(actual).to.deep.eq([]);
});
it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with an empty signature', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
});
describe('queryOrdersAndSampleSells()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns the results of `getOrderInfo()` for each order', async () => {
it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
const [orderInfos] = await testContract
.queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.callAsync();
expect(orderInfos).to.deep.eq(expectedOrderInfos);
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
const [, quotes] = await testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN),
SELL_SOURCES.map(n => allSources[n]),
sampleAmounts,
)
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no orders are passed in', async () => {
const tx = testContract
.queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
.queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
.callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
});
@@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with an unsupported source', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN),
ORDERS,
SIGNATURES,
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
getSampleAmounts(TAKER_TOKEN),
)
@@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN),
)
@@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN),
)
@@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN),
)
@@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN),
)
@@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => {
});
describe('queryOrdersAndSampleBuys()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns the results of `getOrderInfo()` for each order', async () => {
it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
const [orderInfos] = await testContract
.queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.callAsync();
expect(orderInfos).to.deep.eq(expectedOrderInfos);
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
const [, quotes] = await testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN),
BUY_SOURCES.map(n => allSources[n]),
sampleAmounts,
)
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no orders are passed in', async () => {
const tx = testContract
.queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
.queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
.callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
});
@@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with an unsupported source', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN),
ORDERS,
SIGNATURES,
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
getSampleAmounts(MAKER_TOKEN),
)
@@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => {
const sources = [...BUY_SOURCES, 'Kyber'];
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN),
ORDERS,
SIGNATURES,
sources.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN),
)
@@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN),
)
@@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN),
)
@@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN),
)
@@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN),
)
@@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => {
});
describe('sampleSells()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => {
});
describe('sampleBuys()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => {
});
});
describe('sampleSellsFromKyberNetwork()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]);
});
it('can return many quotes', async () => {
it('can quote token - token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
const quotes = await testContract
@@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
@@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
@@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
});
describe('sampleSellsFromEth2Dai()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]);
});
it('can return many quotes', async () => {
it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
const quotes = await testContract
@@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
@@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
@@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
});
describe('sampleBuysFromEth2Dai()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
blockchainTests.resets('sampleBuysFromEth2Dai()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]);
});
it('can return many quotes', async () => {
it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
const quotes = await testContract
@@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
@@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
@@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
});
describe('sampleSellsFromUniswap()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
blockchainTests.resets('sampleSellsFromUniswap()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]);
});
it('can return many quotes', async () => {
it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
const quotes = await testContract
@@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
@@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
@@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress();
const tx = testContract
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no exchange exists for the taker token', async () => {
it('returns zero if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress();
const tx = testContract
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if no exchange exists for the taker token', async () => {
const nonExistantToken = randomAddress();
const sampleAmounts = getSampleAmounts(nonExistantToken);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
});
describe('sampleBuysFromUniswap()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
blockchainTests.resets('sampleBuysFromUniswap()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
@@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]);
});
it('can return many quotes', async () => {
it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
const quotes = await testContract
@@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
@@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
@@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress();
const tx = testContract
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no exchange exists for the taker token', async () => {
it('returns zero if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress();
const tx = testContract
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
const sampleAmounts = getSampleAmounts(nonExistantToken);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if no exchange exists for the taker token', async () => {
const nonExistantToken = randomAddress();
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
});
});

View File

@@ -3,8 +3,8 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/deployment_constants';
export * from '../test/generated-wrappers/erc20_bridge_sampler';
export * from '../test/generated-wrappers/i_dev_utils';
export * from '../test/generated-wrappers/i_erc20_bridge_sampler';
export * from '../test/generated-wrappers/i_eth2_dai';
export * from '../test/generated-wrappers/i_kyber_network';

View File

@@ -5,8 +5,8 @@
"files": [
"generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/DeploymentConstants.json",
"test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/IDevUtils.json",
"test/generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/IEth2Dai.json",
"test/generated-artifacts/IKyberNetwork.json",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "3.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.0.2 - _December 17, 2019_
* Dependencies updated
## v3.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc20",
"version": "3.0.1",
"version": "3.0.2",
"engines": {
"node": ">=6.12"
},
@@ -51,18 +51,18 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -82,7 +82,7 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1"
"@0x/base-contract": "^6.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -3,6 +3,9 @@ export {
DummyMultipleReturnERC20TokenContract,
DummyNoReturnERC20TokenContract,
WETH9Contract,
WETH9Events,
WETH9DepositEventArgs,
WETH9TransferEventArgs,
ZRXTokenContract,
DummyERC20TokenTransferEventArgs,
ERC20TokenEventArgs,

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "3.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.0.2 - _December 17, 2019_
* Dependencies updated
## v3.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc721",
"version": "3.0.1",
"version": "3.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,18 +52,18 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -84,7 +84,7 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1"
"@0x/base-contract": "^6.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "4.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "4.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.0.2 - _December 17, 2019_
* Dependencies updated
## v4.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange-forwarder",
"version": "4.0.1",
"version": "4.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,24 +52,24 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-erc721": "^3.0.1",
"@0x/contracts-exchange": "^3.0.1",
"@0x/contracts-exchange-libs": "^4.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-erc721": "^3.0.2",
"@0x/contracts-exchange": "^3.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -89,8 +89,8 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/typescript-typings": "^5.0.0",
"@0x/base-contract": "^6.0.2",
"@0x/typescript-typings": "^5.0.1",
"ethereum-types": "^3.0.0"
},
"publishConfig": {

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "4.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "4.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.0.2 - _December 17, 2019_
* Dependencies updated
## v4.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange-libs",
"version": "4.0.1",
"version": "4.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,15 +52,15 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/libs/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/subproviders": "^6.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/subproviders": "^6.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -81,12 +81,12 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/contracts-utils": "^4.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/base-contract": "^6.0.2",
"@0x/contracts-utils": "^4.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"ethereum-types": "^3.0.0"
},
"publishConfig": {

View File

@@ -103,7 +103,7 @@ export function calculateFillResults(
order.takerAssetAmount,
order.makerAssetAmount,
);
const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
const makerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee);
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
return {
makerAssetFilledAmount,
@@ -113,3 +113,30 @@ export function calculateFillResults(
protocolFeePaid: safeMul(protocolFeeMultiplier, gasPrice),
};
}
export const LibFractions = {
add: (n1: BigNumber, d1: BigNumber, n2: BigNumber, d2: BigNumber): [BigNumber, BigNumber] => {
if (n1.isZero()) {
return [n2, d2];
}
if (n2.isZero()) {
return [n1, d1];
}
const numerator = safeAdd(safeMul(n1, d2), safeMul(n2, d1));
const denominator = safeMul(d1, d2);
return [numerator, denominator];
},
normalize: (
numerator: BigNumber,
denominator: BigNumber,
maxValue: BigNumber = new BigNumber(2).exponentiatedBy(127),
): [BigNumber, BigNumber] => {
if (numerator.isGreaterThan(maxValue) || denominator.isGreaterThan(maxValue)) {
let rescaleBase = numerator.isGreaterThanOrEqualTo(denominator) ? numerator : denominator;
rescaleBase = safeDiv(rescaleBase, maxValue);
return [safeDiv(numerator, rescaleBase), safeDiv(denominator, rescaleBase)];
} else {
return [numerator, denominator];
}
},
};

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "3.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v3.0.2 - _December 17, 2019_
* Dependencies updated
## v3.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-exchange",
"version": "3.0.1",
"version": "3.0.2",
"engines": {
"node": ">=6.12"
},
@@ -52,21 +52,21 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-exchange-libs": "^4.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-multisig": "^4.0.1",
"@0x/contracts-staking": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-multisig": "^4.0.2",
"@0x/contracts-staking": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -88,13 +88,13 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-erc1155": "^2.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-erc721": "^3.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/utils": "^5.1.0",
"@0x/base-contract": "^6.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-erc1155": "^2.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-erc721": "^3.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/utils": "^5.1.1",
"lodash": "^4.17.11"
},
"publishConfig": {

View File

@@ -107,23 +107,6 @@ describe('Reference functions', () => {
).to.throw(expectedError.message);
});
it('reverts if `order.makerAssetAmount` is 0', () => {
const order = makeOrder({
makerAssetAmount: constants.ZERO_AMOUNT,
takerAssetAmount: ONE_ETHER,
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(() =>
LibReferenceFunctions.calculateFillResults(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.throw(expectedError.message);
});
it('reverts if `order.takerAssetAmount` is 0', () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "5.1.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "5.1.0",
"changes": [

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v5.1.1 - _December 17, 2019_
* Dependencies updated
## v5.1.0 - _December 9, 2019_
* Export function `encodeDutchAuctionAssetData` (#2373)

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-extensions",
"version": "5.1.0",
"version": "5.1.1",
"engines": {
"node": ">=6.12"
},
@@ -52,24 +52,24 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-erc721": "^3.0.1",
"@0x/contracts-exchange": "^3.0.1",
"@0x/contracts-exchange-libs": "^4.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-erc721": "^3.0.2",
"@0x/contracts-exchange": "^3.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/sol-compiler": "^4.0.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -91,8 +91,8 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/typescript-typings": "^5.0.0",
"@0x/base-contract": "^6.0.2",
"@0x/typescript-typings": "^5.0.1",
"ethereum-types": "^3.0.0"
},
"publishConfig": {

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "2.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "2.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v2.0.2 - _December 17, 2019_
* Dependencies updated
## v2.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-integrations",
"version": "2.0.1",
"version": "2.0.2",
"engines": {
"node": ">=6.12"
},
@@ -19,6 +19,7 @@
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"test:fuzz": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/fuzz_tests/*.js' --timeout 0 --bail --exit",
"compile": "sol-compiler",
"watch": "sol-compiler -w",
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
@@ -50,25 +51,27 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contract-addresses": "^4.0.0",
"@0x/contracts-coordinator": "^3.0.1",
"@0x/contracts-dev-utils": "^1.0.1",
"@0x/contracts-exchange-forwarder": "^4.0.1",
"@0x/contracts-exchange-libs": "^4.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-utils": "^4.0.1",
"@0x/coordinator-server": "^1.0.4",
"@0x/dev-utils": "^3.0.1",
"@0x/migrations": "^5.0.1",
"@0x/order-utils": "^10.0.0",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contract-addresses": "^4.1.0",
"@0x/contract-wrappers": "^13.2.0",
"@0x/contracts-coordinator": "^3.0.2",
"@0x/contracts-dev-utils": "^1.0.2",
"@0x/contracts-exchange-forwarder": "^4.0.2",
"@0x/contracts-exchange-libs": "^4.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-utils": "^4.0.2",
"@0x/coordinator-server": "^1.0.5",
"@0x/dev-utils": "^3.0.2",
"@0x/migrations": "^5.0.2",
"@0x/order-utils": "^10.0.1",
"@0x/sol-compiler": "^4.0.2",
"@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",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
"@types/seedrandom": "^2.4.28",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
@@ -78,6 +81,7 @@
"mocha": "^6.2.0",
"nock": "^10.0.6",
"npm-run-all": "^4.1.2",
"seedrandom": "^3.0.5",
"shx": "^0.2.2",
"solhint": "^1.4.1",
"truffle": "^5.0.32",
@@ -85,18 +89,18 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-erc1155": "^2.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-erc721": "^3.0.1",
"@0x/contracts-exchange": "^3.0.1",
"@0x/contracts-multisig": "^4.0.1",
"@0x/contracts-staking": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/types": "^3.1.0",
"@0x/typescript-typings": "^5.0.0",
"@0x/utils": "^5.1.0",
"@0x/base-contract": "^6.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-erc1155": "^2.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-erc721": "^3.0.2",
"@0x/contracts-exchange": "^3.0.2",
"@0x/contracts-multisig": "^4.0.2",
"@0x/contracts-staking": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/types": "^3.1.1",
"@0x/typescript-typings": "^5.0.1",
"@0x/utils": "^5.1.1",
"ethereum-types": "^3.0.0",
"lodash": "^4.17.11"
},

View File

@@ -19,7 +19,7 @@ const coordinatorEndpoint = 'http://localhost:';
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
// tslint:disable:custom-no-magic-numbers
blockchainTests.skip('Coordinator Client', env => {
blockchainTests('Coordinator Client', env => {
const takerTokenFillAmount = new BigNumber(0);
const chainId = 1337;
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);

View File

@@ -33,7 +33,7 @@ import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/wrapper_interfaces';
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/utils/wrapper_interfaces';
// tslint:disable:no-unnecessary-type-assertion
blockchainTests('Deployment and Configuration End to End Tests', env => {

View File

@@ -15,7 +15,6 @@ export type Constructor<T = {}> = new (...args: any[]) => T;
export interface ActorConfig {
name?: string;
deployment: DeploymentManager;
simulationEnvironment?: SimulationEnvironment;
[mixinProperty: string]: any;
}
@@ -25,10 +24,11 @@ export class Actor {
public readonly name: string;
public readonly privateKey: Buffer;
public readonly deployment: DeploymentManager;
public readonly simulationEnvironment?: SimulationEnvironment;
public simulationEnvironment?: SimulationEnvironment;
public simulationActions: {
[action: string]: AsyncIterableIterator<AssertionResult | void>;
} = {};
public mixins: string[] = [];
protected readonly _transactionFactory: TransactionFactory;
public static reset(): void {
@@ -47,7 +47,6 @@ export class Actor {
this.name = config.name || this.address;
this.deployment = config.deployment;
this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)];
this.simulationEnvironment = config.simulationEnvironment;
this._transactionFactory = new TransactionFactory(
this.privateKey,
config.deployment.exchange.address,
@@ -123,7 +122,6 @@ export class Actor {
if (logs.length !== 1) {
throw new Error('Invalid number of `TransferSingle` logs');
}
const { id } = logs[0];
// Mint the token

View File

@@ -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.
*/
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
super(...args);
this.actor = (this as any) as Actor;
this.actor.mixins.push('FeeRecipient');
const { verifyingContract } = args[0] as FeeRecipientConfig;
if (verifyingContract !== undefined) {

View File

@@ -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 { BigNumber } from '@0x/utils';
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';
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.
*/
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
super(...args);
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> {
const { stakingWrapper } = this.actor.deployment.staking;
if (shouldFastForward) {
// 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();
await this._fastForwardToNextEpochAsync();
}
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();
}
};
}

View File

@@ -1,10 +1,11 @@
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants, OrderFactory } from '@0x/contracts-test-utils';
import { Order, SignedOrder } from '@0x/types';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { AssertionResult } from '../assertions/function_assertion';
import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool';
import { Pseudorandom } from '../utils/pseudorandom';
import { Actor, ActorConfig, Constructor } from './base';
@@ -18,10 +19,11 @@ export interface MakerInterface {
signOrderAsync: (customOrderParams?: Partial<Order>) => Promise<SignedOrder>;
cancelOrderAsync: (order: SignedOrder) => 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.
*/
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
super(...args);
this.actor = (this as any) as Actor;
this.actor.mixins.push('Maker');
const { orderConfig } = args[0] as MakerConfig;
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> {
const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validJoinStakingPoolAssertion(this.actor.deployment);
while (true) {
const poolId = _.sample(Object.keys(stakingPools));
const poolId = Pseudorandom.sample(Object.keys(stakingPools));
if (poolId === undefined) {
yield undefined;
yield;
} else {
yield assertion.executeAsync([poolId], { from: this.actor.address });
}

View File

@@ -1,5 +1,4 @@
import { constants, StakingPoolById } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import '@azure/core-asynciterator-polyfill';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
@@ -7,6 +6,7 @@ import * as _ from 'lodash';
import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare';
import { AssertionResult } from '../assertions/function_assertion';
import { Pseudorandom } from '../utils/pseudorandom';
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.
*/
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
super(...args);
this.actor = (this as any) as Actor;
this.actor.mixins.push('PoolOperator');
// Register this mixin's assertion generators
this.actor.simulationActions = {
@@ -80,10 +81,9 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
}
private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
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 });
}
}
@@ -92,11 +92,11 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
while (true) {
const poolId = _.sample(this._getOperatorPoolIds(stakingPools));
const poolId = Pseudorandom.sample(this._getOperatorPoolIds(stakingPools));
if (poolId === undefined) {
yield undefined;
} 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 });
}
}

View File

@@ -1,5 +1,4 @@
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
import { getRandomInteger } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import '@azure/core-asynciterator-polyfill';
import * as _ from 'lodash';
@@ -8,6 +7,8 @@ import { AssertionResult } from '../assertions/function_assertion';
import { validMoveStakeAssertion } from '../assertions/moveStake';
import { validStakeAssertion } from '../assertions/stake';
import { validUnstakeAssertion } from '../assertions/unstake';
import { validWithdrawDelegatorRewardsAssertion } from '../assertions/withdrawDelegatorRewards';
import { Pseudorandom } from '../utils/pseudorandom';
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).
*/
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
super(...args);
this.actor = (this as any) as Actor;
this.actor.mixins.push('Staker');
this.stake = {
[StakeStatus.Undelegated]: new StoredBalance(),
[StakeStatus.Delegated]: { total: new StoredBalance() },
@@ -44,6 +47,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
validStake: this._validStake(),
validUnstake: this._validUnstake(),
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> {
const { zrx } = this.actor.deployment.tokens;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake);
const { deployment, balanceStore } = this.actor.simulationEnvironment!;
const assertion = validStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
while (true) {
await balanceStore.updateErc20BalancesAsync();
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 });
}
}
private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
const { stakingWrapper } = this.actor.deployment.staking;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!;
const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake);
const { deployment, balanceStore } = this.actor.simulationEnvironment!;
const assertion = validUnstakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
while (true) {
await balanceStore.updateErc20BalancesAsync();
@@ -94,39 +98,71 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
undelegatedStake.currentEpochBalance,
undelegatedStake.nextEpochBalance,
);
const amount = getRandomInteger(0, withdrawableStake);
const amount = Pseudorandom.integer(withdrawableStake);
yield assertion.executeAsync([amount], { from: this.actor.address });
}
}
private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!;
const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools);
const { deployment, stakingPools } = this.actor.simulationEnvironment!;
const assertion = validMoveStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
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 =
fromPoolId === undefined
fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
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 =
toPoolId === undefined
toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated
: (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const to = new StakeInfo(toStatus, toPoolId);
// The next epoch balance of the `from` stake is the amount that can be moved
const moveableStake =
from.status === StakeStatus.Undelegated
? this.stake[StakeStatus.Undelegated].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 });
}
}
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 });
}
}
}
};
}

View File

@@ -1,14 +1,15 @@
import { constants, getRandomInteger } from '@0x/contracts-test-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
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 { DeploymentManager } from '../deployment_manager';
import { Pseudorandom } from '../utils/pseudorandom';
import { Actor, Constructor } from './base';
import { Maker } from './maker';
import { filterActorsByRole } from './utils';
export interface TakerInterface {
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`,
*/
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
super(...args);
this.actor = (this as any) as Actor;
this.actor.mixins.push('Taker');
// Register this mixin's assertion generators
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> {
const { marketMakers } = this.actor.simulationEnvironment!;
const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment);
private async *_validFillOrder(): AsyncIterableIterator<AssertionResult | void> {
const { actors } = this.actor.simulationEnvironment!;
const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
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) {
yield undefined;
yield;
} else {
// Configure the maker's token balances so that the order will definitely be fillable.
await Promise.all([
...this.actor.deployment.tokens.erc20.map(async token => maker.configureERC20TokenAsync(token)),
...this.actor.deployment.tokens.erc20.map(async token =>
this.actor.configureERC20TokenAsync(token),
),
this.actor.configureERC20TokenAsync(
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], {
// Maker creates and signs a fillable order
const order = await maker.createFillableOrderAsync(this.actor);
// Taker fills the order by a random amount (up to the order's takerAssetAmount)
const fillAmount = Pseudorandom.integer(order.takerAssetAmount);
// 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.
yield assertion.executeAsync([order, fillAmount, order.signature], {
from: this.actor.address,
value: Pseudorandom.integer(DeploymentManager.protocolFee.times(2)),
});
}
}

View File

@@ -1,7 +1,7 @@
import { ObjectMap } from '@0x/types';
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.
@@ -10,3 +10,14 @@ import { Actor } from './base';
export function actorAddressesByName(actors: Actor[]): ObjectMap<string> {
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>;
}

View File

@@ -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 { BigNumber, logUtils } from '@0x/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';
@@ -16,45 +17,44 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
/* tslint:disable:no-non-null-assertion */
export function validCreateStakingPoolAssertion(
deployment: DeploymentManager,
pools: StakingPoolById,
simulationEnvironment: SimulationEnvironment,
): FunctionAssertion<[number, boolean], string, string> {
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[number, boolean], string, string>(
stakingWrapper.createStakingPool.bind(stakingWrapper),
{
// Returns the expected ID of th created pool
before: async () => {
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
// Effectively the last poolId + 1, but as a bytestring
return `0x${new BigNumber(lastPoolId)
.plus(1)
.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(),
};
},
return new FunctionAssertion<[number, boolean], string, string>(stakingWrapper, 'createStakingPool', {
// Returns the expected ID of th created pool
before: async () => {
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
// Effectively the last poolId + 1, but as a bytestring
return `0x${new BigNumber(lastPoolId)
.plus(1)
.toString(16)
.padStart(64, '0')}`;
},
);
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*/

View File

@@ -1,6 +1,5 @@
import { StakingPoolById } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils';
import { logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { DeploymentManager } from '../deployment_manager';
@@ -17,21 +16,19 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
): FunctionAssertion<[string, number], {}, void> {
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[string, number], {}, void>(
stakingWrapper.decreaseStakingPoolOperatorShare.bind(stakingWrapper),
{
after: async (_beforeInfo, _result: FunctionResult, args: [string, number], txData: Partial<TxData>) => {
const [poolId, expectedOperatorShare] = args;
return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', {
after: async (_beforeInfo, result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
const [poolId, expectedOperatorShare] = args;
// Checks that the on-chain pool's operator share has been updated.
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
// Checks that the on-chain pool's operator share has been updated.
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
// Updates the pool in local state.
pools[poolId].operatorShare = operatorShare;
},
// Updates the pool in local state.
pools[poolId].operatorShare = operatorShare;
},
);
});
}

View 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);
},
});
}

View File

@@ -1,21 +1,41 @@
import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
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 { BigNumber, logUtils } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
import * as _ from 'lodash';
import { Maker } from '../actors/maker';
import { filterActorsByRole } from '../actors/utils';
import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
import { FunctionAssertion, FunctionResult } from './function_assertion';
function verifyFillEvents(
takerAddress: string,
txData: Partial<TxData>,
order: Order,
receipt: TransactionReceiptWithDecodedLogs,
deployment: DeploymentManager,
takerAssetFillAmount: BigNumber,
): 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.
verifyEvents<ExchangeFillEventArgs>(
receipt,
@@ -30,38 +50,54 @@ function verifyFillEvents(
orderHash: orderHashUtils.getOrderHashHex(order),
takerAddress,
senderAddress: takerAddress,
makerAssetFilledAmount: order.makerAssetAmount,
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: DeploymentManager.protocolFee,
...fillResults,
},
],
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.
verifyEvents<ERC20TokenTransferEventArgs>(
receipt,
[
{
_from: takerAddress,
_to: order.makerAddress,
_value: order.takerAssetAmount,
},
{
_from: order.makerAddress,
_to: takerAddress,
_value: order.makerAssetAmount,
},
{
_from: takerAddress,
_to: deployment.staking.stakingProxy.address,
_value: DeploymentManager.protocolFee,
},
],
ERC20TokenEvents.Transfer,
);
verifyEvents<ERC20TokenTransferEventArgs>(receipt, expectedTransferEvents, ERC20TokenEvents.Transfer);
}
interface FillOrderBeforeInfo {
poolStats: PoolStats;
aggregatedStats: AggregatedStats;
poolStake: BigNumber;
operatorStake: BigNumber;
poolId: string;
}
/**
@@ -69,31 +105,117 @@ function verifyFillEvents(
*/
/* tslint:disable:no-unnecessary-type-assertion */
/* tslint:disable:no-non-null-assertion */
export function validFillOrderCompleteFillAssertion(
export function validFillOrderAssertion(
deployment: DeploymentManager,
): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> {
const exchange = deployment.exchange;
simulationEnvironment: SimulationEnvironment,
): 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), {
after: async (
_beforeInfo,
result: FunctionResult,
args: [Order, BigNumber, string],
txData: Partial<TxData>,
) => {
const [order] = args;
return new FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults>(
deployment.exchange,
'fillOrder',
{
before: async (args: [Order, BigNumber, string]) => {
const [order] = args;
const { currentEpoch } = simulationEnvironment;
const maker = filterActorsByRole(actors, Maker).find(actor => actor.address === order.makerAddress);
// Ensure that the tx succeeded.
expect(result.success).to.be.true();
const poolId = maker!.makerPoolId;
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.
verifyFillEvents(txData.from!, order, result.receipt!, deployment);
const [order, fillAmount] = args;
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-unnecessary-type-assertion */

View 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 */

View File

@@ -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 * as _ from 'lodash';
import { logger } from '../utils/logger';
// tslint:disable:max-classes-per-file
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> {
// A condition that will be applied to `wrapperFunction`.
public 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>;
public readonly condition: Condition<TArgs, TBefore>;
constructor(
wrapperFunction: (
...args: TArgs // tslint:disable-line:trailing-comma
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>,
private readonly _contractWrapper: BaseContract,
private readonly _functionName: string,
condition: Partial<Condition<TArgs, TBefore>> = {},
) {
this.condition = {
before: async (args: TArgs, txData: Partial<TxData>) => {
before: async (_args: TArgs, _txData: Partial<TxData>) => {
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;
},
...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.
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
// result and receipt to the after condition.
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.receipt =
functionWithArgs.awaitTransactionSuccessAsync !== undefined

View File

@@ -1,6 +1,5 @@
import { StakingEvents, StakingMakerStakingPoolSetEventArgs } from '@0x/contracts-staking';
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
import { logUtils } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { DeploymentManager } from '../deployment_manager';
@@ -12,16 +11,18 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
*/
/* tslint:disable:no-unnecessary-type-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;
return new FunctionAssertion<[string], {}, void>(stakingWrapper.joinStakingPoolAsMaker.bind(stakingWrapper), {
after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial<TxData>) => {
return new FunctionAssertion<[string], void, void>(stakingWrapper, 'joinStakingPoolAsMaker', {
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;
expect(_result.success).to.be.true();
const logs = _result.receipt!.logs;
// Verify a MakerStakingPoolSet event was emitted
const logs = result.receipt!.logs;
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
logs,
StakingEvents.MakerStakingPoolSet,
@@ -32,10 +33,9 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu
poolId,
},
]);
// Verify that the maker's pool id has been updated in storage
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
expect(joinedPoolId).to.be.eq(poolId);
logUtils.log(`Pool ${poolId} joined by ${txData.from}`);
},
});
}

View File

@@ -1,146 +1,199 @@
import {
GlobalStakeByStatus,
decreaseNextBalance,
increaseNextBalance,
loadCurrentBalance,
OwnerStakeByStatus,
StakeInfo,
StakeStatus,
StakingPoolById,
StoredBalance,
} from '@0x/contracts-staking';
import { constants, expect } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { expect } 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';
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(
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
from: StakeInfo,
to: StakeInfo,
amount: BigNumber,
simulationEnvironment: SimulationEnvironment,
): string[] {
const { globalStake, stakingPools, currentEpoch } = simulationEnvironment;
// The on-chain state of these updated pools will be verified in the `after` of the assertion.
const updatedPools = [];
// Decrement next epoch balances associated with the `from` stake
if (from.status === StakeStatus.Undelegated) {
// Decrement owner undelegated stake
decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
ownerStake[StakeStatus.Undelegated] = decreaseNextBalance(
ownerStake[StakeStatus.Undelegated],
amount,
currentEpoch,
);
// Decrement global undelegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
globalStake[StakeStatus.Undelegated] = decreaseNextBalance(
globalStake[StakeStatus.Undelegated],
amount,
currentEpoch,
);
} else if (from.status === StakeStatus.Delegated) {
// 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
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
ownerStake[StakeStatus.Delegated].total = decreaseNextBalance(
ownerStake[StakeStatus.Delegated].total,
amount,
currentEpoch,
);
// Decrement global delegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
globalStake[StakeStatus.Delegated] = decreaseNextBalance(
globalStake[StakeStatus.Delegated],
amount,
currentEpoch,
);
// 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);
// TODO: Check that delegator rewards have been withdrawn/synced
}
// Increment next epoch balances associated with the `to` stake
if (to.status === StakeStatus.Undelegated) {
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount);
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount);
// Increment owner undelegated stake
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) {
// Initializes the balance for this pool if the user has not previously delegated to it
_.defaults(ownerStake[StakeStatus.Delegated], {
[to.poolId]: new StoredBalance(),
});
// 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
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount);
ownerStake[StakeStatus.Delegated].total = increaseNextBalance(
ownerStake[StakeStatus.Delegated].total,
amount,
currentEpoch,
);
// Increment global delegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount);
globalStake[StakeStatus.Delegated] = increaseNextBalance(
globalStake[StakeStatus.Delegated],
amount,
currentEpoch,
);
// 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);
// TODO: Check that delegator rewards have been withdrawn/synced
}
return updatedPools;
}
/**
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
* FunctionAssertion checks that the staker's
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. Checks that
* the owner's stake and global stake by status get updated correctly.
*/
/* tslint:disable:no-unnecessary-type-assertion */
export function validMoveStakeAssertion(
deployment: DeploymentManager,
globalStake: GlobalStakeByStatus,
simulationEnvironment: SimulationEnvironment,
ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> {
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(
stakingWrapper.moveStake.bind(stakingWrapper),
{
after: async (
_beforeInfo: {},
_result: FunctionResult,
args: [StakeInfo, StakeInfo, BigNumber],
txData: Partial<TxData>,
) => {
const [from, to, amount] = args;
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', {
after: async (
_beforeInfo: void,
result: FunctionResult,
args: [StakeInfo, StakeInfo, BigNumber],
txData: Partial<TxData>,
) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
logUtils.log(
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
StakeStatus[to.status]
}, poolId: ${to.poolId} }, ${amount})`,
const [from, to, amount] = args;
const { stakingPools, globalStake, currentEpoch } = simulationEnvironment;
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),
);
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(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);
}
},
expect(totalStakeDelegated).to.deep.equal(
loadCurrentBalance(stakingPools[poolId].delegatedStake, currentEpoch),
);
}
},
);
});
}
/* tslint:enable:no-unnecessary-type-assertion */

View File

@@ -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 { BigNumber, logUtils } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
import { LocalBalanceStore } from '../balances/local_balance_store';
import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
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
* 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 */
export function validStakeAssertion(
deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore,
globalStake: GlobalStakeByStatus,
simulationEnvironment: SimulationEnvironment,
ownerStake: OwnerStakeByStatus,
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
const { stakingWrapper, zrxVault } = deployment.staking;
return new FunctionAssertion(stakingWrapper.stake.bind(stakingWrapper), {
return new FunctionAssertion(stakingWrapper, 'stake', {
before: async (args: [BigNumber], txData: Partial<TxData>) => {
const [amount] = args;
const { balanceStore } = simulationEnvironment;
// Simulates the transfer of ZRX from staker to vault
const expectedBalances = LocalBalanceStore.create(balanceStore);
expectedBalances.transferAsset(
txData.from!, // tslint:disable-line:no-non-null-assertion
txData.from as string,
zrxVault.address,
amount,
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
@@ -50,35 +39,45 @@ export function validStakeAssertion(
},
after: async (
expectedBalances: LocalBalanceStore,
_result: FunctionResult,
result: FunctionResult,
args: [BigNumber],
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.
await balanceStore.updateErc20BalancesAsync();
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
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) // tslint:disable-line:no-non-null-assertion
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
// Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
// Checks that the global undelegated stake has also increased by the stake amount
const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync();
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
globalStake[StakeStatus.Undelegated],
);
},
});
}

View File

@@ -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 { BigNumber, logUtils } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types';
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
import { LocalBalanceStore } from '../balances/local_balance_store';
import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
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
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX increase and decrease,
* respectively, by the input amount.
*/
/* tslint:disable:no-unnecessary-type-assertion */
/* tslint:disable:no-non-null-assertion */
export function validUnstakeAssertion(
deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore,
globalStake: GlobalStakeByStatus,
simulationEnvironment: SimulationEnvironment,
ownerStake: OwnerStakeByStatus,
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
const { stakingWrapper, zrxVault } = deployment.staking;
return new FunctionAssertion(stakingWrapper.unstake.bind(stakingWrapper), {
return new FunctionAssertion(stakingWrapper, 'unstake', {
before: async (args: [BigNumber], txData: Partial<TxData>) => {
const [amount] = args;
const { balanceStore } = simulationEnvironment;
// Simulates the transfer of ZRX from vault to staker
const expectedBalances = LocalBalanceStore.create(balanceStore);
expectedBalances.transferAsset(
zrxVault.address,
txData.from!,
txData.from as string,
amount,
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
);
@@ -51,37 +39,46 @@ export function validUnstakeAssertion(
},
after: async (
expectedBalances: LocalBalanceStore,
_result: FunctionResult,
result: FunctionResult,
args: [BigNumber],
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.
await balanceStore.updateErc20BalancesAsync();
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
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated)
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
// Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
// 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
.getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync();
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
globalStake[StakeStatus.Undelegated],
);
},
});
}
/* tslint:enable:no-non-null-assertion */
/* tslint:enable:no-unnecessary-type-assertion */

View File

@@ -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 */

View File

@@ -1,5 +1,5 @@
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 { TokenAddresses, TokenContractsByName, TokenOwnersByName } from './types';
@@ -68,6 +68,14 @@ export class BalanceStore {
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.
* @param address The address to get the name for.

View File

@@ -25,7 +25,7 @@ import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types';
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.
@@ -393,7 +393,16 @@ export class DeploymentManager {
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.
await stakingWrapper.setWethContract(tokens.weth.address).awaitTransactionSuccessAsync({ from: owner });

View File

@@ -1,10 +1,17 @@
import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking';
import * as _ from 'lodash';
import {
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 { BlockchainBalanceStore } from './balances/blockchain_balance_store';
import { DeploymentManager } from './deployment_manager';
import { logger } from './utils/logger';
// tslint:disable:max-classes-per-file
@@ -14,12 +21,29 @@ export class SimulationEnvironment {
[StakeStatus.Delegated]: new StoredBalance(),
};
public stakingPools: StakingPoolById = {};
public currentEpoch: BigNumber = stakingConstants.INITIAL_EPOCH;
public constructor(
public readonly deployment: DeploymentManager,
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 {
@@ -30,14 +54,23 @@ export abstract class Simulation {
public async fuzzAsync(steps?: number): Promise<void> {
if (steps !== undefined) {
for (let i = 0; i < steps; i++) {
await this.generator.next();
await this._stepAsync();
}
} else {
while (true) {
await this.generator.next();
await this._stepAsync();
}
}
}
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;
}
}
}

View File

@@ -2,7 +2,7 @@ import { constants as stakingConstants } from '@0x/contracts-staking';
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { DeploymentManager } from '../deployment_manager';
import { Authorizable, Ownable } from '../wrapper_interfaces';
import { Authorizable, Ownable } from '../utils/wrapper_interfaces';
blockchainTests('Deployment Manager', env => {
let owner: string;

View File

@@ -23,14 +23,11 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
describe('executeAsync', () => {
it('should call the before function with the provided arguments', async () => {
let sideEffectTarget = ZERO_AMOUNT;
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
exampleContract.returnInteger.bind(exampleContract),
{
before: async (args: [BigNumber], txData: Partial<TxData>) => {
sideEffectTarget = randomInput;
},
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
before: async (args: [BigNumber], txData: Partial<TxData>) => {
sideEffectTarget = randomInput;
},
);
});
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
await assertion.executeAsync([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 () => {
let sideEffectTarget = ZERO_AMOUNT;
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
exampleContract.returnInteger.bind(exampleContract),
{
after: async (
_beforeInfo: any,
_result: FunctionResult,
args: [BigNumber],
txData: Partial<TxData>,
) => {
[sideEffectTarget] = args;
},
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
after: async (
_beforeInfo: any,
_result: FunctionResult,
args: [BigNumber],
txData: Partial<TxData>,
) => {
[sideEffectTarget] = args;
},
);
});
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
await assertion.executeAsync([randomInput], {});
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
});
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([], {});
});
@@ -65,7 +59,8 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
let sideEffectTarget = ZERO_AMOUNT;
const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>(
exampleContract.returnInteger.bind(exampleContract),
exampleContract,
'returnInteger',
{
before: async (_args: [BigNumber], _txData: Partial<TxData>) => {
return randomInput;
@@ -86,19 +81,16 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
it('should pass the result from the function call to "after"', async () => {
let sideEffectTarget = ZERO_AMOUNT;
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
exampleContract.returnInteger.bind(exampleContract),
{
after: async (
_beforeInfo: any,
result: FunctionResult,
_args: [BigNumber],
_txData: Partial<TxData>,
) => {
sideEffectTarget = result.data;
},
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
after: async (
_beforeInfo: any,
result: FunctionResult,
_args: [BigNumber],
_txData: Partial<TxData>,
) => {
sideEffectTarget = result.data;
},
);
});
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
await assertion.executeAsync([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 () => {
let sideEffectTarget: TransactionReceiptWithDecodedLogs;
const assertion = new FunctionAssertion<[string], void, void>(
exampleContract.emitEvent.bind(exampleContract),
{
after: async (
_beforeInfo: any,
result: FunctionResult,
_args: [string],
_txData: Partial<TxData>,
) => {
if (result.receipt) {
sideEffectTarget = result.receipt;
}
},
const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'emitEvent', {
after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial<TxData>) => {
if (result.receipt) {
sideEffectTarget = result.receipt;
}
},
);
});
const input = 'emitted data';
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 () => {
let sideEffectTarget: Error;
const assertion = new FunctionAssertion<[string], void, void>(
exampleContract.stringRevert.bind(exampleContract),
{
after: async (
_beforeInfo: any,
result: FunctionResult,
_args: [string],
_txData: Partial<TxData>,
) => {
sideEffectTarget = result.data;
},
const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'stringRevert', {
after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial<TxData>) => {
sideEffectTarget = result.data;
},
);
});
const message = 'error message';
await assertion.executeAsync([message], {});

View 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();

View 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();

View File

@@ -1,34 +1,36 @@
import { blockchainTests } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import { Actor } from '../framework/actors/base';
import { PoolOperator } from '../framework/actors/pool_operator';
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';
export class PoolManagementSimulation extends Simulation {
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
const { deployment } = this.environment;
const operator = new PoolOperator({
name: 'Operator',
deployment,
simulationEnvironment: this.environment,
});
const { actors } = this.environment;
const operators = filterActorsByRole(actors, PoolOperator);
const actions = [
operator.simulationActions.validCreateStakingPool,
operator.simulationActions.validDecreaseStakingPoolOperatorShare,
...operators.map(operator => operator.simulationActions.validCreateStakingPool),
...operators.map(operator => operator.simulationActions.validDecreaseStakingPoolOperatorShare),
];
while (true) {
const action = _.sample(actions);
const action = Pseudorandom.sample(actions);
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 () => {
Actor.reset();
});
@@ -41,8 +43,12 @@ blockchainTests.skip('Pool management fuzz test', env => {
});
const balanceStore = new BlockchainBalanceStore({}, {});
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
const simulation = new PoolManagementSimulation(simulationEnv);
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, [
new PoolOperator({ deployment, name: 'Operator 1' }),
new PoolOperator({ deployment, name: 'Operator 2' }),
]);
const simulation = new PoolManagementSimulation(simulationEnvironment);
return simulation.fuzzAsync();
});
});

View File

@@ -1,76 +1,58 @@
import { blockchainTests, constants } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import { blockchainTests } from '@0x/contracts-test-utils';
import { Actor } from '../framework/actors/base';
import { MakerTaker } from '../framework/actors/hybrids';
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 { 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 { PoolManagementSimulation } from './pool_management_test';
class PoolMembershipSimulation extends Simulation {
export class PoolMembershipSimulation extends Simulation {
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 member = new MakerTaker({
name: 'member',
deployment,
simulationEnvironment: this.environment,
});
const actions = [
member.simulationActions.validJoinStakingPool,
member.simulationActions.validFillOrderCompleteFill,
...makers.map(maker => maker.simulationActions.validJoinStakingPool),
...takers.map(taker => taker.simulationActions.validFillOrder),
poolManagement.generator,
];
while (true) {
const action = _.sample(actions);
const action = Pseudorandom.sample(actions);
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
}
}
}
blockchainTests.skip('pool membership fuzz test', env => {
let deployment: DeploymentManager;
let maker: Maker;
blockchainTests('pool membership fuzz test', env => {
before(async function(): Promise<void> {
if (process.env.FUZZ_TEST !== 'pool_membership') {
this.skip();
}
});
before(async () => {
deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 2,
after(async () => {
Actor.reset();
});
it('fuzz', async () => {
const deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 4,
numErc721TokensToDeploy: 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(
{
StakingProxy: deployment.staking.stakingProxy.address,
@@ -79,8 +61,24 @@ blockchainTests.skip('pool membership fuzz test', env => {
{ erc20: { ZRX: deployment.tokens.zrx } },
);
const simulationEnv = new SimulationEnvironment(deployment, balanceStore, [maker]);
const simulation = new PoolMembershipSimulation(simulationEnv);
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 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();
});
});

View File

@@ -1,38 +1,45 @@
import { blockchainTests } from '@0x/contracts-test-utils';
import * as _ from 'lodash';
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 { 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 { PoolManagementSimulation } from './pool_management_test';
export class StakeManagementSimulation extends Simulation {
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 staker = new Staker({ name: 'Staker', deployment, simulationEnvironment: this.environment });
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
balanceStore.registerTokenOwner(staker.address, staker.name);
const actions = [
staker.simulationActions.validStake,
staker.simulationActions.validUnstake,
staker.simulationActions.validMoveStake,
...stakers.map(staker => staker.simulationActions.validStake),
...stakers.map(staker => staker.simulationActions.validUnstake),
...stakers.map(staker => staker.simulationActions.validMoveStake),
poolManagement.generator,
];
while (true) {
const action = _.sample(actions);
const action = Pseudorandom.sample(actions);
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 () => {
Actor.reset();
});
@@ -51,8 +58,21 @@ blockchainTests.skip('Stake management fuzz test', env => {
{ erc20: { ZRX: deployment.tokens.zrx } },
);
const simulationEnv = new SimulationEnvironment(deployment, balanceStore);
const simulation = new StakeManagementSimulation(simulationEnv);
const actors = [
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();
});
});

View 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();
});
});

View File

@@ -0,0 +1,6 @@
{
"extends": ["@0x/tslint-config"],
"rules": {
"no-invalid-this": false
}
}

View 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);
});
});
});

View 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 };

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "4.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "4.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.0.2 - _December 17, 2019_
* Dependencies updated
## v4.0.1 - _December 9, 2019_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-multisig",
"version": "4.0.1",
"version": "4.0.2",
"engines": {
"node": ">=6.12"
},
@@ -49,18 +49,18 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/multisig/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.0.1",
"@0x/contracts-asset-proxy": "^3.0.1",
"@0x/contracts-erc20": "^3.0.1",
"@0x/contracts-gen": "^2.0.1",
"@0x/contracts-test-utils": "^5.0.0",
"@0x/contracts-utils": "^4.0.1",
"@0x/dev-utils": "^3.0.1",
"@0x/sol-compiler": "^4.0.1",
"@0x/abi-gen": "^5.0.2",
"@0x/contracts-asset-proxy": "^3.0.2",
"@0x/contracts-erc20": "^3.0.2",
"@0x/contracts-gen": "^2.0.2",
"@0x/contracts-test-utils": "^5.0.1",
"@0x/contracts-utils": "^4.0.2",
"@0x/dev-utils": "^3.0.2",
"@0x/sol-compiler": "^4.0.2",
"@0x/tslint-config": "^4.0.0",
"@0x/types": "^3.1.0",
"@0x/utils": "^5.1.0",
"@0x/web3-wrapper": "^7.0.1",
"@0x/types": "^3.1.1",
"@0x/utils": "^5.1.1",
"@0x/web3-wrapper": "^7.0.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@@ -78,8 +78,8 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^6.0.1",
"@0x/typescript-typings": "^5.0.0",
"@0x/base-contract": "^6.0.2",
"@0x/typescript-typings": "^5.0.1",
"ethereum-types": "^3.0.0"
},
"publishConfig": {

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1576540892,
"version": "2.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1575931811,
"version": "2.0.1",

Some files were not shown because too many files have changed in this diff Show More