Add aggregator mainnet tests (#2407)
* `@0x/contracts-erc20-bridge-sampler`: Add gas limits to external quote calls. `@0x/contract-addresses`: Point `erc20BridgeSampler` to new version. * `@0x/contracts-utils`: Add kovan addresses to `DeploymentConstants`. `@0x/contract-addresses`: Add kovan `ERC20BridgeSampler` address. * `@0x/contracts-erc20-bridge-sampler`: Fix changelog. * `@0x/asset-swapper`: Ignore zero sample results from the sampler contract. `@0x/asset-swapper`: Allow skipping Uniswap when dealing with low precision amounts with `minUniswapDecimals` option. `@0x/asset-swapper`: Increase default `runLimit` from `1024` to `4096`. `@0x/asset-swapper`: Increase default `numSamples` from `8` to `10` `@0x/asset-swapper`: Fix ordering of optimized orders. `@0x/asset-swapper`: Fix best and worst quotes being reversed sometimes. `@0x/asset-swapper`: Fix rounding of quoted asset amounts. * `@0x/asset-swapper`: Change default `minUniswapDecimals` option from 8 to 7. * `@0x/asset-swapper`: Revert uniswap decimals fix. * `@0x/contracts-test-utils`: Add `blockchainTests.live()` for live network tests. `@0x/contracts-test-utils`: Add modifiers to `blockchainTests.fork()`. `@0x/contracts-integrations`: Add aggregator mainnet tests. * `@0x/contracts-integrations`: Fix `fork/resets` modifier ordering on dydx tests. `@0x/contracts-integrations`: Move and tweak aggregation tests. * `@0x/contracts-integrations`: Handle non-responsive third-party SRA ordebooks with a little more grace. * `@0x/contracts-integrations`: Fix linter error. * `@0x/contracts-test-utils`: Consolidate fork provider logic into `mocha_blockchain.ts`. * `@0x/contracts-integrations`: Run prettier on aggregation fill tests. * `@0x/dev-utils`: Add `locked` to `Web3Config`. * `@0x/contracts-integrations`: Update mainnet fork tests. `@0x/contracts-test-utils`: Fix forked tests being skipped. `@0x/contracts-erc20-bridge-sampler`: Regenerate artifacts. * `@0x/contracts-test-utils`: Remove unecessary `locked` option when creating forked ganache provider. * Fix redundant zero check * Set fee amount in fillable amounts test Co-authored-by: Jacob Evans <dekz@dekz.net>
This commit is contained in:
parent
0571a96cea
commit
ff2cc8c887
@ -5,6 +5,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Integration tests for DydxBridge with (i) Exchange v3 and (ii) Mainnet dYdX SoloMargin contract.",
|
"note": "Integration tests for DydxBridge with (i) Exchange v3 and (ii) Mainnet dYdX SoloMargin contract.",
|
||||||
"pr": 2401
|
"pr": 2401
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add aggregator mainnet tests.",
|
||||||
|
"pr": 2407
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2019 ZeroEx Intl.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity ^0.5.9;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||||
|
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||||
|
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||||
|
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev A forwarder contract for filling 0x asset-swapper aggregated orders.
|
||||||
|
/// The forwarder is necessary to purchase taker assets and set up
|
||||||
|
/// approvals in one transaction. Only call the functions on this contract
|
||||||
|
/// in an `eth_call` context or you will lose money!
|
||||||
|
contract TestMainnetAggregatorFills is
|
||||||
|
DeploymentConstants
|
||||||
|
{
|
||||||
|
using LibSafeMath for uint256;
|
||||||
|
|
||||||
|
address constant internal EXCHANGE_ADDRESS = 0x61935CbDd02287B511119DDb11Aeb42F1593b7Ef;
|
||||||
|
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
|
||||||
|
|
||||||
|
struct SimulatedMarketFillResults {
|
||||||
|
uint256 makerAssetBalanceBefore;
|
||||||
|
uint256 takerAssetBalanceBefore;
|
||||||
|
uint256 makerAssetBalanceAfter;
|
||||||
|
uint256 takerAssetBalanceAfter;
|
||||||
|
LibFillResults.FillResults fillResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// solhint-disable-next-line no-empty-blocks
|
||||||
|
function() external payable {}
|
||||||
|
|
||||||
|
/// @dev Buy taker assets with ETH from `takerOrders` and then perform a
|
||||||
|
/// market buy on `makerOrders`.
|
||||||
|
function marketBuy(
|
||||||
|
address makerTokenAddress,
|
||||||
|
address takerTokenAddress,
|
||||||
|
LibOrder.Order[] memory makerOrders,
|
||||||
|
LibOrder.Order[] memory takerOrders,
|
||||||
|
bytes[] memory makerOrderSignatures,
|
||||||
|
bytes[] memory takerOrderSignatures,
|
||||||
|
uint256 makerAssetBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (SimulatedMarketFillResults memory results)
|
||||||
|
{
|
||||||
|
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||||
|
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
results.fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||||
|
.marketBuyOrdersNoThrow
|
||||||
|
.value(address(this).balance)(
|
||||||
|
makerOrders,
|
||||||
|
makerAssetBuyAmount,
|
||||||
|
makerOrderSignatures
|
||||||
|
);
|
||||||
|
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Buy taker assets with ETH from `takerOrders` and then perform a
|
||||||
|
/// market sell on `makerOrders`.
|
||||||
|
function marketSell(
|
||||||
|
address makerTokenAddress,
|
||||||
|
address takerTokenAddress,
|
||||||
|
LibOrder.Order[] memory makerOrders,
|
||||||
|
LibOrder.Order[] memory takerOrders,
|
||||||
|
bytes[] memory makerOrderSignatures,
|
||||||
|
bytes[] memory takerOrderSignatures,
|
||||||
|
uint256 takerAssetSellAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (SimulatedMarketFillResults memory results)
|
||||||
|
{
|
||||||
|
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||||
|
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
results.fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||||
|
.marketSellOrdersNoThrow
|
||||||
|
.value(address(this).balance)(
|
||||||
|
makerOrders,
|
||||||
|
takerAssetSellAmount,
|
||||||
|
makerOrderSignatures
|
||||||
|
);
|
||||||
|
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Like `marketSell`, but calls `fillOrder()` individually to detect
|
||||||
|
/// errors.
|
||||||
|
function fillOrders(
|
||||||
|
address makerTokenAddress,
|
||||||
|
address takerTokenAddress,
|
||||||
|
LibOrder.Order[] memory makerOrders,
|
||||||
|
LibOrder.Order[] memory takerOrders,
|
||||||
|
bytes[] memory makerOrderSignatures,
|
||||||
|
bytes[] memory takerOrderSignatures,
|
||||||
|
uint256 takerAssetSellAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (SimulatedMarketFillResults memory results)
|
||||||
|
{
|
||||||
|
_prepareFunds(takerTokenAddress, makerOrders, takerOrders, takerOrderSignatures);
|
||||||
|
results.makerAssetBalanceBefore = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceBefore = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
for (uint256 i = 0; i < makerOrders.length; i++) {
|
||||||
|
if (takerAssetSellAmount == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LibFillResults.FillResults memory fillResults = IExchange(EXCHANGE_ADDRESS)
|
||||||
|
.fillOrder
|
||||||
|
.value(address(this).balance)(
|
||||||
|
makerOrders[i],
|
||||||
|
takerAssetSellAmount,
|
||||||
|
makerOrderSignatures[i]
|
||||||
|
);
|
||||||
|
results.fillResults = LibFillResults.addFillResults(results.fillResults, fillResults);
|
||||||
|
takerAssetSellAmount = takerAssetSellAmount.safeSub(fillResults.takerAssetFilledAmount);
|
||||||
|
}
|
||||||
|
results.makerAssetBalanceAfter = IERC20Token(makerTokenAddress).balanceOf(address(this));
|
||||||
|
results.takerAssetBalanceAfter = IERC20Token(takerTokenAddress).balanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _approveAssetProxy(address tokenAddress) private {
|
||||||
|
address assetProxyAddress = IExchange(EXCHANGE_ADDRESS).getAssetProxy(ERC20_PROXY_ID);
|
||||||
|
LibERC20Token.approve(tokenAddress, assetProxyAddress, uint256(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Buys as much of `takerOrders` as possible with the ETH transferred
|
||||||
|
/// to this contract, leaving enough ETH behind for protocol fees.
|
||||||
|
function _prepareFunds(
|
||||||
|
address takerTokenAddress,
|
||||||
|
LibOrder.Order[] memory makerOrders,
|
||||||
|
LibOrder.Order[] memory takerOrders,
|
||||||
|
bytes[] memory takerOrderSignatures
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
_approveAssetProxy(_getWethAddress());
|
||||||
|
uint256 protocolFee = IExchange(EXCHANGE_ADDRESS).protocolFeeMultiplier() * tx.gasprice;
|
||||||
|
uint256 maxProtocolFees = protocolFee * (takerOrders.length + makerOrders.length);
|
||||||
|
uint256 ethSellAmount = msg.value.safeSub(maxProtocolFees);
|
||||||
|
IEtherToken(_getWethAddress()).deposit.value(ethSellAmount)();
|
||||||
|
if (takerTokenAddress != _getWethAddress()) {
|
||||||
|
IExchange(EXCHANGE_ADDRESS)
|
||||||
|
.marketSellOrdersNoThrow
|
||||||
|
.value(maxProtocolFees)(
|
||||||
|
takerOrders,
|
||||||
|
ethSellAmount,
|
||||||
|
takerOrderSignatures
|
||||||
|
);
|
||||||
|
_approveAssetProxy(takerTokenAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "TestFramework",
|
"publicInterfaceContracts": "TestFramework",
|
||||||
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -89,6 +89,7 @@
|
|||||||
"typescript": "3.0.1"
|
"typescript": "3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@0x/asset-swapper": "^3.0.1",
|
||||||
"@0x/base-contract": "^6.0.2",
|
"@0x/base-contract": "^6.0.2",
|
||||||
"@0x/contracts-asset-proxy": "^3.0.2",
|
"@0x/contracts-asset-proxy": "^3.0.2",
|
||||||
"@0x/contracts-erc1155": "^2.0.2",
|
"@0x/contracts-erc1155": "^2.0.2",
|
||||||
|
239
contracts/integrations/test/aggregation/fill_test.ts
Normal file
239
contracts/integrations/test/aggregation/fill_test.ts
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import { MarketBuySwapQuote, MarketSellSwapQuote, Orderbook, SwapQuoter } from '@0x/asset-swapper';
|
||||||
|
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
|
||||||
|
import { assetDataUtils } from '@0x/order-utils';
|
||||||
|
import { FillResults, SignedOrder } from '@0x/types';
|
||||||
|
import { BigNumber, logUtils } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { TestMainnetAggregatorFillsContract } from '../wrappers';
|
||||||
|
|
||||||
|
import { tokens } from './tokens';
|
||||||
|
|
||||||
|
blockchainTests.live('Aggregator Mainnet Tests', env => {
|
||||||
|
// Mainnet address of the `TestMainnetAggregatorFills` contract.
|
||||||
|
const TEST_CONTRACT_ADDRESS = '0x37Ca306F42748b7fe105F89FCBb2CD03D27c8146';
|
||||||
|
const TAKER_ADDRESS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; // Vitalik
|
||||||
|
const ORDERBOOK_POLLING_MS = 1000;
|
||||||
|
const GAS_PRICE = new BigNumber(1);
|
||||||
|
const TAKER_ASSET_ETH_VALUE = 500e18;
|
||||||
|
const MIN_BALANCE = 500.1e18;
|
||||||
|
const SYMBOLS = ['ETH', 'DAI', 'USDC', 'FOAM'];
|
||||||
|
const TEST_PAIRS = _.flatten(SYMBOLS.map(m => SYMBOLS.filter(t => t !== m).map(t => [m, t])));
|
||||||
|
const FILL_VALUES = [1, 10, 1e2, 1e3, 1e4, 2.5e4, 5e4];
|
||||||
|
|
||||||
|
let testContract: TestMainnetAggregatorFillsContract;
|
||||||
|
let swapQuoter: SwapQuoter;
|
||||||
|
let takerEthBalance: BigNumber;
|
||||||
|
const orderbooks: { [name: string]: Orderbook } = {};
|
||||||
|
|
||||||
|
async function getTakerOrdersAsync(takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||||
|
if (takerAssetSymbol === 'ETH') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getOrdersAsync(takerAssetSymbol, 'ETH');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches ETH -> taker asset orders for the forwarder contract.
|
||||||
|
async function getOrdersAsync(makerAssetSymbol: string, takerAssetSymbol: string): Promise<SignedOrder[]> {
|
||||||
|
const takerTokenAddress = tokens[takerAssetSymbol].address;
|
||||||
|
const makerTokenAddress = tokens[makerAssetSymbol].address;
|
||||||
|
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||||
|
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||||
|
const orders = _.flatten(
|
||||||
|
await Promise.all(
|
||||||
|
Object.keys(orderbooks).map(async name =>
|
||||||
|
getOrdersFromOrderBookAsync(name, makerAssetData, takerAssetData),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const uniqueOrders: SignedOrder[] = [];
|
||||||
|
for (const order of orders) {
|
||||||
|
if (!order.makerFee.eq(0) || !order.takerFee.eq(0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (uniqueOrders.findIndex(o => isSameOrder(order, o)) === -1) {
|
||||||
|
uniqueOrders.push(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrdersFromOrderBookAsync(
|
||||||
|
name: string,
|
||||||
|
makerAssetData: string,
|
||||||
|
takerAssetData: string,
|
||||||
|
): Promise<SignedOrder[]> {
|
||||||
|
try {
|
||||||
|
return (await orderbooks[name].getOrdersAsync(makerAssetData, takerAssetData)).map(r => r.order);
|
||||||
|
} catch (err) {
|
||||||
|
logUtils.warn(`Failed to retrieve orders from orderbook "${name}".`);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameOrder(a: SignedOrder, b: SignedOrder): boolean {
|
||||||
|
for (const [k, v] of Object.entries(a)) {
|
||||||
|
if (k in (b as any)) {
|
||||||
|
if (BigNumber.isBigNumber(v) && !v.eq((b as any)[k])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (v !== (b as any)[k]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTokenUnits(symbol: string, weis: Numberish): BigNumber {
|
||||||
|
return new BigNumber(weis).div(new BigNumber(10).pow(tokens[symbol].decimals));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromTokenUnits(symbol: string, units: Numberish): BigNumber {
|
||||||
|
return new BigNumber(units)
|
||||||
|
.times(new BigNumber(10).pow(tokens[symbol].decimals))
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarketOperationResult {
|
||||||
|
makerAssetBalanceBefore: BigNumber;
|
||||||
|
takerAssetBalanceBefore: BigNumber;
|
||||||
|
makerAssetBalanceAfter: BigNumber;
|
||||||
|
takerAssetBalanceAfter: BigNumber;
|
||||||
|
fillResults: FillResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liquidity is low right now so it's possible we didn't have
|
||||||
|
// enough taker assets to cover the orders, so occasionally we'll get incomplete
|
||||||
|
// fills. This function will catch those cases.
|
||||||
|
// TODO(dorothy-zbornak): Remove this special case when liquidity is up.
|
||||||
|
function checkHadEnoughTakerAsset(
|
||||||
|
quote: MarketBuySwapQuote | MarketSellSwapQuote,
|
||||||
|
result: MarketOperationResult,
|
||||||
|
): boolean {
|
||||||
|
if (result.takerAssetBalanceBefore.gte(quote.worstCaseQuoteInfo.takerAssetAmount)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const takerAssetPct = result.takerAssetBalanceBefore
|
||||||
|
.div(quote.worstCaseQuoteInfo.takerAssetAmount)
|
||||||
|
.times(100)
|
||||||
|
.toNumber()
|
||||||
|
.toFixed(1);
|
||||||
|
logUtils.warn(`Could not acquire enough taker asset to complete the fill: ${takerAssetPct}%`);
|
||||||
|
expect(result.fillResults.makerAssetFilledAmount).to.bignumber.lt(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
testContract = new TestMainnetAggregatorFillsContract(TEST_CONTRACT_ADDRESS, env.provider, {
|
||||||
|
...env.txDefaults,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 10e6,
|
||||||
|
});
|
||||||
|
swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(env.provider, 'https://api.0x.org/sra');
|
||||||
|
// Pool orderbooks because we're desperate for liquidity.
|
||||||
|
orderbooks.swapQuoter = swapQuoter.orderbook;
|
||||||
|
orderbooks.bamboo = Orderbook.getOrderbookForPollingProvider({
|
||||||
|
httpEndpoint: 'https://sra.bamboorelay.com/0x/v3',
|
||||||
|
pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||||
|
});
|
||||||
|
// TODO(dorothy-zbornak): Uncomment when radar's SRA is up.
|
||||||
|
// orderbooks.radar = Orderbook.getOrderbookForPollingProvider({
|
||||||
|
// httpEndpoint: 'https://api-v3.radarrelay.com/v3',
|
||||||
|
// pollingIntervalMs: ORDERBOOK_POLLING_MS,
|
||||||
|
// });
|
||||||
|
takerEthBalance = await env.web3Wrapper.getBalanceInWeiAsync(TAKER_ADDRESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('taker has minimum ETH', async () => {
|
||||||
|
expect(takerEthBalance).to.bignumber.gte(MIN_BALANCE);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('market sells', () => {
|
||||||
|
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||||
|
for (const fillValue of FILL_VALUES) {
|
||||||
|
const fillAmount = fromTokenUnits(takerSymbol, new BigNumber(fillValue).div(tokens[takerSymbol].price));
|
||||||
|
it(`sell ${toTokenUnits(takerSymbol, fillAmount)} ${takerSymbol} for ${makerSymbol}`, async () => {
|
||||||
|
const [quote, takerOrders] = await Promise.all([
|
||||||
|
swapQuoter.getMarketSellSwapQuoteAsync(
|
||||||
|
tokens[makerSymbol].address,
|
||||||
|
tokens[takerSymbol].address,
|
||||||
|
fillAmount,
|
||||||
|
{ gasPrice: GAS_PRICE },
|
||||||
|
),
|
||||||
|
getTakerOrdersAsync(takerSymbol),
|
||||||
|
]);
|
||||||
|
// Buy taker assets from `takerOrders` and and perform a
|
||||||
|
// market sell on the bridge orders.
|
||||||
|
const fill = await testContract
|
||||||
|
.marketSell(
|
||||||
|
tokens[makerSymbol].address,
|
||||||
|
tokens[takerSymbol].address,
|
||||||
|
quote.orders,
|
||||||
|
takerOrders,
|
||||||
|
quote.orders.map(o => o.signature),
|
||||||
|
takerOrders.map(o => o.signature),
|
||||||
|
quote.takerAssetFillAmount,
|
||||||
|
)
|
||||||
|
.callAsync({
|
||||||
|
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||||
|
from: TAKER_ADDRESS,
|
||||||
|
gasPrice: quote.gasPrice,
|
||||||
|
});
|
||||||
|
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||||
|
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||||
|
quote.worstCaseQuoteInfo.makerAssetAmount,
|
||||||
|
);
|
||||||
|
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||||
|
quote.takerAssetFillAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('market buys', () => {
|
||||||
|
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
|
||||||
|
for (const fillValue of FILL_VALUES) {
|
||||||
|
const fillAmount = fromTokenUnits(makerSymbol, new BigNumber(fillValue).div(tokens[makerSymbol].price));
|
||||||
|
it(`buy ${toTokenUnits(makerSymbol, fillAmount)} ${makerSymbol} with ${takerSymbol}`, async () => {
|
||||||
|
const [quote, takerOrders] = await Promise.all([
|
||||||
|
swapQuoter.getMarketBuySwapQuoteAsync(
|
||||||
|
tokens[makerSymbol].address,
|
||||||
|
tokens[takerSymbol].address,
|
||||||
|
fillAmount,
|
||||||
|
{ gasPrice: GAS_PRICE },
|
||||||
|
),
|
||||||
|
getTakerOrdersAsync(takerSymbol),
|
||||||
|
]);
|
||||||
|
// Buy taker assets from `takerOrders` and and perform a
|
||||||
|
// market buy on the bridge orders.
|
||||||
|
const fill = await testContract
|
||||||
|
.marketBuy(
|
||||||
|
tokens[makerSymbol].address,
|
||||||
|
tokens[takerSymbol].address,
|
||||||
|
quote.orders,
|
||||||
|
takerOrders,
|
||||||
|
quote.orders.map(o => o.signature),
|
||||||
|
takerOrders.map(o => o.signature),
|
||||||
|
quote.makerAssetFillAmount,
|
||||||
|
)
|
||||||
|
.callAsync({
|
||||||
|
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
|
||||||
|
from: TAKER_ADDRESS,
|
||||||
|
gasPrice: quote.gasPrice,
|
||||||
|
});
|
||||||
|
if (checkHadEnoughTakerAsset(quote, fill)) {
|
||||||
|
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
|
||||||
|
quote.worstCaseQuoteInfo.takerAssetAmount,
|
||||||
|
);
|
||||||
|
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
|
||||||
|
quote.makerAssetFillAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
77
contracts/integrations/test/aggregation/tokens.ts
Normal file
77
contracts/integrations/test/aggregation/tokens.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
export const tokens: { [symbol: string]: { address: string; decimals: number; price: number } } = {
|
||||||
|
ETH: {
|
||||||
|
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||||
|
decimals: 18,
|
||||||
|
price: 133,
|
||||||
|
},
|
||||||
|
SAI: {
|
||||||
|
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
|
||||||
|
decimals: 18,
|
||||||
|
price: 1,
|
||||||
|
},
|
||||||
|
DAI: {
|
||||||
|
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||||
|
decimals: 18,
|
||||||
|
price: 1,
|
||||||
|
},
|
||||||
|
USDC: {
|
||||||
|
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||||
|
decimals: 6,
|
||||||
|
price: 1,
|
||||||
|
},
|
||||||
|
WBTC: {
|
||||||
|
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||||
|
decimals: 8,
|
||||||
|
price: 6900,
|
||||||
|
},
|
||||||
|
MKR: {
|
||||||
|
address: '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
decimals: 18,
|
||||||
|
price: 454,
|
||||||
|
},
|
||||||
|
BAT: {
|
||||||
|
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
|
||||||
|
decimals: 18,
|
||||||
|
price: 0.17,
|
||||||
|
},
|
||||||
|
OMG: {
|
||||||
|
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
|
||||||
|
decimals: 18,
|
||||||
|
price: 0.65,
|
||||||
|
},
|
||||||
|
ZRX: {
|
||||||
|
address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
|
||||||
|
decimals: 18,
|
||||||
|
price: 0.19,
|
||||||
|
},
|
||||||
|
ZIL: {
|
||||||
|
address: '0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27',
|
||||||
|
decimals: 12,
|
||||||
|
price: 0.004,
|
||||||
|
},
|
||||||
|
FOAM: {
|
||||||
|
address: '0x4946Fcea7C692606e8908002e55A582af44AC121',
|
||||||
|
decimals: 18,
|
||||||
|
price: 0.004,
|
||||||
|
},
|
||||||
|
USDT: {
|
||||||
|
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||||
|
decimals: 6,
|
||||||
|
price: 0.019,
|
||||||
|
},
|
||||||
|
REP: {
|
||||||
|
address: '0x1985365e9f78359a9B6AD760e32412f4a445E862',
|
||||||
|
decimals: 18,
|
||||||
|
price: 8.9,
|
||||||
|
},
|
||||||
|
MANA: {
|
||||||
|
address: '0x0F5D2fB29fb7d3CFeE444a200298f468908cC942',
|
||||||
|
decimals: 18,
|
||||||
|
price: 0.025,
|
||||||
|
},
|
||||||
|
LINK: {
|
||||||
|
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
||||||
|
decimals: 18,
|
||||||
|
price: 1.8,
|
||||||
|
},
|
||||||
|
};
|
@ -9,6 +9,7 @@ import * as TestDydxUser from '../test/generated-artifacts/TestDydxUser.json';
|
|||||||
import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json';
|
import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json';
|
||||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||||
import * as TestFramework from '../test/generated-artifacts/TestFramework.json';
|
import * as TestFramework from '../test/generated-artifacts/TestFramework.json';
|
||||||
|
import * as TestMainnetAggregatorFills from '../test/generated-artifacts/TestMainnetAggregatorFills.json';
|
||||||
import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json';
|
import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json';
|
||||||
import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json';
|
import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json';
|
||||||
import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json';
|
import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json';
|
||||||
@ -17,6 +18,7 @@ export const artifacts = {
|
|||||||
TestEth2Dai: TestEth2Dai as ContractArtifact,
|
TestEth2Dai: TestEth2Dai as ContractArtifact,
|
||||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||||
TestFramework: TestFramework as ContractArtifact,
|
TestFramework: TestFramework as ContractArtifact,
|
||||||
|
TestMainnetAggregatorFills: TestMainnetAggregatorFills as ContractArtifact,
|
||||||
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||||
TestUniswapExchange: TestUniswapExchange as ContractArtifact,
|
TestUniswapExchange: TestUniswapExchange as ContractArtifact,
|
||||||
TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact,
|
TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact,
|
||||||
|
@ -14,7 +14,7 @@ import { contractAddresses, dydxAccountOwner } from '../mainnet_fork_utils';
|
|||||||
|
|
||||||
import { dydxEvents } from './abi/dydxEvents';
|
import { dydxEvents } from './abi/dydxEvents';
|
||||||
|
|
||||||
blockchainTests.resets.fork('Mainnet dydx bridge tests', env => {
|
blockchainTests.fork.resets('Mainnet dydx bridge tests', env => {
|
||||||
let testContract: DydxBridgeContract;
|
let testContract: DydxBridgeContract;
|
||||||
// random account to receive tokens from dydx
|
// random account to receive tokens from dydx
|
||||||
const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308';
|
const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308';
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
|
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||||
import { ERC20ProxyContract, MultiAssetProxyContract } from '@0x/contracts-asset-proxy';
|
import { ERC20ProxyContract, MultiAssetProxyContract } from '@0x/contracts-asset-proxy';
|
||||||
import { StakingProxyContract, ZrxVaultContract } from '@0x/contracts-staking';
|
import { StakingProxyContract, ZrxVaultContract } from '@0x/contracts-staking';
|
||||||
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
||||||
import { AssetProxyId } from '@0x/types';
|
import { AssetProxyId } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { contractAddresses, contractWrappers } from './mainnet_fork_utils';
|
import { contractAddresses, getContractwrappers } from './mainnet_fork_utils';
|
||||||
|
|
||||||
|
blockchainTests.fork.resets('Mainnet configs tests', env => {
|
||||||
|
let contractWrappers: ContractWrappers;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
contractWrappers = getContractwrappers(env.provider);
|
||||||
|
});
|
||||||
|
|
||||||
blockchainTests.resets.fork('Mainnet configs tests', env => {
|
|
||||||
describe('Exchange', () => {
|
describe('Exchange', () => {
|
||||||
it('should be owned by the ZeroExGovernor ', async () => {
|
it('should be owned by the ZeroExGovernor ', async () => {
|
||||||
const owner = await contractWrappers.exchange.owner().callAsync();
|
const owner = await contractWrappers.exchange.owner().callAsync();
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
import { ContractWrappers } from '@0x/contract-wrappers';
|
||||||
import { provider } from '@0x/contracts-test-utils';
|
import { Web3ProviderEngine } from '@0x/dev-utils';
|
||||||
|
|
||||||
const chainId = 1;
|
const chainId = 1;
|
||||||
const contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
export const dydxAccountOwner = '0xeb58c2caa96f39626dcceb74fdbb7a9a8b54ec18';
|
||||||
const contractWrappers = new ContractWrappers(provider, { chainId, contractAddresses });
|
export const contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
||||||
const dydxAccountOwner = '0xeb58c2caa96f39626dcceb74fdbb7a9a8b54ec18';
|
|
||||||
|
|
||||||
export { contractAddresses, contractWrappers, dydxAccountOwner };
|
/**
|
||||||
|
* Create contract wrappers for the mainnet given a mainnet/forked provider.
|
||||||
|
*/
|
||||||
|
export function getContractwrappers(provider: Web3ProviderEngine): ContractWrappers {
|
||||||
|
return new ContractWrappers(provider, { chainId, contractAddresses });
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ export * from '../test/generated-wrappers/test_dydx_user';
|
|||||||
export * from '../test/generated-wrappers/test_eth2_dai';
|
export * from '../test/generated-wrappers/test_eth2_dai';
|
||||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||||
export * from '../test/generated-wrappers/test_framework';
|
export * from '../test/generated-wrappers/test_framework';
|
||||||
|
export * from '../test/generated-wrappers/test_mainnet_aggregator_fills';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_bridge';
|
export * from '../test/generated-wrappers/test_uniswap_bridge';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_exchange';
|
export * from '../test/generated-wrappers/test_uniswap_exchange';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_exchange_factory';
|
export * from '../test/generated-wrappers/test_uniswap_exchange_factory';
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"test/generated-artifacts/TestEth2Dai.json",
|
"test/generated-artifacts/TestEth2Dai.json",
|
||||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||||
"test/generated-artifacts/TestFramework.json",
|
"test/generated-artifacts/TestFramework.json",
|
||||||
|
"test/generated-artifacts/TestMainnetAggregatorFills.json",
|
||||||
"test/generated-artifacts/TestUniswapBridge.json",
|
"test/generated-artifacts/TestUniswapBridge.json",
|
||||||
"test/generated-artifacts/TestUniswapExchange.json",
|
"test/generated-artifacts/TestUniswapExchange.json",
|
||||||
"test/generated-artifacts/TestUniswapExchangeFactory.json"
|
"test/generated-artifacts/TestUniswapExchangeFactory.json"
|
||||||
|
@ -5,6 +5,14 @@
|
|||||||
{
|
{
|
||||||
"note": "Added ERC20BridgeProxy to list of unlocked accounts on mainnet fork",
|
"note": "Added ERC20BridgeProxy to list of unlocked accounts on mainnet fork",
|
||||||
"pr": 2401
|
"pr": 2401
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add `blockchainTests.live()` for live network tests.",
|
||||||
|
"pr": 2407
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add modifiers to `blockchainTests.fork()`.",
|
||||||
|
"pr": 2407
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
import { BlockchainLifecycle, web3Factory } from '@0x/dev-utils';
|
||||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
import { RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
|
||||||
import { providerUtils } from '@0x/utils';
|
import { providerUtils } from '@0x/utils';
|
||||||
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
|
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as mocha from 'mocha';
|
import * as mocha from 'mocha';
|
||||||
import * as process from 'process';
|
import * as process from 'process';
|
||||||
|
|
||||||
import { provider, txDefaults, web3Wrapper } from './web3_wrapper';
|
import { provider, providerConfigs, txDefaults, web3Wrapper } from './web3_wrapper';
|
||||||
|
|
||||||
// tslint:disable: no-namespace only-arrow-functions no-unbound-method
|
// tslint:disable: no-namespace only-arrow-functions no-unbound-method max-classes-per-file
|
||||||
|
|
||||||
export type ISuite = mocha.ISuite;
|
export type ISuite = mocha.ISuite;
|
||||||
export type ISuiteCallbackContext = mocha.ISuiteCallbackContext;
|
export type ISuiteCallbackContext = mocha.ISuiteCallbackContext;
|
||||||
@ -18,22 +18,32 @@ export type BlockchainSuiteCallback = (this: ISuiteCallbackContext, env: Blockch
|
|||||||
export type BlockchainContextDefinitionCallback<T> = (description: string, callback: BlockchainSuiteCallback) => T;
|
export type BlockchainContextDefinitionCallback<T> = (description: string, callback: BlockchainSuiteCallback) => T;
|
||||||
export interface ContextDefinition extends mocha.IContextDefinition {
|
export interface ContextDefinition extends mocha.IContextDefinition {
|
||||||
optional: ContextDefinitionCallback<ISuite | void>;
|
optional: ContextDefinitionCallback<ISuite | void>;
|
||||||
fork: ContextDefinitionCallback<ISuite | void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for `blockchainTests()`.
|
* Interface for `blockchainTests()`.
|
||||||
*/
|
*/
|
||||||
export interface BlockchainContextDefinition extends BlockchainContextDefinitionPartial {
|
export interface BlockchainContextDefinition {
|
||||||
resets: BlockchainContextDefinitionPartial;
|
(description: string, callback: BlockchainSuiteCallback): ISuite;
|
||||||
}
|
|
||||||
|
|
||||||
interface BlockchainContextDefinitionPartial {
|
|
||||||
only: BlockchainContextDefinitionCallback<ISuite>;
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
||||||
skip: BlockchainContextDefinitionCallback<void>;
|
skip: BlockchainContextDefinitionCallback<void>;
|
||||||
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||||
fork: BlockchainContextDefinitionCallback<ISuite | void>;
|
resets: BlockchainContextDefinitionCallback<ISuite | void> & {
|
||||||
(description: string, callback: BlockchainSuiteCallback): ISuite;
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
||||||
|
skip: BlockchainContextDefinitionCallback<void>;
|
||||||
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||||
|
};
|
||||||
|
fork: BlockchainContextDefinitionCallback<ISuite | void> & {
|
||||||
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
||||||
|
skip: BlockchainContextDefinitionCallback<void>;
|
||||||
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||||
|
resets: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||||
|
};
|
||||||
|
live: BlockchainContextDefinitionCallback<ISuite | void> & {
|
||||||
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
||||||
|
skip: BlockchainContextDefinitionCallback<void>;
|
||||||
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,29 +58,11 @@ export interface BlockchainTestsEnvironment {
|
|||||||
getAccountAddressesAsync(): Promise<string[]>;
|
getAccountAddressesAsync(): Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class BlockchainTestsEnvironmentBase {
|
||||||
* Concret implementation of `BlockchainTestsEnvironment`.
|
public blockchainLifecycle!: BlockchainLifecycle;
|
||||||
*/
|
public provider!: Web3ProviderEngine;
|
||||||
export class BlockchainTestsEnvironmentSingleton {
|
public txDefaults!: Partial<TxData>;
|
||||||
private static _instance: BlockchainTestsEnvironmentSingleton | undefined;
|
public web3Wrapper!: Web3Wrapper;
|
||||||
|
|
||||||
public blockchainLifecycle: BlockchainLifecycle;
|
|
||||||
public provider: Web3ProviderEngine;
|
|
||||||
public txDefaults: Partial<TxData>;
|
|
||||||
public web3Wrapper: Web3Wrapper;
|
|
||||||
|
|
||||||
// Create or retrieve the singleton instance of this class.
|
|
||||||
public static create(): BlockchainTestsEnvironmentSingleton {
|
|
||||||
if (BlockchainTestsEnvironmentSingleton._instance === undefined) {
|
|
||||||
BlockchainTestsEnvironmentSingleton._instance = new BlockchainTestsEnvironmentSingleton();
|
|
||||||
}
|
|
||||||
return BlockchainTestsEnvironmentSingleton._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the singleton instance of this class.
|
|
||||||
public static getInstance(): BlockchainTestsEnvironmentSingleton | undefined {
|
|
||||||
return BlockchainTestsEnvironmentSingleton._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChainIdAsync(): Promise<number> {
|
public async getChainIdAsync(): Promise<number> {
|
||||||
return providerUtils.getChainIdAsync(this.provider);
|
return providerUtils.getChainIdAsync(this.provider);
|
||||||
@ -79,8 +71,33 @@ export class BlockchainTestsEnvironmentSingleton {
|
|||||||
public async getAccountAddressesAsync(): Promise<string[]> {
|
public async getAccountAddressesAsync(): Promise<string[]> {
|
||||||
return this.web3Wrapper.getAvailableAddressesAsync();
|
return this.web3Wrapper.getAvailableAddressesAsync();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockchainEnvironmentFactory {
|
||||||
|
create(): BlockchainTestsEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `BlockchainTestsEnvironment` that uses the default ganache provider.
|
||||||
|
*/
|
||||||
|
export class StandardBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
||||||
|
private static _instance: StandardBlockchainTestsEnvironmentSingleton | undefined;
|
||||||
|
|
||||||
|
// Create or retrieve the singleton instance of this class.
|
||||||
|
public static create(): StandardBlockchainTestsEnvironmentSingleton {
|
||||||
|
if (StandardBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
||||||
|
StandardBlockchainTestsEnvironmentSingleton._instance = new StandardBlockchainTestsEnvironmentSingleton();
|
||||||
|
}
|
||||||
|
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the singleton instance of this class.
|
||||||
|
public static getInstance(): StandardBlockchainTestsEnvironmentSingleton | undefined {
|
||||||
|
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
|
super();
|
||||||
this.blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
this.blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.txDefaults = txDefaults;
|
this.txDefaults = txDefaults;
|
||||||
@ -88,6 +105,89 @@ export class BlockchainTestsEnvironmentSingleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `BlockchainTestsEnvironment` that uses a forked ganache provider.
|
||||||
|
*/
|
||||||
|
export class ForkedBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
||||||
|
private static _instance: ForkedBlockchainTestsEnvironmentSingleton | undefined;
|
||||||
|
|
||||||
|
// Create or retrieve the singleton instance of this class.
|
||||||
|
public static create(): ForkedBlockchainTestsEnvironmentSingleton {
|
||||||
|
if (ForkedBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton._instance = new ForkedBlockchainTestsEnvironmentSingleton();
|
||||||
|
}
|
||||||
|
return ForkedBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static _createWeb3Provider(forkHost: string): Web3ProviderEngine {
|
||||||
|
return web3Factory.getRpcProvider({
|
||||||
|
...providerConfigs,
|
||||||
|
fork: forkHost,
|
||||||
|
blockTime: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the singleton instance of this class.
|
||||||
|
public static getInstance(): ForkedBlockchainTestsEnvironmentSingleton | undefined {
|
||||||
|
return ForkedBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
super();
|
||||||
|
this.txDefaults = txDefaults;
|
||||||
|
this.provider = process.env.FORK_RPC_URL
|
||||||
|
? ForkedBlockchainTestsEnvironmentSingleton._createWeb3Provider(process.env.FORK_RPC_URL)
|
||||||
|
: // Create a dummy provider if no RPC backend supplied.
|
||||||
|
createDummyProvider();
|
||||||
|
this.web3Wrapper = new Web3Wrapper(this.provider);
|
||||||
|
this.blockchainLifecycle = new BlockchainLifecycle(this.web3Wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `BlockchainTestsEnvironment` that uses a live web3 provider.
|
||||||
|
*/
|
||||||
|
export class LiveBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
||||||
|
private static _instance: LiveBlockchainTestsEnvironmentSingleton | undefined;
|
||||||
|
|
||||||
|
// Create or retrieve the singleton instance of this class.
|
||||||
|
public static create(): LiveBlockchainTestsEnvironmentSingleton {
|
||||||
|
if (LiveBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
||||||
|
LiveBlockchainTestsEnvironmentSingleton._instance = new LiveBlockchainTestsEnvironmentSingleton();
|
||||||
|
}
|
||||||
|
return LiveBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static _createWeb3Provider(rpcHost: string): Web3ProviderEngine {
|
||||||
|
const providerEngine = new Web3ProviderEngine();
|
||||||
|
providerEngine.addProvider(new RPCSubprovider(rpcHost));
|
||||||
|
providerUtils.startProviderEngine(providerEngine);
|
||||||
|
return providerEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the singleton instance of this class.
|
||||||
|
public static getInstance(): LiveBlockchainTestsEnvironmentSingleton | undefined {
|
||||||
|
return LiveBlockchainTestsEnvironmentSingleton._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
super();
|
||||||
|
this.txDefaults = txDefaults;
|
||||||
|
this.provider = process.env.LIVE_RPC_URL
|
||||||
|
? LiveBlockchainTestsEnvironmentSingleton._createWeb3Provider(process.env.LIVE_RPC_URL)
|
||||||
|
: // Create a dummy provider if no RPC backend supplied.
|
||||||
|
createDummyProvider();
|
||||||
|
this.web3Wrapper = new Web3Wrapper(this.provider);
|
||||||
|
const snapshotHandlerAsync = async (): Promise<void> => {
|
||||||
|
throw new Error('Snapshots are not supported with a live provider.');
|
||||||
|
};
|
||||||
|
this.blockchainLifecycle = {
|
||||||
|
startAsync: snapshotHandlerAsync,
|
||||||
|
revertAsync: snapshotHandlerAsync,
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The original `describe()` global provided by mocha.
|
// The original `describe()` global provided by mocha.
|
||||||
const mochaDescribe = (global as any).describe as mocha.IContextDefinition;
|
const mochaDescribe = (global as any).describe as mocha.IContextDefinition;
|
||||||
|
|
||||||
@ -99,77 +199,153 @@ export const describe = _.assign(mochaDescribe, {
|
|||||||
const describeCall = process.env.TEST_ALL ? mochaDescribe : mochaDescribe.skip;
|
const describeCall = process.env.TEST_ALL ? mochaDescribe : mochaDescribe.skip;
|
||||||
return describeCall(description, callback);
|
return describeCall(description, callback);
|
||||||
},
|
},
|
||||||
fork(description: string, callback: SuiteCallback): ISuite | void {
|
|
||||||
const describeCall = process.env.FORK_RPC_URL ? mochaDescribe.only : mochaDescribe.skip;
|
|
||||||
return describeCall(description, callback);
|
|
||||||
},
|
|
||||||
}) as ContextDefinition;
|
}) as ContextDefinition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like mocha's `describe()`, but sets up a blockchain environment on first call.
|
* Like mocha's `describe()`, but sets up a blockchain environment for you.
|
||||||
*/
|
*/
|
||||||
export const blockchainTests: BlockchainContextDefinition = _.assign(
|
export const blockchainTests: BlockchainContextDefinition = _.assign(
|
||||||
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
||||||
return defineBlockchainSuite(description, callback, describe);
|
return defineBlockchainSuite(StandardBlockchainTestsEnvironmentSingleton, description, callback, describe);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
||||||
return defineBlockchainSuite(description, callback, describe.only);
|
return defineBlockchainSuite(
|
||||||
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
describe.only,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
skip(description: string, callback: BlockchainSuiteCallback): void {
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
||||||
return defineBlockchainSuite(description, callback, describe.skip);
|
return defineBlockchainSuite(
|
||||||
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
describe.skip,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
return defineBlockchainSuite(description, callback, process.env.TEST_ALL ? describe : describe.skip);
|
|
||||||
},
|
|
||||||
fork(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
||||||
return defineBlockchainSuite(
|
return defineBlockchainSuite(
|
||||||
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.TEST_ALL ? describe : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fork: _.assign(
|
||||||
|
function(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.FORK_RPC_URL ? describe : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
only(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton,
|
||||||
description,
|
description,
|
||||||
callback,
|
callback,
|
||||||
process.env.FORK_RPC_URL ? describe.only : describe.skip,
|
process.env.FORK_RPC_URL ? describe.only : describe.skip,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.FORK_RPC_URL ? describe.optional : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
resets(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineResetsBlockchainSuite(
|
||||||
|
ForkedBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.FORK_RPC_URL ? describe : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
live: _.assign(
|
||||||
|
function(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
LiveBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.LIVE_RPC_URL ? describe : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
only(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
LiveBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.LIVE_RPC_URL ? describe.only : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
LiveBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
|
return defineBlockchainSuite(
|
||||||
|
LiveBlockchainTestsEnvironmentSingleton,
|
||||||
|
description,
|
||||||
|
callback,
|
||||||
|
process.env.LIVE_RPC_URL ? describe.optional : describe.skip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
resets: _.assign(
|
resets: _.assign(
|
||||||
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
||||||
return defineBlockchainSuite(description, callback, function(
|
return defineResetsBlockchainSuite(
|
||||||
_description: string,
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
_callback: SuiteCallback,
|
description,
|
||||||
): ISuite {
|
callback,
|
||||||
return defineResetsSuite(_description, _callback, describe);
|
describe,
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
||||||
return defineBlockchainSuite(description, callback, function(
|
return defineResetsBlockchainSuite(
|
||||||
_description: string,
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
_callback: SuiteCallback,
|
description,
|
||||||
): ISuite {
|
callback,
|
||||||
return defineResetsSuite(_description, _callback, describe.only);
|
describe.only,
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
skip(description: string, callback: BlockchainSuiteCallback): void {
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
||||||
return defineBlockchainSuite(description, callback, function(
|
return defineResetsBlockchainSuite(
|
||||||
_description: string,
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
_callback: SuiteCallback,
|
description,
|
||||||
): void {
|
callback,
|
||||||
return defineResetsSuite(_description, _callback, describe.skip);
|
describe.skip,
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
||||||
return defineBlockchainSuite(description, callback, function(
|
return defineResetsBlockchainSuite(
|
||||||
_description: string,
|
StandardBlockchainTestsEnvironmentSingleton,
|
||||||
_callback: SuiteCallback,
|
description,
|
||||||
): ISuite | void {
|
callback,
|
||||||
return defineResetsSuite(_description, _callback, describe.optional);
|
describe.optional,
|
||||||
});
|
);
|
||||||
},
|
|
||||||
fork(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
||||||
return defineBlockchainSuite(description, callback, function(
|
|
||||||
_description: string,
|
|
||||||
_callback: SuiteCallback,
|
|
||||||
): ISuite | void {
|
|
||||||
return defineResetsSuite(_description, _callback, describe.fork);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -177,29 +353,37 @@ export const blockchainTests: BlockchainContextDefinition = _.assign(
|
|||||||
) as BlockchainContextDefinition;
|
) as BlockchainContextDefinition;
|
||||||
|
|
||||||
function defineBlockchainSuite<T>(
|
function defineBlockchainSuite<T>(
|
||||||
|
envFactory: BlockchainEnvironmentFactory,
|
||||||
description: string,
|
description: string,
|
||||||
callback: BlockchainSuiteCallback,
|
callback: BlockchainSuiteCallback,
|
||||||
describeCall: ContextDefinitionCallback<T>,
|
describeCall: ContextDefinitionCallback<T>,
|
||||||
): T {
|
): T {
|
||||||
const env = BlockchainTestsEnvironmentSingleton.create();
|
|
||||||
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
||||||
before(async () => env.blockchainLifecycle.startAsync());
|
callback.call(this, envFactory.create());
|
||||||
after(async () => env.blockchainLifecycle.revertAsync());
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineResetsBlockchainSuite<T>(
|
||||||
|
envFactory: BlockchainEnvironmentFactory,
|
||||||
|
description: string,
|
||||||
|
callback: BlockchainSuiteCallback,
|
||||||
|
describeCall: ContextDefinitionCallback<T>,
|
||||||
|
): T {
|
||||||
|
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
||||||
|
const env = envFactory.create();
|
||||||
|
beforeEach(async () => env.blockchainLifecycle.startAsync());
|
||||||
|
afterEach(async () => env.blockchainLifecycle.revertAsync());
|
||||||
callback.call(this, env);
|
callback.call(this, env);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineResetsSuite<T>(
|
function createDummyProvider(): Web3ProviderEngine {
|
||||||
description: string,
|
return {
|
||||||
callback: SuiteCallback,
|
addProvider: _.noop,
|
||||||
describeCall: ContextDefinitionCallback<T>,
|
on: _.noop,
|
||||||
): T {
|
send: _.noop,
|
||||||
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
sendAsync: _.noop,
|
||||||
const env = BlockchainTestsEnvironmentSingleton.getInstance();
|
start: _.noop,
|
||||||
if (env !== undefined) {
|
stop: _.noop,
|
||||||
beforeEach(async () => env.blockchainLifecycle.startAsync());
|
};
|
||||||
afterEach(async () => env.blockchainLifecycle.revertAsync());
|
|
||||||
}
|
|
||||||
callback.call(this);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -15,28 +15,12 @@ export const txDefaults = {
|
|||||||
gasPrice: constants.DEFAULT_GAS_PRICE,
|
gasPrice: constants.DEFAULT_GAS_PRICE,
|
||||||
};
|
};
|
||||||
|
|
||||||
let providerConfigs: Web3Config = {
|
export let providerConfigs: Web3Config = {
|
||||||
total_accounts: constants.NUM_TEST_ACCOUNTS,
|
total_accounts: constants.NUM_TEST_ACCOUNTS,
|
||||||
shouldUseInProcessGanache: true,
|
shouldUseInProcessGanache: true,
|
||||||
shouldAllowUnlimitedContractSize: true,
|
shouldAllowUnlimitedContractSize: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.FORK_RPC_URL !== undefined) {
|
|
||||||
providerConfigs = {
|
|
||||||
...providerConfigs,
|
|
||||||
fork: process.env.FORK_RPC_URL,
|
|
||||||
blockTime: 0,
|
|
||||||
unlocked_accounts: [
|
|
||||||
// ZeroExGovernor signer addresses
|
|
||||||
'0x257619b7155d247e43c8b6d90c8c17278ae481f0',
|
|
||||||
'0x5ee2a00f8f01d099451844af7f894f26a57fcbf2',
|
|
||||||
'0x894d623e0e0e8ed12c4a73dada999e275684a37d',
|
|
||||||
// ERC20BridgeProxy
|
|
||||||
'0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const provider: Web3ProviderEngine = web3Factory.getRpcProvider(providerConfigs);
|
export const provider: Web3ProviderEngine = web3Factory.getRpcProvider(providerConfigs);
|
||||||
provider.stop();
|
provider.stop();
|
||||||
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `locked` `Web3Config` option.",
|
||||||
|
"pr": 2407
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1576540892,
|
"timestamp": 1576540892,
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
|
@ -23,6 +23,7 @@ export interface Web3Config {
|
|||||||
shouldAllowUnlimitedContractSize?: boolean;
|
shouldAllowUnlimitedContractSize?: boolean;
|
||||||
fork?: string;
|
fork?: string;
|
||||||
blockTime?: number;
|
blockTime?: number;
|
||||||
|
locked?: boolean;
|
||||||
unlocked_accounts?: string[];
|
unlocked_accounts?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ export const web3Factory = {
|
|||||||
mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
|
mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
|
||||||
fork: config.fork,
|
fork: config.fork,
|
||||||
blockTime: config.blockTime,
|
blockTime: config.blockTime,
|
||||||
|
locked: config.locked,
|
||||||
unlocked_accounts: config.unlocked_accounts,
|
unlocked_accounts: config.unlocked_accounts,
|
||||||
} as any), // TODO remove any once types are merged in DefinitelyTyped
|
} as any), // TODO remove any once types are merged in DefinitelyTyped
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user