Compare commits
44 Commits
@0x/contra
...
@0x/contra
Author | SHA1 | Date | |
---|---|---|---|
|
51ca3109eb | ||
|
2bcb79dc44 | ||
|
ecec985649 | ||
|
994908549d | ||
|
6808e0d531 | ||
|
410c95308a | ||
|
bec1f23616 | ||
|
34596b7f83 | ||
|
5ca7169ee5 | ||
|
3300aaa1b9 | ||
|
54afc8a4a1 | ||
|
f19f4310f4 | ||
|
444125a7e1 | ||
|
56cbb69401 | ||
|
70870ffcd2 | ||
|
a556d91673 | ||
|
8ecbde8e1e | ||
|
a24b293818 | ||
|
cab5ebf94b | ||
|
a54b5baef2 | ||
|
c324fe204e | ||
|
37d972ed9e | ||
|
e4a3b1cb05 | ||
|
49538f272e | ||
|
1283232144 | ||
|
2f9891f0aa | ||
|
865a2b1fb0 | ||
|
1fde62eeb6 | ||
|
6754cd48e2 | ||
|
ccb477687a | ||
|
be0e6c8925 | ||
|
1c2cb947c0 | ||
|
4663eec950 | ||
|
fff3c1eb36 | ||
|
4b7434d1e8 | ||
|
8cc35a60e6 | ||
|
130653a1aa | ||
|
1dcbebd130 | ||
|
faf306ad23 | ||
|
d11cdcd5d2 | ||
|
0e59bd0bf3 | ||
|
c0c6154ec1 | ||
|
cb5384c2fb | ||
|
038c836fe5 |
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
@@ -64,6 +73,10 @@
|
||||
{
|
||||
"note": "Implement `KyberBridge`.",
|
||||
"pr": 2352
|
||||
},
|
||||
{
|
||||
"note": "Implement `DydxBridge`.",
|
||||
"pr": 2365
|
||||
}
|
||||
],
|
||||
"timestamp": 1575290197
|
||||
|
@@ -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_
|
||||
|
||||
|
241
contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
Normal file
241
contracts/asset-proxy/contracts/src/bridges/DydxBridge.sol
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "../interfaces/IERC20Bridge.sol";
|
||||
import "../interfaces/IDydxBridge.sol";
|
||||
import "../interfaces/IDydx.sol";
|
||||
|
||||
|
||||
contract DydxBridge is
|
||||
IERC20Bridge,
|
||||
IDydxBridge,
|
||||
DeploymentConstants
|
||||
{
|
||||
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Callback for `IERC20Bridge`. Deposits or withdraws tokens from a dydx account.
|
||||
/// Notes:
|
||||
/// 1. This bridge must be set as an operator of the input dydx account.
|
||||
/// 2. This function may only be called in the context of the 0x Exchange.
|
||||
/// 3. The maker or taker of the 0x order must be the dydx account owner.
|
||||
/// 4. Deposits into dydx are made from the `from` address.
|
||||
/// 5. Withdrawals from dydx are made to the `to` address.
|
||||
/// 6. Calling this function must always withdraw at least `amount`,
|
||||
/// otherwise the `ERC20Bridge` will revert.
|
||||
/// @param from The sender of the tokens and owner of the dydx account.
|
||||
/// @param to The recipient of the tokens.
|
||||
/// @param amount Minimum amount of `toTokenAddress` tokens to deposit or withdraw.
|
||||
/// @param encodedBridgeData An abi-encoded `BridgeData` struct.
|
||||
/// @return success The magic bytes if successful.
|
||||
function bridgeTransferFrom(
|
||||
address,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata encodedBridgeData
|
||||
)
|
||||
external
|
||||
returns (bytes4 success)
|
||||
{
|
||||
// Ensure that only the `ERC20BridgeProxy` can call this function.
|
||||
require(
|
||||
msg.sender == _getERC20BridgeProxyAddress(),
|
||||
"DydxBridge/ONLY_CALLABLE_BY_ERC20_BRIDGE_PROXY"
|
||||
);
|
||||
|
||||
// Decode bridge data.
|
||||
(BridgeData memory bridgeData) = abi.decode(encodedBridgeData, (BridgeData));
|
||||
|
||||
// The dydx accounts are owned by the `from` address.
|
||||
IDydx.AccountInfo[] memory accounts = _createAccounts(from, bridgeData);
|
||||
|
||||
// Create dydx actions to run on the dydx accounts.
|
||||
IDydx.ActionArgs[] memory actions = _createActions(
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
bridgeData
|
||||
);
|
||||
|
||||
// Run operation. This will revert on failure.
|
||||
IDydx(_getDydxAddress()).operate(accounts, actions);
|
||||
return BRIDGE_SUCCESS;
|
||||
}
|
||||
|
||||
/// @dev Creates an array of accounts for dydx to operate on.
|
||||
/// All accounts must belong to the same owner.
|
||||
/// @param accountOwner Owner of the dydx account.
|
||||
/// @param bridgeData A `BridgeData` struct.
|
||||
function _createAccounts(
|
||||
address accountOwner,
|
||||
BridgeData memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (IDydx.AccountInfo[] memory accounts)
|
||||
{
|
||||
uint256[] memory accountNumbers = bridgeData.accountNumbers;
|
||||
uint256 nAccounts = accountNumbers.length;
|
||||
accounts = new IDydx.AccountInfo[](nAccounts);
|
||||
for (uint256 i = 0; i < nAccounts; ++i) {
|
||||
accounts[i] = IDydx.AccountInfo({
|
||||
owner: accountOwner,
|
||||
number: accountNumbers[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Creates an array of actions to carry out on dydx.
|
||||
/// @param depositFrom Deposit value from this address (owner of the dydx account).
|
||||
/// @param withdrawTo Withdraw value to this address.
|
||||
/// @param amount The amount of value available to operate on.
|
||||
/// @param bridgeData A `BridgeData` struct.
|
||||
function _createActions(
|
||||
address depositFrom,
|
||||
address withdrawTo,
|
||||
uint256 amount,
|
||||
BridgeData memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (IDydx.ActionArgs[] memory actions)
|
||||
{
|
||||
BridgeAction[] memory bridgeActions = bridgeData.actions;
|
||||
uint256 nBridgeActions = bridgeActions.length;
|
||||
actions = new IDydx.ActionArgs[](nBridgeActions);
|
||||
for (uint256 i = 0; i < nBridgeActions; ++i) {
|
||||
// Cache current bridge action.
|
||||
BridgeAction memory bridgeAction = bridgeActions[i];
|
||||
|
||||
// Scale amount, if conversion rate is set.
|
||||
uint256 scaledAmount;
|
||||
if (bridgeAction.conversionRateDenominator > 0) {
|
||||
scaledAmount = LibMath.safeGetPartialAmountFloor(
|
||||
bridgeAction.conversionRateNumerator,
|
||||
bridgeAction.conversionRateDenominator,
|
||||
amount
|
||||
);
|
||||
} else {
|
||||
scaledAmount = amount;
|
||||
}
|
||||
|
||||
// Construct dydx action.
|
||||
if (bridgeAction.actionType == BridgeActionType.Deposit) {
|
||||
// Deposit tokens from the account owner into their dydx account.
|
||||
actions[i] = _createDepositAction(
|
||||
depositFrom,
|
||||
scaledAmount,
|
||||
bridgeAction
|
||||
);
|
||||
} else if (bridgeAction.actionType == BridgeActionType.Withdraw) {
|
||||
// Withdraw tokens from dydx to the `otherAccount`.
|
||||
actions[i] = _createWithdrawAction(
|
||||
withdrawTo,
|
||||
scaledAmount,
|
||||
bridgeAction
|
||||
);
|
||||
} else {
|
||||
// If all values in the `Action` enum are handled then this
|
||||
// revert is unreachable: Solidity will revert when casting
|
||||
// from `uint8` to `Action`.
|
||||
revert("DydxBridge/UNRECOGNIZED_BRIDGE_ACTION");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns a dydx `DepositAction`.
|
||||
/// @param depositFrom Deposit tokens from this address who is also the account owner.
|
||||
/// @param amount of tokens to deposit.
|
||||
/// @param bridgeAction A `BridgeAction` struct.
|
||||
/// @return depositAction The encoded dydx action.
|
||||
function _createDepositAction(
|
||||
address depositFrom,
|
||||
uint256 amount,
|
||||
BridgeAction memory bridgeAction
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IDydx.ActionArgs memory depositAction
|
||||
)
|
||||
{
|
||||
// Create dydx amount.
|
||||
IDydx.AssetAmount memory dydxAmount = IDydx.AssetAmount({
|
||||
sign: true, // true if positive.
|
||||
denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account.
|
||||
ref: IDydx.AssetReference.Delta, // Delta => a relative amount.
|
||||
value: amount // amount to deposit.
|
||||
});
|
||||
|
||||
// Create dydx deposit action.
|
||||
depositAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Deposit, // deposit tokens.
|
||||
amount: dydxAmount, // amount to deposit.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to deposit.
|
||||
otherAddress: depositFrom, // deposit from the account owner.
|
||||
// unused parameters
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns a dydx `WithdrawAction`.
|
||||
/// @param withdrawTo Withdraw tokens to this address.
|
||||
/// @param amount of tokens to withdraw.
|
||||
/// @param bridgeAction A `BridgeAction` struct.
|
||||
/// @return withdrawAction The encoded dydx action.
|
||||
function _createWithdrawAction(
|
||||
address withdrawTo,
|
||||
uint256 amount,
|
||||
BridgeAction memory bridgeAction
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IDydx.ActionArgs memory withdrawAction
|
||||
)
|
||||
{
|
||||
// Create dydx amount.
|
||||
IDydx.AssetAmount memory amountToWithdraw = IDydx.AssetAmount({
|
||||
sign: false, // false if negative.
|
||||
denomination: IDydx.AssetDenomination.Wei, // Wei => actual token amount held in account.
|
||||
ref: IDydx.AssetReference.Delta, // Delta => a relative amount.
|
||||
value: amount // amount to withdraw.
|
||||
});
|
||||
|
||||
// Create withdraw action.
|
||||
withdrawAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Withdraw, // withdraw tokens.
|
||||
amount: amountToWithdraw, // amount to withdraw.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to withdraw.
|
||||
otherAddress: withdrawTo, // withdraw tokens to this address.
|
||||
// unused parameters
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
}
|
89
contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
Normal file
89
contracts/asset-proxy/contracts/src/interfaces/IDydx.sol
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
interface IDydx {
|
||||
|
||||
/// @dev Represents the unique key that specifies an account
|
||||
struct AccountInfo {
|
||||
address owner; // The address that owns the account
|
||||
uint256 number; // A nonce that allows a single address to control many accounts
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
Deposit, // supply tokens
|
||||
Withdraw, // borrow tokens
|
||||
Transfer, // transfer balance between accounts
|
||||
Buy, // buy an amount of some token (externally)
|
||||
Sell, // sell an amount of some token (externally)
|
||||
Trade, // trade tokens against another account
|
||||
Liquidate, // liquidate an undercollateralized or expiring account
|
||||
Vaporize, // use excess tokens to zero-out a completely negative account
|
||||
Call // send arbitrary data to an address
|
||||
}
|
||||
|
||||
/// @dev Arguments that are passed to Solo in an ordered list as part of a single operation.
|
||||
/// Each ActionArgs has an actionType which specifies which action struct that this data will be
|
||||
/// parsed into before being processed.
|
||||
struct ActionArgs {
|
||||
ActionType actionType;
|
||||
uint256 accountId;
|
||||
AssetAmount amount;
|
||||
uint256 primaryMarketId;
|
||||
uint256 secondaryMarketId;
|
||||
address otherAddress;
|
||||
uint256 otherAccountId;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
enum AssetDenomination {
|
||||
Wei, // the amount is denominated in wei
|
||||
Par // the amount is denominated in par
|
||||
}
|
||||
|
||||
enum AssetReference {
|
||||
Delta, // the amount is given as a delta from the current value
|
||||
Target // the amount is given as an exact number to end up at
|
||||
}
|
||||
|
||||
struct AssetAmount {
|
||||
bool sign; // true if positive
|
||||
AssetDenomination denomination;
|
||||
AssetReference ref;
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
/// @dev The main entry-point to Solo that allows users and contracts to manage accounts.
|
||||
/// Take one or more actions on one or more accounts. The msg.sender must be the owner or
|
||||
/// operator of all accounts except for those being liquidated, vaporized, or traded with.
|
||||
/// One call to operate() is considered a singular "operation". Account collateralization is
|
||||
/// ensured only after the completion of the entire operation.
|
||||
/// @param accounts A list of all accounts that will be used in this operation. Cannot contain
|
||||
/// duplicates. In each action, the relevant account will be referred-to by its
|
||||
/// index in the list.
|
||||
/// @param actions An ordered list of all actions that will be taken in this operation. The
|
||||
/// actions will be processed in order.
|
||||
function operate(
|
||||
AccountInfo[] calldata accounts,
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external;
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
|
||||
interface IDydxBridge {
|
||||
|
||||
/// @dev This is the subset of `IDydx.ActionType` that are supported by the bridge.
|
||||
enum BridgeActionType {
|
||||
Deposit, // Deposit tokens into dydx account.
|
||||
Withdraw // Withdraw tokens from dydx account.
|
||||
}
|
||||
|
||||
struct BridgeAction {
|
||||
BridgeActionType actionType; // Action to run on dydx account.
|
||||
uint256 accountId; // Index in `BridgeData.accountNumbers` for this action.
|
||||
uint256 marketId; // Market to operate on.
|
||||
uint256 conversionRateNumerator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
uint256 conversionRateDenominator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
}
|
||||
|
||||
struct BridgeData {
|
||||
uint256[] accountNumbers; // Account number used to identify the owner's specific account.
|
||||
BridgeAction[] actions; // Actions to carry out on the owner's accounts.
|
||||
}
|
||||
}
|
101
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
101
contracts/asset-proxy/contracts/test/TestDydxBridge.sol
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/bridges/DydxBridge.sol";
|
||||
|
||||
|
||||
// solhint-disable space-after-comma
|
||||
contract TestDydxBridge is
|
||||
IDydx,
|
||||
DydxBridge
|
||||
{
|
||||
|
||||
address private constant ALWAYS_REVERT_ADDRESS = address(1);
|
||||
|
||||
event OperateAccount(
|
||||
address owner,
|
||||
uint256 number
|
||||
);
|
||||
|
||||
event OperateAction(
|
||||
ActionType actionType,
|
||||
uint256 accountId,
|
||||
bool amountSign,
|
||||
AssetDenomination amountDenomination,
|
||||
AssetReference amountRef,
|
||||
uint256 amountValue,
|
||||
uint256 primaryMarketId,
|
||||
uint256 secondaryMarketId,
|
||||
address otherAddress,
|
||||
uint256 otherAccountId,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/// @dev Simulates `operate` in dydx contract.
|
||||
/// Emits events so that arguments can be validated client-side.
|
||||
function operate(
|
||||
AccountInfo[] calldata accounts,
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external
|
||||
{
|
||||
for (uint i = 0; i < accounts.length; ++i) {
|
||||
emit OperateAccount(
|
||||
accounts[i].owner,
|
||||
accounts[i].number
|
||||
);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < actions.length; ++i) {
|
||||
emit OperateAction(
|
||||
actions[i].actionType,
|
||||
actions[i].accountId,
|
||||
actions[i].amount.sign,
|
||||
actions[i].amount.denomination,
|
||||
actions[i].amount.ref,
|
||||
actions[i].amount.value,
|
||||
actions[i].primaryMarketId,
|
||||
actions[i].secondaryMarketId,
|
||||
actions[i].otherAddress,
|
||||
actions[i].otherAccountId,
|
||||
actions[i].data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev overrides `_getDydxAddress()` from `DeploymentConstants` to return this address.
|
||||
function _getDydxAddress()
|
||||
internal
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return address(this);
|
||||
}
|
||||
|
||||
/// @dev overrides `_getERC20BridgeProxyAddress()` from `DeploymentConstants` for testing.
|
||||
function _getERC20BridgeProxyAddress()
|
||||
internal
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return msg.sender == ALWAYS_REVERT_ADDRESS ? address(0) : msg.sender;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-asset-proxy",
|
||||
"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"
|
||||
},
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -5,6 +5,7 @@ export {
|
||||
ERC20ProxyContract,
|
||||
ERC721ProxyContract,
|
||||
Eth2DaiBridgeContract,
|
||||
DydxBridgeContract,
|
||||
IAssetDataContract,
|
||||
IAssetProxyContract,
|
||||
MultiAssetProxyContract,
|
||||
|
@@ -4,6 +4,7 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/chai_bridge';
|
||||
export * from '../generated-wrappers/dydx_bridge';
|
||||
export * from '../generated-wrappers/erc1155_proxy';
|
||||
export * from '../generated-wrappers/erc20_bridge_proxy';
|
||||
export * from '../generated-wrappers/erc20_proxy';
|
||||
@@ -11,8 +12,27 @@ export * from '../generated-wrappers/erc721_proxy';
|
||||
export * from '../generated-wrappers/eth2_dai_bridge';
|
||||
export * from '../generated-wrappers/i_asset_data';
|
||||
export * from '../generated-wrappers/i_asset_proxy';
|
||||
export * from '../generated-wrappers/i_asset_proxy_dispatcher';
|
||||
export * from '../generated-wrappers/i_authorizable';
|
||||
export * from '../generated-wrappers/i_chai';
|
||||
export * from '../generated-wrappers/i_dydx';
|
||||
export * from '../generated-wrappers/i_dydx_bridge';
|
||||
export * from '../generated-wrappers/i_erc20_bridge';
|
||||
export * from '../generated-wrappers/i_eth2_dai';
|
||||
export * from '../generated-wrappers/i_kyber_network_proxy';
|
||||
export * from '../generated-wrappers/i_uniswap_exchange';
|
||||
export * from '../generated-wrappers/i_uniswap_exchange_factory';
|
||||
export * from '../generated-wrappers/kyber_bridge';
|
||||
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
|
||||
export * from '../generated-wrappers/mixin_authorizable';
|
||||
export * from '../generated-wrappers/multi_asset_proxy';
|
||||
export * from '../generated-wrappers/ownable';
|
||||
export * from '../generated-wrappers/static_call_proxy';
|
||||
export * from '../generated-wrappers/test_chai_bridge';
|
||||
export * from '../generated-wrappers/test_dydx_bridge';
|
||||
export * from '../generated-wrappers/test_erc20_bridge';
|
||||
export * from '../generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../generated-wrappers/test_kyber_bridge';
|
||||
export * from '../generated-wrappers/test_static_call_target';
|
||||
export * from '../generated-wrappers/test_uniswap_bridge';
|
||||
export * from '../generated-wrappers/uniswap_bridge';
|
||||
|
@@ -6,6 +6,7 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json';
|
||||
import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json';
|
||||
import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json';
|
||||
import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json';
|
||||
import * as ERC20Proxy from '../test/generated-artifacts/ERC20Proxy.json';
|
||||
@@ -16,6 +17,8 @@ import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json';
|
||||
import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json';
|
||||
import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json';
|
||||
import * as IChai from '../test/generated-artifacts/IChai.json';
|
||||
import * as IDydx from '../test/generated-artifacts/IDydx.json';
|
||||
import * as IDydxBridge from '../test/generated-artifacts/IDydxBridge.json';
|
||||
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
|
||||
@@ -28,6 +31,7 @@ import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.js
|
||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
||||
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
|
||||
import * as TestChaiBridge from '../test/generated-artifacts/TestChaiBridge.json';
|
||||
import * as TestDydxBridge from '../test/generated-artifacts/TestDydxBridge.json';
|
||||
import * as TestERC20Bridge from '../test/generated-artifacts/TestERC20Bridge.json';
|
||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||
import * as TestKyberBridge from '../test/generated-artifacts/TestKyberBridge.json';
|
||||
@@ -45,6 +49,7 @@ export const artifacts = {
|
||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||
ChaiBridge: ChaiBridge as ContractArtifact,
|
||||
DydxBridge: DydxBridge as ContractArtifact,
|
||||
Eth2DaiBridge: Eth2DaiBridge as ContractArtifact,
|
||||
KyberBridge: KyberBridge as ContractArtifact,
|
||||
UniswapBridge: UniswapBridge as ContractArtifact,
|
||||
@@ -53,12 +58,15 @@ export const artifacts = {
|
||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||
IChai: IChai as ContractArtifact,
|
||||
IDydx: IDydx as ContractArtifact,
|
||||
IDydxBridge: IDydxBridge as ContractArtifact,
|
||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
|
||||
IUniswapExchange: IUniswapExchange as ContractArtifact,
|
||||
IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
|
||||
TestChaiBridge: TestChaiBridge as ContractArtifact,
|
||||
TestDydxBridge: TestDydxBridge as ContractArtifact,
|
||||
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
TestKyberBridge: TestKyberBridge as ContractArtifact,
|
||||
|
244
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
244
contracts/asset-proxy/test/dydx_bridge.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId, RevertReason } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { TestDydxBridgeContract, TestDydxBridgeEvents } from './wrappers';
|
||||
|
||||
blockchainTests.resets('DydxBridge unit tests', env => {
|
||||
const defaultAccountNumber = new BigNumber(1);
|
||||
const marketId = new BigNumber(2);
|
||||
const defaultAmount = new BigNumber(4);
|
||||
const notAuthorized = '0x0000000000000000000000000000000000000001';
|
||||
let testContract: TestDydxBridgeContract;
|
||||
let authorized: string;
|
||||
let accountOwner: string;
|
||||
let receiver: string;
|
||||
|
||||
before(async () => {
|
||||
// Get accounts
|
||||
const accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
[, /* owner */ authorized, accountOwner, receiver] = accounts;
|
||||
|
||||
// Deploy dydx bridge
|
||||
testContract = await TestDydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestDydxBridge,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
enum BridgeActionType {
|
||||
Deposit,
|
||||
Withdraw,
|
||||
}
|
||||
interface BrigeAction {
|
||||
actionType: BridgeActionType;
|
||||
accountId: BigNumber;
|
||||
marketId: BigNumber;
|
||||
conversionRateNumerator: BigNumber;
|
||||
conversionRateDenominator: BigNumber;
|
||||
}
|
||||
interface BridgeData {
|
||||
accountNumbers: BigNumber[];
|
||||
actions: BrigeAction[];
|
||||
}
|
||||
const defaultDepositAction = {
|
||||
actionType: BridgeActionType.Deposit as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: BridgeActionType.Withdraw as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const bridgeDataEncoder = AbiEncoder.create([
|
||||
{
|
||||
name: 'bridgeData',
|
||||
type: 'tuple',
|
||||
components: [
|
||||
{ name: 'accountNumbers', type: 'uint256[]' },
|
||||
{
|
||||
name: 'actions',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'actionType', type: 'uint8' },
|
||||
{ name: 'accountId', type: 'uint256' },
|
||||
{ name: 'marketId', type: 'uint256' },
|
||||
{ name: 'conversionRateNumerator', type: 'uint256' },
|
||||
{ name: 'conversionRateDenominator', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const callBridgeTransferFrom = async (
|
||||
from: string,
|
||||
to: string,
|
||||
amount: BigNumber,
|
||||
bridgeData: BridgeData,
|
||||
sender: string,
|
||||
): Promise<string> => {
|
||||
const returnValue = await testContract
|
||||
.bridgeTransferFrom(constants.NULL_ADDRESS, from, to, amount, bridgeDataEncoder.encode({ bridgeData }))
|
||||
.callAsync({ from: sender });
|
||||
return returnValue;
|
||||
};
|
||||
const callBridgeTransferFromAndVerifyEvents = async (
|
||||
from: string,
|
||||
to: string,
|
||||
amount: BigNumber,
|
||||
bridgeData: BridgeData,
|
||||
sender: string,
|
||||
): Promise<void> => {
|
||||
// Execute transaction.
|
||||
const txReceipt = await testContract
|
||||
.bridgeTransferFrom(constants.NULL_ADDRESS, from, to, amount, bridgeDataEncoder.encode({ bridgeData }))
|
||||
.awaitTransactionSuccessAsync({ from: sender });
|
||||
|
||||
// Verify `OperateAccount` event.
|
||||
const expectedOperateAccountEvents = [];
|
||||
for (const accountNumber of bridgeData.accountNumbers) {
|
||||
expectedOperateAccountEvents.push({
|
||||
owner: accountOwner,
|
||||
number: accountNumber,
|
||||
});
|
||||
}
|
||||
verifyEventsFromLogs(txReceipt.logs, expectedOperateAccountEvents, TestDydxBridgeEvents.OperateAccount);
|
||||
|
||||
// Verify `OperateAction` event.
|
||||
const weiDenomination = 0;
|
||||
const deltaAmountRef = 0;
|
||||
const expectedOperateActionEvents = [];
|
||||
for (const action of bridgeData.actions) {
|
||||
expectedOperateActionEvents.push({
|
||||
actionType: action.actionType as number,
|
||||
accountId: action.accountId,
|
||||
amountSign: action.actionType === BridgeActionType.Deposit ? true : false,
|
||||
amountDenomination: weiDenomination,
|
||||
amountRef: deltaAmountRef,
|
||||
amountValue: action.conversionRateDenominator.gt(0)
|
||||
? amount.times(action.conversionRateNumerator).div(action.conversionRateDenominator)
|
||||
: amount,
|
||||
primaryMarketId: marketId,
|
||||
secondaryMarketId: constants.ZERO_AMOUNT,
|
||||
otherAddress: action.actionType === BridgeActionType.Deposit ? from : to,
|
||||
otherAccountId: constants.ZERO_AMOUNT,
|
||||
data: '0x',
|
||||
});
|
||||
}
|
||||
verifyEventsFromLogs(txReceipt.logs, expectedOperateActionEvents, TestDydxBridgeEvents.OperateAction);
|
||||
};
|
||||
it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and a single accuont', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when calling `operate` with multiple actions under a single account', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultWithdrawAction, defaultDepositAction],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when scaling the `amount` to deposit', async () => {
|
||||
const conversionRateNumerator = new BigNumber(1);
|
||||
const conversionRateDenominator = new BigNumber(2);
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [
|
||||
defaultWithdrawAction,
|
||||
{
|
||||
...defaultDepositAction,
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
},
|
||||
],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('succeeds when scaling the `amount` to withdraw', async () => {
|
||||
const conversionRateNumerator = new BigNumber(1);
|
||||
const conversionRateDenominator = new BigNumber(2);
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [
|
||||
defaultDepositAction,
|
||||
{
|
||||
...defaultWithdrawAction,
|
||||
conversionRateNumerator,
|
||||
conversionRateDenominator,
|
||||
},
|
||||
],
|
||||
};
|
||||
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
|
||||
});
|
||||
it('reverts if not called by the ERC20 Bridge Proxy', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
const callBridgeTransferFromPromise = callBridgeTransferFrom(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
notAuthorized,
|
||||
);
|
||||
const expectedError = RevertReason.DydxBridgeOnlyCallableByErc20BridgeProxy;
|
||||
return expect(callBridgeTransferFromPromise).to.revertWith(expectedError);
|
||||
});
|
||||
it('should return magic bytes if call succeeds', async () => {
|
||||
const bridgeData = {
|
||||
accountNumbers: [defaultAccountNumber],
|
||||
actions: [defaultDepositAction],
|
||||
};
|
||||
const returnValue = await callBridgeTransferFrom(
|
||||
accountOwner,
|
||||
receiver,
|
||||
defaultAmount,
|
||||
bridgeData,
|
||||
authorized,
|
||||
);
|
||||
expect(returnValue).to.equal(AssetProxyId.ERC20Bridge);
|
||||
});
|
||||
});
|
||||
});
|
@@ -4,6 +4,7 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/chai_bridge';
|
||||
export * from '../test/generated-wrappers/dydx_bridge';
|
||||
export * from '../test/generated-wrappers/erc1155_proxy';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_proxy';
|
||||
export * from '../test/generated-wrappers/erc20_proxy';
|
||||
@@ -14,6 +15,8 @@ export * from '../test/generated-wrappers/i_asset_proxy';
|
||||
export * from '../test/generated-wrappers/i_asset_proxy_dispatcher';
|
||||
export * from '../test/generated-wrappers/i_authorizable';
|
||||
export * from '../test/generated-wrappers/i_chai';
|
||||
export * from '../test/generated-wrappers/i_dydx';
|
||||
export * from '../test/generated-wrappers/i_dydx_bridge';
|
||||
export * from '../test/generated-wrappers/i_erc20_bridge';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_network_proxy';
|
||||
@@ -26,6 +29,7 @@ export * from '../test/generated-wrappers/multi_asset_proxy';
|
||||
export * from '../test/generated-wrappers/ownable';
|
||||
export * from '../test/generated-wrappers/static_call_proxy';
|
||||
export * from '../test/generated-wrappers/test_chai_bridge';
|
||||
export * from '../test/generated-wrappers/test_dydx_bridge';
|
||||
export * from '../test/generated-wrappers/test_erc20_bridge';
|
||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../test/generated-wrappers/test_kyber_bridge';
|
||||
|
@@ -4,6 +4,7 @@
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/ChaiBridge.json",
|
||||
"generated-artifacts/DydxBridge.json",
|
||||
"generated-artifacts/ERC1155Proxy.json",
|
||||
"generated-artifacts/ERC20BridgeProxy.json",
|
||||
"generated-artifacts/ERC20Proxy.json",
|
||||
@@ -11,12 +12,32 @@
|
||||
"generated-artifacts/Eth2DaiBridge.json",
|
||||
"generated-artifacts/IAssetData.json",
|
||||
"generated-artifacts/IAssetProxy.json",
|
||||
"generated-artifacts/IAssetProxyDispatcher.json",
|
||||
"generated-artifacts/IAuthorizable.json",
|
||||
"generated-artifacts/IChai.json",
|
||||
"generated-artifacts/IDydx.json",
|
||||
"generated-artifacts/IDydxBridge.json",
|
||||
"generated-artifacts/IERC20Bridge.json",
|
||||
"generated-artifacts/IEth2Dai.json",
|
||||
"generated-artifacts/IKyberNetworkProxy.json",
|
||||
"generated-artifacts/IUniswapExchange.json",
|
||||
"generated-artifacts/IUniswapExchangeFactory.json",
|
||||
"generated-artifacts/KyberBridge.json",
|
||||
"generated-artifacts/MixinAssetProxyDispatcher.json",
|
||||
"generated-artifacts/MixinAuthorizable.json",
|
||||
"generated-artifacts/MultiAssetProxy.json",
|
||||
"generated-artifacts/Ownable.json",
|
||||
"generated-artifacts/StaticCallProxy.json",
|
||||
"generated-artifacts/TestChaiBridge.json",
|
||||
"generated-artifacts/TestDydxBridge.json",
|
||||
"generated-artifacts/TestERC20Bridge.json",
|
||||
"generated-artifacts/TestEth2DaiBridge.json",
|
||||
"generated-artifacts/TestKyberBridge.json",
|
||||
"generated-artifacts/TestStaticCallTarget.json",
|
||||
"generated-artifacts/TestUniswapBridge.json",
|
||||
"generated-artifacts/UniswapBridge.json",
|
||||
"test/generated-artifacts/ChaiBridge.json",
|
||||
"test/generated-artifacts/DydxBridge.json",
|
||||
"test/generated-artifacts/ERC1155Proxy.json",
|
||||
"test/generated-artifacts/ERC20BridgeProxy.json",
|
||||
"test/generated-artifacts/ERC20Proxy.json",
|
||||
@@ -27,6 +48,8 @@
|
||||
"test/generated-artifacts/IAssetProxyDispatcher.json",
|
||||
"test/generated-artifacts/IAuthorizable.json",
|
||||
"test/generated-artifacts/IChai.json",
|
||||
"test/generated-artifacts/IDydx.json",
|
||||
"test/generated-artifacts/IDydxBridge.json",
|
||||
"test/generated-artifacts/IERC20Bridge.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberNetworkProxy.json",
|
||||
@@ -39,6 +62,7 @@
|
||||
"test/generated-artifacts/Ownable.json",
|
||||
"test/generated-artifacts/StaticCallProxy.json",
|
||||
"test/generated-artifacts/TestChaiBridge.json",
|
||||
"test/generated-artifacts/TestDydxBridge.json",
|
||||
"test/generated-artifacts/TestERC20Bridge.json",
|
||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||
"test/generated-artifacts/TestKyberBridge.json",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "1.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "1.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "2.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "2.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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": {
|
||||
|
@@ -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",
|
||||
|
@@ -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
|
||||
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
|
||||
|
||||
contract DeploymentConstants {
|
||||
|
||||
/// @dev Address of the 0x Exchange contract.
|
||||
address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712;
|
||||
/// @dev Address of the Eth2Dai MatchingMarket contract.
|
||||
address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e;
|
||||
/// @dev Address of the UniswapExchangeFactory contract.
|
||||
address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
|
||||
/// @dev Address of the KyberNeworkProxy contract.
|
||||
address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755;
|
||||
/// @dev Address of the WETH contract.
|
||||
address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
/// @dev Kyber ETH pseudo-address.
|
||||
address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
|
||||
/// @dev An overridable way to retrieve the 0x Exchange contract.
|
||||
/// @return zeroex The 0x Exchange contract.
|
||||
function _getExchangeContract()
|
||||
internal
|
||||
view
|
||||
returns (IExchange zeroex)
|
||||
{
|
||||
return IExchange(EXCHANGE_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Eth2Dai exchange contract.
|
||||
/// @return eth2dai The Eth2Dai exchange contract.
|
||||
function _getEth2DaiContract()
|
||||
internal
|
||||
view
|
||||
returns (IEth2Dai eth2dai)
|
||||
{
|
||||
return IEth2Dai(ETH2DAI_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Uniswap exchange factory contract.
|
||||
/// @return uniswap The UniswapExchangeFactory contract.
|
||||
function _getUniswapExchangeFactoryContract()
|
||||
internal
|
||||
view
|
||||
returns (IUniswapExchangeFactory uniswap)
|
||||
{
|
||||
return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the Kyber network proxy contract.
|
||||
/// @return kyber The KyberNeworkProxy contract.
|
||||
function _getKyberNetworkContract()
|
||||
internal
|
||||
view
|
||||
returns (IKyberNetwork kyber)
|
||||
{
|
||||
return IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS);
|
||||
}
|
||||
|
||||
/// @dev An overridable way to retrieve the WETH contract address.
|
||||
/// @return weth The WETH contract address.
|
||||
function _getWETHAddress()
|
||||
internal
|
||||
view
|
||||
returns (address weth)
|
||||
{
|
||||
return WETH_ADDRESS;
|
||||
}
|
||||
}
|
@@ -23,11 +23,13 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IDevUtils.sol";
|
||||
import "./IERC20BridgeSampler.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
import "./IUniswapExchangeQuotes.sol";
|
||||
import "./DeploymentConstants.sol";
|
||||
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
45
contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
Normal file
45
contracts/erc20-bridge-sampler/contracts/src/IDevUtils.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IDevUtils {
|
||||
|
||||
/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
|
||||
/// @param order The order structure.
|
||||
/// @param signature Signature provided by maker that proves the order's authenticity.
|
||||
/// `0x01` can always be provided if the signature does not need to be validated.
|
||||
/// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order),
|
||||
/// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state),
|
||||
/// and isValidSignature (validity of the provided signature).
|
||||
/// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled"
|
||||
/// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final
|
||||
/// amount of each asset that can be filled.
|
||||
function getOrderRelevantState(LibOrder.Order calldata order, bytes calldata signature)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
);
|
||||
}
|
@@ -19,59 +19,82 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IERC20BridgeSampler {
|
||||
|
||||
/// @dev Query native orders and sample sell orders on multiple DEXes at once.
|
||||
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
||||
/// @param takerTokenAmounts Taker sell amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||
/// each taker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleSells(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address[] calldata sources,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableTakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
);
|
||||
|
||||
/// @dev Query native orders and sample buy orders on multiple DEXes at once.
|
||||
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param sources Address of each DEX. Passing in an unknown DEX will throw.
|
||||
/// @param makerTokenAmounts Maker sell amount for each sample.
|
||||
/// @return orderInfos `OrderInfo`s for each order in `orders`.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||
/// each maker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function queryOrdersAndSampleBuys(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address[] calldata sources,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory orderInfos,
|
||||
uint256[] memory orderFillableMakerAssetAmounts,
|
||||
uint256[][] memory makerTokenAmountsBySource
|
||||
);
|
||||
|
||||
/// @dev Queries the status of several native orders.
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @return orderInfos Order info for each respective order.
|
||||
function queryOrders(LibOrder.Order[] calldata orders)
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableTakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (LibOrder.OrderInfo[] memory orderInfos);
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts);
|
||||
|
||||
/// @dev Queries the fillable maker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableMakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts);
|
||||
|
||||
/// @dev Sample sell quotes on multiple DEXes at once.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
|
@@ -23,6 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "../src/ERC20BridgeSampler.sol";
|
||||
import "../src/IEth2Dai.sol";
|
||||
import "../src/IDevUtils.sol";
|
||||
import "../src/IKyberNetwork.sol";
|
||||
|
||||
|
||||
@@ -90,9 +91,28 @@ library LibDeterministicQuotes {
|
||||
}
|
||||
|
||||
|
||||
contract FailTrigger {
|
||||
|
||||
// Give this address a balance to force operations to fail.
|
||||
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
|
||||
|
||||
// Funds `FAILURE_ADDRESS`.
|
||||
function enableFailTrigger() external payable {
|
||||
FAILURE_ADDRESS.transfer(msg.value);
|
||||
}
|
||||
|
||||
function _revertIfShouldFail() internal view {
|
||||
if (FAILURE_ADDRESS.balance != 0) {
|
||||
revert("FAIL_TRIGGERED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerUniswapExchange is
|
||||
IUniswapExchangeQuotes,
|
||||
DeploymentConstants
|
||||
DeploymentConstants,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
|
||||
|
||||
@@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 tokensBought)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
salt,
|
||||
tokenAddress,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
ethSold
|
||||
);
|
||||
}
|
||||
@@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 ethSold)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
salt,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokenAddress,
|
||||
tokensBought
|
||||
);
|
||||
@@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 ethBought)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
salt,
|
||||
tokenAddress,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokensSold
|
||||
);
|
||||
}
|
||||
@@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
view
|
||||
returns (uint256 tokensSold)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
salt,
|
||||
WETH_ADDRESS,
|
||||
_getWethAddress(),
|
||||
tokenAddress,
|
||||
ethBought
|
||||
);
|
||||
@@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is
|
||||
|
||||
contract TestERC20BridgeSamplerKyberNetwork is
|
||||
IKyberNetwork,
|
||||
DeploymentConstants
|
||||
DeploymentConstants,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
|
||||
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
@@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
||||
view
|
||||
returns (uint256 expectedRate, uint256)
|
||||
{
|
||||
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken;
|
||||
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken;
|
||||
_revertIfShouldFail();
|
||||
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
||||
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
||||
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
||||
SALT,
|
||||
fromToken,
|
||||
@@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is
|
||||
|
||||
|
||||
contract TestERC20BridgeSamplerEth2Dai is
|
||||
IEth2Dai
|
||||
IEth2Dai,
|
||||
FailTrigger
|
||||
{
|
||||
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
|
||||
|
||||
@@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
||||
SALT,
|
||||
payToken,
|
||||
@@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is
|
||||
view
|
||||
returns (uint256 payAmount)
|
||||
{
|
||||
_revertIfShouldFail();
|
||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
||||
SALT,
|
||||
payToken,
|
||||
@@ -269,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);
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -5,16 +5,16 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json';
|
||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
|
||||
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||
export const artifacts = {
|
||||
DeploymentConstants: DeploymentConstants as ContractArtifact,
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
IDevUtils: IDevUtils as ContractArtifact,
|
||||
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
getRandomPortion,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { Order, OrderInfo } from '@0x/types';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const INVALID_ASSET_DATA = hexUtils.random(37);
|
||||
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
|
||||
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
|
||||
const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS';
|
||||
const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY';
|
||||
const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA';
|
||||
const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE';
|
||||
const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE';
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR';
|
||||
const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
|
||||
const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
|
||||
const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
|
||||
const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
@@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
return quotes;
|
||||
}
|
||||
|
||||
function getDeterministicOrderInfo(order: Order): OrderInfo {
|
||||
const hash = getPackedHash(hexUtils.leftPad(order.salt, 32));
|
||||
return {
|
||||
orderHash: hash,
|
||||
orderStatus: new BigNumber(hash).mod(255).toNumber(),
|
||||
orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount),
|
||||
};
|
||||
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
|
||||
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
|
||||
const orderStatus = new BigNumber(hash).mod(255).toNumber();
|
||||
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
|
||||
if (orderStatus !== 3 || !isValidSignature) {
|
||||
return constants.ZERO_AMOUNT;
|
||||
}
|
||||
return order.takerAssetAmount.minus(new BigNumber(hash).mod(order.takerAssetAmount));
|
||||
}
|
||||
|
||||
function getDeterministicFillableMakerAssetAmount(order: Order): BigNumber {
|
||||
const takerAmount = getDeterministicFillableTakerAssetAmount(order);
|
||||
return order.makerAssetAmount
|
||||
.times(takerAmount)
|
||||
.div(order.takerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
}
|
||||
|
||||
function getERC20AssetData(tokenAddress: string): string {
|
||||
@@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
|
||||
}
|
||||
|
||||
describe('queryOrders()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
async function enableFailTriggerAsync(): Promise<void> {
|
||||
await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
|
||||
}
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
describe('getOrderFillableTakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expected = orders.map(getDeterministicOrderInfo);
|
||||
const actual = await testContract.queryOrders(orders).callAsync();
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('returns empty for no orders', async () => {
|
||||
const actual = await testContract.queryOrders([]).callAsync();
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync();
|
||||
expect(actual).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with an empty signature', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrderFillableMakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const expected = orders.map(getDeterministicFillableMakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('returns empty for no orders', async () => {
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts([], []).callAsync();
|
||||
expect(actual).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
|
||||
it('returns zero for an order with an empty signature', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryOrdersAndSampleSells()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
it('returns expected fillable amounts for each order', async () => {
|
||||
const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
||||
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
|
||||
const [orderInfos] = await testContract
|
||||
.queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.callAsync();
|
||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
||||
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('can return quotes for all sources', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
|
||||
const [, quotes] = await testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
sampleAmounts,
|
||||
)
|
||||
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no orders are passed in', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
||||
.queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||
});
|
||||
@@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with an unsupported source', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleSells(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
SELL_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(TAKER_TOKEN),
|
||||
)
|
||||
@@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('queryOrdersAndSampleBuys()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('returns the results of `getOrderInfo()` for each order', async () => {
|
||||
it('returns expected fillable amounts for each order', async () => {
|
||||
const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
|
||||
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
|
||||
const [orderInfos] = await testContract
|
||||
.queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
|
||||
.callAsync();
|
||||
expect(orderInfos).to.deep.eq(expectedOrderInfos);
|
||||
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('can return quotes for all sources', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
|
||||
const [, quotes] = await testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
sampleAmounts,
|
||||
)
|
||||
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no orders are passed in', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
||||
.queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
|
||||
});
|
||||
@@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with an unsupported source', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const sources = [...BUY_SOURCES, 'Kyber'];
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN),
|
||||
ORDERS,
|
||||
SIGNATURES,
|
||||
sources.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with non-ERC20 taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid maker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
makerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('throws with invalid taker asset data', async () => {
|
||||
const tx = testContract
|
||||
.queryOrdersAndSampleBuys(
|
||||
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({
|
||||
ORDERS.map(o => ({
|
||||
...o,
|
||||
takerAssetData: INVALID_ASSET_DATA,
|
||||
})),
|
||||
SIGNATURES,
|
||||
BUY_SOURCES.map(n => allSources[n]),
|
||||
getSampleAmounts(MAKER_TOKEN),
|
||||
)
|
||||
@@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('sampleSells()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
describe('sampleBuys()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromKyberNetwork()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token - token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
||||
@@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
@@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromEth2Dai()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleBuysFromEth2Dai()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleBuysFromEth2Dai()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
|
||||
@@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleSellsFromUniswap()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleSellsFromUniswap()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
@@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
@@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the taker token', async () => {
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sampleBuysFromUniswap()', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
|
||||
blockchainTests.resets('sampleBuysFromUniswap()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
@@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can return many quotes', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
|
||||
@@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
|
||||
@@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN))
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('throws if no exchange exists for the taker token', async () => {
|
||||
it('returns zero if no exchange exists for the maker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const tx = testContract
|
||||
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken))
|
||||
const sampleAmounts = getSampleAmounts(nonExistantToken);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
|
||||
.callAsync();
|
||||
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('returns zero if no exchange exists for the taker token', async () => {
|
||||
const nonExistantToken = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,8 +3,8 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/deployment_constants';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/i_dev_utils';
|
||||
export * from '../test/generated-wrappers/i_erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_network';
|
||||
|
@@ -5,8 +5,8 @@
|
||||
"files": [
|
||||
"generated-artifacts/ERC20BridgeSampler.json",
|
||||
"generated-artifacts/IERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/DeploymentConstants.json",
|
||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/IDevUtils.json",
|
||||
"test/generated-artifacts/IERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberNetwork.json",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -3,6 +3,9 @@ export {
|
||||
DummyMultipleReturnERC20TokenContract,
|
||||
DummyNoReturnERC20TokenContract,
|
||||
WETH9Contract,
|
||||
WETH9Events,
|
||||
WETH9DepositEventArgs,
|
||||
WETH9TransferEventArgs,
|
||||
ZRXTokenContract,
|
||||
DummyERC20TokenTransferEventArgs,
|
||||
ERC20TokenEventArgs,
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "4.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "4.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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": {
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "4.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "4.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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": {
|
||||
|
@@ -103,7 +103,7 @@ export function calculateFillResults(
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
);
|
||||
const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
|
||||
const makerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee);
|
||||
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
|
||||
return {
|
||||
makerAssetFilledAmount,
|
||||
@@ -113,3 +113,30 @@ export function calculateFillResults(
|
||||
protocolFeePaid: safeMul(protocolFeeMultiplier, gasPrice),
|
||||
};
|
||||
}
|
||||
|
||||
export const LibFractions = {
|
||||
add: (n1: BigNumber, d1: BigNumber, n2: BigNumber, d2: BigNumber): [BigNumber, BigNumber] => {
|
||||
if (n1.isZero()) {
|
||||
return [n2, d2];
|
||||
}
|
||||
if (n2.isZero()) {
|
||||
return [n1, d1];
|
||||
}
|
||||
const numerator = safeAdd(safeMul(n1, d2), safeMul(n2, d1));
|
||||
const denominator = safeMul(d1, d2);
|
||||
return [numerator, denominator];
|
||||
},
|
||||
normalize: (
|
||||
numerator: BigNumber,
|
||||
denominator: BigNumber,
|
||||
maxValue: BigNumber = new BigNumber(2).exponentiatedBy(127),
|
||||
): [BigNumber, BigNumber] => {
|
||||
if (numerator.isGreaterThan(maxValue) || denominator.isGreaterThan(maxValue)) {
|
||||
let rescaleBase = numerator.isGreaterThanOrEqualTo(denominator) ? numerator : denominator;
|
||||
rescaleBase = safeDiv(rescaleBase, maxValue);
|
||||
return [safeDiv(numerator, rescaleBase), safeDiv(denominator, rescaleBase)];
|
||||
} else {
|
||||
return [numerator, denominator];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "3.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "3.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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": {
|
||||
|
@@ -107,23 +107,6 @@ describe('Reference functions', () => {
|
||||
).to.throw(expectedError.message);
|
||||
});
|
||||
|
||||
it('reverts if `order.makerAssetAmount` is 0', () => {
|
||||
const order = makeOrder({
|
||||
makerAssetAmount: constants.ZERO_AMOUNT,
|
||||
takerAssetAmount: ONE_ETHER,
|
||||
});
|
||||
const takerAssetFilledAmount = ONE_ETHER;
|
||||
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
|
||||
return expect(() =>
|
||||
LibReferenceFunctions.calculateFillResults(
|
||||
order,
|
||||
takerAssetFilledAmount,
|
||||
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
|
||||
DEFAULT_GAS_PRICE,
|
||||
),
|
||||
).to.throw(expectedError.message);
|
||||
});
|
||||
|
||||
it('reverts if `order.takerAssetAmount` is 0', () => {
|
||||
const order = makeOrder({
|
||||
makerAssetAmount: ONE_ETHER,
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "5.1.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "5.1.0",
|
||||
"changes": [
|
||||
|
@@ -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)
|
||||
|
@@ -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": {
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "2.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "2.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -19,7 +19,7 @@ const coordinatorEndpoint = 'http://localhost:';
|
||||
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
blockchainTests.skip('Coordinator Client', env => {
|
||||
blockchainTests('Coordinator Client', env => {
|
||||
const takerTokenFillAmount = new BigNumber(0);
|
||||
const chainId = 1337;
|
||||
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
|
||||
|
@@ -33,7 +33,7 @@ import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/wrapper_interfaces';
|
||||
import { AssetProxyDispatcher, Authorizable, Ownable } from './framework/utils/wrapper_interfaces';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('Deployment and Configuration End to End Tests', env => {
|
||||
|
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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 });
|
||||
}
|
||||
|
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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>;
|
||||
}
|
||||
|
@@ -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*/
|
||||
|
@@ -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;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
119
contracts/integrations/test/framework/assertions/endEpoch.ts
Normal file
119
contracts/integrations/test/framework/assertions/endEpoch.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { WETH9DepositEventArgs, WETH9Events } from '@0x/contracts-erc20';
|
||||
import {
|
||||
AggregatedStats,
|
||||
StakingEpochEndedEventArgs,
|
||||
StakingEpochFinalizedEventArgs,
|
||||
StakingEvents,
|
||||
} from '@0x/contracts-staking';
|
||||
import { constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
import { SimulationEnvironment } from '../simulation';
|
||||
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
interface EndEpochBeforeInfo {
|
||||
wethReservedForPoolRewards: BigNumber;
|
||||
aggregatedStatsBefore: AggregatedStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionAssertion for `endEpoch` which assumes valid input is provided. It checks
|
||||
* that the staking proxy contract wrapped its ETH balance, aggregated stats were updated, and
|
||||
* EpochFinalized/EpochEnded events were emitted.
|
||||
*/
|
||||
export function validEndEpochAssertion(
|
||||
deployment: DeploymentManager,
|
||||
simulationEnvironment: SimulationEnvironment,
|
||||
): FunctionAssertion<[], EndEpochBeforeInfo, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
const { balanceStore } = simulationEnvironment;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper, 'endEpoch', {
|
||||
before: async () => {
|
||||
await balanceStore.updateEthBalancesAsync();
|
||||
const aggregatedStatsBefore = AggregatedStats.fromArray(
|
||||
await stakingWrapper.aggregatedStatsByEpoch(simulationEnvironment.currentEpoch).callAsync(),
|
||||
);
|
||||
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
|
||||
return { wethReservedForPoolRewards, aggregatedStatsBefore };
|
||||
},
|
||||
after: async (beforeInfo: EndEpochBeforeInfo, result: FunctionResult, _args: [], _txData: Partial<TxData>) => {
|
||||
// Ensure that the tx succeeded.
|
||||
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||
|
||||
const { currentEpoch } = simulationEnvironment;
|
||||
const logs = result.receipt!.logs; // tslint:disable-line
|
||||
|
||||
// Check WETH deposit event
|
||||
const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT;
|
||||
const expectedDepositEvents = previousEthBalance.isGreaterThan(0)
|
||||
? [
|
||||
{
|
||||
_owner: deployment.staking.stakingProxy.address,
|
||||
_value: previousEthBalance,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
verifyEventsFromLogs<WETH9DepositEventArgs>(logs, expectedDepositEvents, WETH9Events.Deposit);
|
||||
|
||||
// Check that the aggregated stats were updated
|
||||
await balanceStore.updateErc20BalancesAsync();
|
||||
const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo;
|
||||
const expectedAggregatedStats = {
|
||||
...aggregatedStatsBefore,
|
||||
rewardsAvailable: _.get(
|
||||
balanceStore.balances,
|
||||
['erc20', stakingWrapper.address, deployment.tokens.weth.address],
|
||||
constants.ZERO_AMOUNT,
|
||||
).minus(wethReservedForPoolRewards),
|
||||
};
|
||||
const aggregatedStatsAfter = AggregatedStats.fromArray(
|
||||
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
|
||||
);
|
||||
expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats);
|
||||
|
||||
// Check that an EpochEnded event was emitted
|
||||
verifyEventsFromLogs<StakingEpochEndedEventArgs>(
|
||||
logs,
|
||||
[
|
||||
{
|
||||
epoch: currentEpoch,
|
||||
numPoolsToFinalize: aggregatedStatsAfter.numPoolsToFinalize,
|
||||
rewardsAvailable: aggregatedStatsAfter.rewardsAvailable,
|
||||
totalFeesCollected: aggregatedStatsAfter.totalFeesCollected,
|
||||
totalWeightedStake: aggregatedStatsAfter.totalWeightedStake,
|
||||
},
|
||||
],
|
||||
StakingEvents.EpochEnded,
|
||||
);
|
||||
|
||||
// If there are no more pools to finalize, an EpochFinalized event should've been emitted
|
||||
const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero()
|
||||
? [
|
||||
{
|
||||
epoch: currentEpoch,
|
||||
rewardsPaid: constants.ZERO_AMOUNT,
|
||||
rewardsRemaining: aggregatedStatsAfter.rewardsAvailable,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
|
||||
logs,
|
||||
expectedEpochFinalizedEvents,
|
||||
StakingEvents.EpochFinalized,
|
||||
);
|
||||
|
||||
// The function returns the remaining number of unfinalized pools for the epoch
|
||||
expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal(
|
||||
aggregatedStatsAfter.numPoolsToFinalize,
|
||||
);
|
||||
|
||||
// Update currentEpoch locally
|
||||
simulationEnvironment.currentEpoch = currentEpoch.plus(1);
|
||||
},
|
||||
});
|
||||
}
|
@@ -1,21 +1,41 @@
|
||||
import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
|
||||
import { 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 */
|
||||
|
217
contracts/integrations/test/framework/assertions/finalizePool.ts
Normal file
217
contracts/integrations/test/framework/assertions/finalizePool.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { WETH9Events, WETH9TransferEventArgs } from '@0x/contracts-erc20';
|
||||
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
|
||||
import {
|
||||
AggregatedStats,
|
||||
constants as stakingConstants,
|
||||
PoolStats,
|
||||
StakingEpochFinalizedEventArgs,
|
||||
StakingEvents,
|
||||
StakingRewardsPaidEventArgs,
|
||||
} from '@0x/contracts-staking';
|
||||
import {
|
||||
assertRoughlyEquals,
|
||||
constants,
|
||||
expect,
|
||||
filterLogsToArguments,
|
||||
toDecimal,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
import { SimulationEnvironment } from '../simulation';
|
||||
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
const PRECISION = 15;
|
||||
const COBB_DOUGLAS_ALPHA = toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator).dividedBy(
|
||||
toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaDenominator),
|
||||
);
|
||||
|
||||
// Reference function for Cobb-Douglas
|
||||
function cobbDouglas(poolStats: PoolStats, aggregatedStats: AggregatedStats): BigNumber {
|
||||
const { feesCollected, weightedStake } = poolStats;
|
||||
const { rewardsAvailable, totalFeesCollected, totalWeightedStake } = aggregatedStats;
|
||||
|
||||
const feeRatio = toDecimal(feesCollected).dividedBy(toDecimal(totalFeesCollected));
|
||||
const stakeRatio = toDecimal(weightedStake).dividedBy(toDecimal(totalWeightedStake));
|
||||
// totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)
|
||||
return new BigNumber(
|
||||
feeRatio
|
||||
.pow(COBB_DOUGLAS_ALPHA)
|
||||
.times(stakeRatio.pow(toDecimal(1).minus(COBB_DOUGLAS_ALPHA)))
|
||||
.times(toDecimal(rewardsAvailable))
|
||||
.toFixed(0, BigNumber.ROUND_FLOOR),
|
||||
);
|
||||
}
|
||||
|
||||
interface FinalizePoolBeforeInfo {
|
||||
poolStats: PoolStats;
|
||||
aggregatedStats: AggregatedStats;
|
||||
poolRewards: BigNumber;
|
||||
cumulativeRewardsLastStored: BigNumber;
|
||||
mostRecentCumulativeRewards: {
|
||||
numerator: BigNumber;
|
||||
denominator: BigNumber;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionAssertion for `finalizePool` which assumes valid input is provided. The `after`
|
||||
* callback below is annotated with the solidity source of `finalizePool`.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
export function validFinalizePoolAssertion(
|
||||
deployment: DeploymentManager,
|
||||
simulationEnvironment: SimulationEnvironment,
|
||||
): FunctionAssertion<[string], FinalizePoolBeforeInfo, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion<[string], FinalizePoolBeforeInfo, void>(stakingWrapper, 'finalizePool', {
|
||||
before: async (args: [string]) => {
|
||||
const [poolId] = args;
|
||||
const { currentEpoch } = simulationEnvironment;
|
||||
const prevEpoch = currentEpoch.minus(1);
|
||||
|
||||
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
|
||||
const aggregatedStats = AggregatedStats.fromArray(
|
||||
await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(),
|
||||
);
|
||||
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||
const [
|
||||
mostRecentCumulativeRewards,
|
||||
cumulativeRewardsLastStored,
|
||||
] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
|
||||
return {
|
||||
poolStats,
|
||||
aggregatedStats,
|
||||
poolRewards,
|
||||
cumulativeRewardsLastStored,
|
||||
mostRecentCumulativeRewards,
|
||||
};
|
||||
},
|
||||
after: async (beforeInfo: FinalizePoolBeforeInfo, result: FunctionResult, args: [string]) => {
|
||||
// Ensure that the tx succeeded.
|
||||
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||
|
||||
const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion
|
||||
const { stakingPools, currentEpoch } = simulationEnvironment;
|
||||
const prevEpoch = currentEpoch.minus(1);
|
||||
const [poolId] = args;
|
||||
const pool = stakingPools[poolId];
|
||||
|
||||
// finalizePool noops if there are no pools to finalize or
|
||||
// the pool did not earn rewards or already finalized (has no fees).
|
||||
if (beforeInfo.aggregatedStats.numPoolsToFinalize.isZero() || beforeInfo.poolStats.feesCollected.isZero()) {
|
||||
expect(logs.length, 'Expect no events to be emitted').to.equal(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// It should have cleared the pool stats for prevEpoch
|
||||
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
|
||||
expect(poolStats).to.deep.equal({
|
||||
feesCollected: constants.ZERO_AMOUNT,
|
||||
weightedStake: constants.ZERO_AMOUNT,
|
||||
membersStake: constants.ZERO_AMOUNT,
|
||||
});
|
||||
|
||||
// uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats);
|
||||
const rewards = BigNumber.min(
|
||||
cobbDouglas(beforeInfo.poolStats, beforeInfo.aggregatedStats),
|
||||
beforeInfo.aggregatedStats.rewardsAvailable.minus(beforeInfo.aggregatedStats.totalRewardsFinalized),
|
||||
);
|
||||
|
||||
// Check that a RewardsPaid event was emitted
|
||||
const events = filterLogsToArguments<StakingRewardsPaidEventArgs>(logs, StakingEvents.RewardsPaid);
|
||||
expect(events.length, 'Number of RewardsPaid events emitted').to.equal(1);
|
||||
const [rewardsPaidEvent] = events;
|
||||
expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.equal(poolId);
|
||||
expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch);
|
||||
|
||||
// Pull the operator and members' reward from the event
|
||||
const { operatorReward, membersReward } = rewardsPaidEvent;
|
||||
const totalReward = operatorReward.plus(membersReward);
|
||||
// Should be approximately equal to the rewards compute using the Cobb-Douglas reference function
|
||||
assertRoughlyEquals(totalReward, rewards, PRECISION);
|
||||
|
||||
// Operator takes their share of the rewards
|
||||
if (beforeInfo.poolStats.membersStake.isZero()) {
|
||||
expect(
|
||||
operatorReward,
|
||||
"operatorReward should equal totalReward if pool's membersStake is 0",
|
||||
).to.bignumber.equal(totalReward);
|
||||
} else {
|
||||
expect(operatorReward).to.bignumber.equal(
|
||||
ReferenceFunctions.getPartialAmountCeil(
|
||||
new BigNumber(pool.operatorShare),
|
||||
new BigNumber(stakingConstants.PPM),
|
||||
totalReward,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Pays the operator in WETH if the operator's reward is non-zero
|
||||
const expectedTransferEvents = operatorReward.isGreaterThan(0)
|
||||
? [
|
||||
{
|
||||
_from: deployment.staking.stakingProxy.address,
|
||||
_to: pool.operator,
|
||||
_value: operatorReward,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
// Check for WETH transfer event emitted when paying out operator's reward.
|
||||
verifyEventsFromLogs<WETH9TransferEventArgs>(logs, expectedTransferEvents, WETH9Events.Transfer);
|
||||
// Check that pool rewards have increased.
|
||||
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||
expect(poolRewards).to.bignumber.equal(beforeInfo.poolRewards.plus(membersReward));
|
||||
// Check that cumulative rewards have increased.
|
||||
const [
|
||||
mostRecentCumulativeRewards,
|
||||
cumulativeRewardsLastStored,
|
||||
] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
|
||||
expect(cumulativeRewardsLastStored).to.bignumber.equal(currentEpoch);
|
||||
let [numerator, denominator] = ReferenceFunctions.LibFractions.add(
|
||||
beforeInfo.mostRecentCumulativeRewards.numerator,
|
||||
beforeInfo.mostRecentCumulativeRewards.denominator,
|
||||
membersReward,
|
||||
beforeInfo.poolStats.membersStake,
|
||||
);
|
||||
[numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator);
|
||||
expect(mostRecentCumulativeRewards).to.deep.equal({ numerator, denominator });
|
||||
|
||||
// Check that aggregated stats have been updated
|
||||
const aggregatedStats = AggregatedStats.fromArray(
|
||||
await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(),
|
||||
);
|
||||
expect(aggregatedStats).to.deep.equal({
|
||||
...beforeInfo.aggregatedStats,
|
||||
totalRewardsFinalized: beforeInfo.aggregatedStats.totalRewardsFinalized.plus(totalReward),
|
||||
numPoolsToFinalize: beforeInfo.aggregatedStats.numPoolsToFinalize.minus(1),
|
||||
});
|
||||
|
||||
// If there are no more unfinalized pools remaining, the epoch is finalized.
|
||||
const expectedEpochFinalizedEvents = aggregatedStats.numPoolsToFinalize.isZero()
|
||||
? [
|
||||
{
|
||||
epoch: prevEpoch,
|
||||
rewardsPaid: aggregatedStats.totalRewardsFinalized,
|
||||
rewardsRemaining: aggregatedStats.rewardsAvailable.minus(
|
||||
aggregatedStats.totalRewardsFinalized,
|
||||
),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
|
||||
logs,
|
||||
expectedEpochFinalizedEvents,
|
||||
StakingEvents.EpochFinalized,
|
||||
);
|
||||
|
||||
// Update local state
|
||||
pool.lastFinalized = prevEpoch;
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -1,7 +1,9 @@
|
||||
import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||
import { BaseContract, ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * 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
|
||||
|
@@ -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}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -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],
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -0,0 +1,76 @@
|
||||
import { WETH9Events, WETH9TransferEventArgs } from '@0x/contracts-erc20';
|
||||
import { loadCurrentBalance, StoredBalance } from '@0x/contracts-staking';
|
||||
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
import { SimulationEnvironment } from '../simulation';
|
||||
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
interface WithdrawDelegatorRewardsBeforeInfo {
|
||||
delegatorStake: StoredBalance;
|
||||
poolRewards: BigNumber;
|
||||
wethReservedForPoolRewards: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionAssertion for `withdrawDelegatorRewards` which assumes valid input is provided.
|
||||
* It checks that the delegator's stake gets synced and pool rewards are updated to reflect the
|
||||
* amount withdrawn.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
export function validWithdrawDelegatorRewardsAssertion(
|
||||
deployment: DeploymentManager,
|
||||
simulationEnvironment: SimulationEnvironment,
|
||||
): FunctionAssertion<[string], WithdrawDelegatorRewardsBeforeInfo, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper, 'withdrawDelegatorRewards', {
|
||||
before: async (args: [string], txData: Partial<TxData>) => {
|
||||
const [poolId] = args;
|
||||
|
||||
const delegatorStake = await stakingWrapper
|
||||
.getStakeDelegatedToPoolByOwner(txData.from as string, poolId)
|
||||
.callAsync();
|
||||
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
|
||||
return { delegatorStake, poolRewards, wethReservedForPoolRewards };
|
||||
},
|
||||
after: async (
|
||||
beforeInfo: WithdrawDelegatorRewardsBeforeInfo,
|
||||
result: FunctionResult,
|
||||
args: [string],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
// Ensure that the tx succeeded.
|
||||
expect(result.success, `Error: ${result.data}`).to.be.true();
|
||||
|
||||
const [poolId] = args;
|
||||
const { currentEpoch } = simulationEnvironment;
|
||||
|
||||
// Check that delegator stake has been synced
|
||||
const expectedDelegatorStake = loadCurrentBalance(beforeInfo.delegatorStake, currentEpoch);
|
||||
const delegatorStake = await stakingWrapper
|
||||
.getStakeDelegatedToPoolByOwner(txData.from as string, poolId)
|
||||
.callAsync();
|
||||
expect(delegatorStake).to.deep.equal(expectedDelegatorStake);
|
||||
|
||||
// Check that pool rewards have been updated to reflect the amount withdrawn.
|
||||
const transferEvents = filterLogsToArguments<WETH9TransferEventArgs>(
|
||||
result.receipt!.logs, // tslint:disable-line:no-non-null-assertion
|
||||
WETH9Events.Transfer,
|
||||
);
|
||||
const expectedPoolRewards =
|
||||
transferEvents.length > 0
|
||||
? beforeInfo.poolRewards.minus(transferEvents[0]._value)
|
||||
: beforeInfo.poolRewards;
|
||||
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
|
||||
expect(poolRewards).to.bignumber.equal(expectedPoolRewards);
|
||||
|
||||
// TODO: Check CR
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -1,5 +1,5 @@
|
||||
import { BaseContract } from '@0x/base-contract';
|
||||
import { 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.
|
||||
|
@@ -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 });
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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], {});
|
||||
|
||||
|
55
contracts/integrations/test/framework/utils/logger.ts
Normal file
55
contracts/integrations/test/framework/utils/logger.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { Pseudorandom } from '../utils/pseudorandom';
|
||||
|
||||
// tslint:disable:no-console
|
||||
|
||||
class Logger {
|
||||
private _step = 0;
|
||||
|
||||
constructor() {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'info',
|
||||
time: new Date(),
|
||||
msg: `Pseudorandom seed: ${Pseudorandom.seed}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs the name of the function executed, the arguments and transaction data it was
|
||||
* called with, and the current step of the simulation.
|
||||
*/
|
||||
public logFunctionAssertion(functionName: string, functionArgs: any[], txData: Partial<TxData>): void {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'info',
|
||||
time: new Date(),
|
||||
msg: `Function called: ${functionName}(${functionArgs
|
||||
.map(arg => JSON.stringify(arg).replace(/"/g, "'"))
|
||||
.join(', ')})`,
|
||||
step: ++this._step,
|
||||
txData,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs information about a assertion failure. Dumps the error thrown and arbitrary data from
|
||||
* the calling context.
|
||||
*/
|
||||
public logFailure(error: Error, data: string): void {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'error',
|
||||
time: new Date(),
|
||||
step: this._step,
|
||||
error,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
54
contracts/integrations/test/framework/utils/pseudorandom.ts
Normal file
54
contracts/integrations/test/framework/utils/pseudorandom.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Numberish } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as seedrandom from 'seedrandom';
|
||||
|
||||
class PRNGWrapper {
|
||||
public readonly seed = process.env.SEED || Math.random().toString();
|
||||
private readonly _rng = seedrandom(this.seed);
|
||||
|
||||
/*
|
||||
* Pseudorandom version of _.sample. Picks an element of the given array with uniform probability.
|
||||
* Return undefined if the array is empty.
|
||||
*/
|
||||
public sample<T>(arr: T[]): T | undefined {
|
||||
if (arr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const index = Math.abs(this._rng.int32()) % arr.length;
|
||||
return arr[index];
|
||||
}
|
||||
|
||||
/*
|
||||
* Pseudorandom version of _.sampleSize. Returns an array of `n` samples from the given array
|
||||
* (with replacement), chosen with uniform probability. Return undefined if the array is empty.
|
||||
*/
|
||||
public sampleSize<T>(arr: T[], n: number): T[] | undefined {
|
||||
if (arr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const samples = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
samples.push(this.sample(arr) as T);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
// tslint:disable:unified-signatures
|
||||
/*
|
||||
* Pseudorandom version of getRandomPortion/getRandomInteger. If two arguments are provided,
|
||||
* treats those arguments as the min and max (inclusive) of the desired range. If only one
|
||||
* argument is provided, picks an integer between 0 and the argument.
|
||||
*/
|
||||
public integer(max: Numberish): BigNumber;
|
||||
public integer(min: Numberish, max: Numberish): BigNumber;
|
||||
public integer(a: Numberish, b?: Numberish): BigNumber {
|
||||
if (b === undefined) {
|
||||
return new BigNumber(this._rng()).times(a).integerValue(BigNumber.ROUND_HALF_UP);
|
||||
} else {
|
||||
const range = new BigNumber(b).minus(a);
|
||||
return this.integer(range).plus(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const Pseudorandom = new PRNGWrapper();
|
@@ -1,34 +1,36 @@
|
||||
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||
import * 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();
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
120
contracts/integrations/test/fuzz_tests/staking_rewards_test.ts
Normal file
120
contracts/integrations/test/fuzz_tests/staking_rewards_test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||
|
||||
import { Actor } from '../framework/actors/base';
|
||||
import {
|
||||
MakerTaker,
|
||||
OperatorStakerMaker,
|
||||
StakerKeeper,
|
||||
StakerMaker,
|
||||
StakerOperator,
|
||||
} from '../framework/actors/hybrids';
|
||||
import { Keeper } from '../framework/actors/keeper';
|
||||
import { Maker } from '../framework/actors/maker';
|
||||
import { PoolOperator } from '../framework/actors/pool_operator';
|
||||
import { Staker } from '../framework/actors/staker';
|
||||
import { Taker } from '../framework/actors/taker';
|
||||
import { filterActorsByRole } from '../framework/actors/utils';
|
||||
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||
|
||||
import { PoolMembershipSimulation } from './pool_membership_test';
|
||||
import { StakeManagementSimulation } from './stake_management_test';
|
||||
|
||||
export class StakingRewardsSimulation extends Simulation {
|
||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||
const { actors } = this.environment;
|
||||
const stakers = filterActorsByRole(actors, Staker);
|
||||
const keepers = filterActorsByRole(actors, Keeper);
|
||||
|
||||
const poolMembership = new PoolMembershipSimulation(this.environment);
|
||||
const stakeManagement = new StakeManagementSimulation(this.environment);
|
||||
|
||||
const actions = [
|
||||
...stakers.map(staker => staker.simulationActions.validWithdrawDelegatorRewards),
|
||||
...keepers.map(keeper => keeper.simulationActions.validFinalizePool),
|
||||
...keepers.map(keeper => keeper.simulationActions.validEndEpoch),
|
||||
poolMembership.generator,
|
||||
stakeManagement.generator,
|
||||
];
|
||||
while (true) {
|
||||
const action = Pseudorandom.sample(actions);
|
||||
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockchainTests('Staking rewards fuzz test', env => {
|
||||
before(function(): void {
|
||||
if (process.env.FUZZ_TEST !== 'staking_rewards') {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
it('fuzz', async () => {
|
||||
// Deploy contracts
|
||||
const deployment = await DeploymentManager.deployAsync(env, {
|
||||
numErc20TokensToDeploy: 4,
|
||||
numErc721TokensToDeploy: 0,
|
||||
numErc1155TokensToDeploy: 0,
|
||||
});
|
||||
const [ERC20TokenA, ERC20TokenB, ERC20TokenC, ERC20TokenD] = deployment.tokens.erc20;
|
||||
|
||||
// Set up balance store
|
||||
const balanceStore = new BlockchainBalanceStore(
|
||||
{
|
||||
StakingProxy: deployment.staking.stakingProxy.address,
|
||||
ZRXVault: deployment.staking.zrxVault.address,
|
||||
},
|
||||
{
|
||||
erc20: {
|
||||
ZRX: deployment.tokens.zrx,
|
||||
WETH: deployment.tokens.weth,
|
||||
ERC20TokenA,
|
||||
ERC20TokenB,
|
||||
ERC20TokenC,
|
||||
ERC20TokenD,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Spin up actors
|
||||
const actors = [
|
||||
new Maker({ deployment, name: 'Maker 1' }),
|
||||
new Maker({ deployment, name: 'Maker 2' }),
|
||||
new Taker({ deployment, name: 'Taker 1' }),
|
||||
new Taker({ deployment, name: 'Taker 2' }),
|
||||
new MakerTaker({ deployment, name: 'Maker/Taker' }),
|
||||
new Staker({ deployment, name: 'Staker 1' }),
|
||||
new Staker({ deployment, name: 'Staker 2' }),
|
||||
new Keeper({ deployment, name: 'Keeper' }),
|
||||
new StakerKeeper({ deployment, name: 'Staker/Keeper' }),
|
||||
new StakerMaker({ deployment, name: 'Staker/Maker' }),
|
||||
new PoolOperator({ deployment, name: 'Pool Operator' }),
|
||||
new StakerOperator({ deployment, name: 'Staker/Operator' }),
|
||||
new OperatorStakerMaker({ deployment, name: 'Operator/Staker/Maker' }),
|
||||
];
|
||||
|
||||
// Set up simulation environment
|
||||
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore, actors);
|
||||
|
||||
// Takers need to set a WETH allowance for the staking proxy in case they pay the protocol fee in WETH
|
||||
const takers = filterActorsByRole(actors, Taker);
|
||||
for (const taker of takers) {
|
||||
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
|
||||
}
|
||||
// Stakers need to set a ZRX allowance to deposit their ZRX into the zrxVault
|
||||
const stakers = filterActorsByRole(actors, Staker);
|
||||
for (const staker of stakers) {
|
||||
await staker.configureERC20TokenAsync(deployment.tokens.zrx);
|
||||
}
|
||||
const simulation = new StakingRewardsSimulation(simulationEnvironment);
|
||||
return simulation.fuzzAsync();
|
||||
});
|
||||
});
|
6
contracts/integrations/test/fuzz_tests/tslint.json
Normal file
6
contracts/integrations/test/fuzz_tests/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"],
|
||||
"rules": {
|
||||
"no-invalid-this": false
|
||||
}
|
||||
}
|
200
contracts/integrations/test/mainnet_configs_test.ts
Normal file
200
contracts/integrations/test/mainnet_configs_test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { ERC20ProxyContract, MultiAssetProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingProxyContract, ZrxVaultContract } from '@0x/contracts-staking';
|
||||
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { contractAddresses, contractWrappers } from './mainnet_fork_utils';
|
||||
|
||||
blockchainTests.resets.fork('Mainnet configs tests', env => {
|
||||
describe('Exchange', () => {
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await contractWrappers.exchange.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('ERC20Proxy should be registered', async () => {
|
||||
const erc20Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||
});
|
||||
it('ERC721Proxy should be registered', async () => {
|
||||
const erc721Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC721).callAsync();
|
||||
expect(erc721Proxy).to.eq(contractAddresses.erc721Proxy);
|
||||
});
|
||||
it('ERC1155Proxy should be registered', async () => {
|
||||
const erc1155Proxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.ERC1155).callAsync();
|
||||
expect(erc1155Proxy).to.eq(contractAddresses.erc1155Proxy);
|
||||
});
|
||||
it('ERC20BridgeProxy should be registered', async () => {
|
||||
const erc20BridgeProxy = await contractWrappers.exchange
|
||||
.getAssetProxy(AssetProxyId.ERC20Bridge)
|
||||
.callAsync();
|
||||
expect(erc20BridgeProxy).to.eq(contractAddresses.erc20BridgeProxy);
|
||||
});
|
||||
it('MultiAssetProxy should be registered', async () => {
|
||||
const multiAssetProxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.MultiAsset).callAsync();
|
||||
expect(multiAssetProxy).to.eq(contractAddresses.multiAssetProxy);
|
||||
});
|
||||
it('StaticCallProxy should be registered', async () => {
|
||||
const staticCallProxy = await contractWrappers.exchange.getAssetProxy(AssetProxyId.StaticCall).callAsync();
|
||||
expect(staticCallProxy).to.eq(contractAddresses.staticCallProxy);
|
||||
});
|
||||
it('StakingProxy should be attached', async () => {
|
||||
const stakingProxy = await contractWrappers.exchange.protocolFeeCollector().callAsync();
|
||||
expect(stakingProxy).to.eq(contractAddresses.stakingProxy);
|
||||
});
|
||||
it('should have the correct protocol fee multiplier', async () => {
|
||||
const protocolFeeMultiplier = await contractWrappers.exchange.protocolFeeMultiplier().callAsync();
|
||||
expect(protocolFeeMultiplier).to.bignumber.eq(150000);
|
||||
});
|
||||
});
|
||||
describe('ERC20Proxy', () => {
|
||||
const erc20Proxy = new ERC20ProxyContract(contractAddresses.erc20Proxy, env.provider);
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await erc20Proxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await erc20Proxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(4);
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||
expect(authorizedAddresses.includes(contractAddresses.zrxVault), 'ZrxVault');
|
||||
});
|
||||
});
|
||||
describe('ERC721Proxy', () => {
|
||||
const erc721Proxy = new ERC20ProxyContract(contractAddresses.erc721Proxy, env.provider);
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await erc721Proxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await erc721Proxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(3);
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||
});
|
||||
});
|
||||
describe('ERC1155Proxy', () => {
|
||||
const erc1155Proxy = new ERC20ProxyContract(contractAddresses.erc1155Proxy, env.provider);
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await erc1155Proxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await erc1155Proxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(3);
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||
});
|
||||
});
|
||||
describe('ERC20BridgeProxy', () => {
|
||||
const erc20BridgeProxy = new ERC20ProxyContract(contractAddresses.erc20BridgeProxy, env.provider);
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await erc20BridgeProxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await erc20BridgeProxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(2);
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||
expect(authorizedAddresses.includes(contractAddresses.multiAssetProxy), 'MultiAssetProxy');
|
||||
});
|
||||
});
|
||||
describe('MultiAssetProxy', () => {
|
||||
const multiAssetProxy = new MultiAssetProxyContract(contractAddresses.multiAssetProxy, env.provider);
|
||||
it('should be owned by the ZeroExGovernor ', async () => {
|
||||
const owner = await multiAssetProxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await multiAssetProxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(2);
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchangeV2), 'ExchangeV2');
|
||||
expect(authorizedAddresses.includes(contractAddresses.exchange), 'Exchange');
|
||||
});
|
||||
it('ERC20Proxy should be registered', async () => {
|
||||
const erc20Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||
});
|
||||
it('ERC721Proxy should be registered', async () => {
|
||||
const erc721Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC721).callAsync();
|
||||
expect(erc721Proxy).to.eq(contractAddresses.erc721Proxy);
|
||||
});
|
||||
it('ERC1155Proxy should be registered', async () => {
|
||||
const erc1155Proxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC1155).callAsync();
|
||||
expect(erc1155Proxy).to.eq(contractAddresses.erc1155Proxy);
|
||||
});
|
||||
it('ERC20BridgeProxy should be registered', async () => {
|
||||
const erc20BridgeProxy = await multiAssetProxy.getAssetProxy(AssetProxyId.ERC20Bridge).callAsync();
|
||||
expect(erc20BridgeProxy).to.eq(contractAddresses.erc20BridgeProxy);
|
||||
});
|
||||
it('StaticCallProxy should be registered', async () => {
|
||||
const staticCallProxy = await multiAssetProxy.getAssetProxy(AssetProxyId.StaticCall).callAsync();
|
||||
expect(staticCallProxy).to.eq(contractAddresses.staticCallProxy);
|
||||
});
|
||||
});
|
||||
describe('StakingProxy', () => {
|
||||
const stakingProxy = new StakingProxyContract(contractAddresses.stakingProxy, env.provider);
|
||||
it('should be owned by ZeroExGovernor', async () => {
|
||||
const owner = await stakingProxy.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('Staking contract should be attached', async () => {
|
||||
const staking = await stakingProxy.stakingContract().callAsync();
|
||||
expect(staking).to.eq(contractAddresses.staking);
|
||||
});
|
||||
it('Exchange should be registered', async () => {
|
||||
const isRegistered = await contractWrappers.staking.validExchanges(contractAddresses.exchange).callAsync();
|
||||
expect(isRegistered).to.be.true();
|
||||
});
|
||||
it('ZrxVault should be set', async () => {
|
||||
const zrxVault = await contractWrappers.staking.getZrxVault().callAsync();
|
||||
expect(zrxVault).to.eq(contractAddresses.zrxVault);
|
||||
});
|
||||
it('WETH should be set', async () => {
|
||||
const weth = await contractWrappers.staking.getWethContract().callAsync();
|
||||
expect(weth).to.eq(contractAddresses.etherToken);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await stakingProxy.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(1);
|
||||
expect(authorizedAddresses.includes(contractAddresses.zeroExGovernor), 'ZeroExGovernor');
|
||||
});
|
||||
it('should have the correct params set', async () => {
|
||||
const params = await contractWrappers.staking.getParams().callAsync();
|
||||
const epochDurationInSeconds = 10 * 24 * 60 * 60;
|
||||
const rewardDelegatedStakeWeight = 10 ** 6 * 0.9;
|
||||
const mimimumPoolStake = new BigNumber(10).pow(18).times(100);
|
||||
const cobbDouglasAlphaNumerator = 2;
|
||||
const cobbDouglasAlphaDenominator = 3;
|
||||
expect(params[0]).to.bignumber.eq(epochDurationInSeconds, 'epochDurationInSeconds');
|
||||
expect(params[1]).to.bignumber.eq(rewardDelegatedStakeWeight, 'rewardDelegatedStakeWeight');
|
||||
expect(params[2]).to.bignumber.eq(mimimumPoolStake, 'mimimumPoolStake');
|
||||
expect(params[3]).to.bignumber.eq(cobbDouglasAlphaNumerator, 'cobbDouglasAlphaNumerator');
|
||||
expect(params[4]).to.bignumber.eq(cobbDouglasAlphaDenominator, 'cobbDouglasAlphaDenominator');
|
||||
});
|
||||
});
|
||||
describe('ZrxVault', () => {
|
||||
const zrxVault = new ZrxVaultContract(contractAddresses.zrxVault, env.provider);
|
||||
it('should be owned by ZeroExGovernor', async () => {
|
||||
const owner = await zrxVault.owner().callAsync();
|
||||
expect(owner).to.eq(contractAddresses.zeroExGovernor);
|
||||
});
|
||||
it('should have the correct authorized addresses', async () => {
|
||||
const authorizedAddresses = await zrxVault.getAuthorizedAddresses().callAsync();
|
||||
expect(authorizedAddresses.length).to.eq(1);
|
||||
expect(authorizedAddresses.includes(contractAddresses.zeroExGovernor), 'ZeroExGovernor');
|
||||
});
|
||||
it('ERC20Proxy should be set', async () => {
|
||||
const erc20Proxy = await zrxVault.zrxAssetProxy().callAsync();
|
||||
expect(erc20Proxy).to.eq(contractAddresses.erc20Proxy);
|
||||
});
|
||||
it('StakingProxy should be set', async () => {
|
||||
const stakingProxy = await zrxVault.stakingProxyAddress().callAsync();
|
||||
expect(stakingProxy).to.eq(contractAddresses.stakingProxy);
|
||||
});
|
||||
});
|
||||
});
|
9
contracts/integrations/test/mainnet_fork_utils.ts
Normal file
9
contracts/integrations/test/mainnet_fork_utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||
import { provider } from '@0x/contracts-test-utils';
|
||||
|
||||
const chainId = 1;
|
||||
const contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
||||
const contractWrappers = new ContractWrappers(provider, { chainId, contractAddresses });
|
||||
|
||||
export { contractAddresses, contractWrappers };
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1576540892,
|
||||
"version": "4.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1575931811,
|
||||
"version": "4.0.1",
|
||||
|
@@ -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
|
||||
|
@@ -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": {
|
||||
|
@@ -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
Reference in New Issue
Block a user