* UniswapV3 VIP (#237) * `@0x/contracts-zero-ex`: Add UniswapV3Feature * `@0x/contracts-zero-ex`: Add UniswapV3 VIP `@0x/contract-artifacts`: Regenerate. `@0x/contract-wrappers`: Regenerate. `@0x/asset-swapper`: Add UniswapV3 VIP support. * address review comments and appease linter * `@0x/contracts-zero-ex`: Add UniswapV3Feature tests * Multiplex UniswapV3 (#241) * Add UniswapV3 support to Multiplex batchFill * Add AssetSwapper support for Multiplex UniswapV3 * fix repo scripts that use PKG= env var (#242) Co-authored-by: Lawrence Forman <me@merklejerk.com> * `@0x/asset-swapper`: Adjust uniswap gas overhead Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com> * OTC orders feature (#244) * Add OTC orders feature contracts * Address PR feedback * Remove partial fills for takerSigned variant * Add function to query the min valid nonce * Add ETH support * Tightly pack expiry, nonceBucket, and nonce * Address PR feedback * OTC orders unit tests * Bump prettier version * Skip unnecessary math if takerTokenFillAmount == order.takerAmount * appease CI * Update contract-artifacts and contract-wrappers and CHANGELOGs * `@0x/contracts-zero-ex`: Address spot check feedback * `regen wrappers * prettier * `@0x/asset-swapper`: prettier and tweak gas schedule slightly for uni3 Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>
		
			
				
	
	
		
			649 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			649 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
 | |
| import { encodeERC20AssetData } from '@0x/contracts-asset-proxy';
 | |
| import { ExchangeContract } from '@0x/contracts-exchange';
 | |
| import { blockchainTests, constants, expect, OrderFactory } from '@0x/contracts-test-utils';
 | |
| import { defaultOrmConfig, getAppAsync } from '@0x/coordinator-server';
 | |
| import { devConstants, tokenUtils } from '@0x/dev-utils';
 | |
| import { runMigrationsOnceAsync } from '@0x/migrations';
 | |
| import { SignedOrder } from '@0x/types';
 | |
| import { BigNumber, fetchAsync, logUtils } from '@0x/utils';
 | |
| import * as nock from 'nock';
 | |
| 
 | |
| import { CoordinatorClient, CoordinatorRegistryContract, CoordinatorServerErrorMsg } from '@0x/contracts-coordinator';
 | |
| import { DummyERC20TokenContract } from '@0x/contracts-erc20';
 | |
| 
 | |
| const coordinatorPort = '3000';
 | |
| const anotherCoordinatorPort = '4000';
 | |
| const coordinatorEndpoint = 'http://localhost:';
 | |
| 
 | |
| const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
 | |
| 
 | |
| // tslint:disable:custom-no-magic-numbers
 | |
| blockchainTests.skip('Coordinator Client', env => {
 | |
|     const takerTokenFillAmount = new BigNumber(0);
 | |
|     const chainId = 1337;
 | |
| 
 | |
|     let contractAddresses: ContractAddresses;
 | |
|     let coordinatorRegistry: CoordinatorRegistryContract;
 | |
|     let coordinatorClient: CoordinatorClient;
 | |
|     let orderFactory: OrderFactory;
 | |
|     let userAddresses: string[];
 | |
|     let makerAddress: string;
 | |
|     let takerAddress: string;
 | |
|     let feeRecipientAddressOne: string;
 | |
|     let feeRecipientAddressTwo: string;
 | |
|     let feeRecipientAddressThree: string;
 | |
|     let feeRecipientAddressFour: string;
 | |
|     let makerAssetData: string;
 | |
|     let takerAssetData: string;
 | |
|     let feeAssetData: string;
 | |
| 
 | |
|     let makerToken: DummyERC20TokenContract;
 | |
|     let takerToken: DummyERC20TokenContract;
 | |
|     let txHash: string;
 | |
|     let signedOrder: SignedOrder;
 | |
|     let anotherSignedOrder: SignedOrder;
 | |
|     let signedOrderWithDifferentFeeRecipient: SignedOrder;
 | |
|     let signedOrderWithDifferentCoordinatorOperator: SignedOrder;
 | |
| 
 | |
|     // for testing server error responses
 | |
|     let serverValidationError: any;
 | |
| 
 | |
|     before(async () => {
 | |
|         contractAddresses = await runMigrationsOnceAsync(env.provider, {
 | |
|             gas: devConstants.GAS_LIMIT,
 | |
|             from: devConstants.TESTRPC_FIRST_ADDRESS,
 | |
|         });
 | |
| 
 | |
|         coordinatorClient = new CoordinatorClient(
 | |
|             contractAddresses.coordinator,
 | |
|             env.provider,
 | |
|             chainId,
 | |
|             {}, // txDefaults
 | |
|             contractAddresses.exchange,
 | |
|             contractAddresses.coordinatorRegistry,
 | |
|         );
 | |
|         coordinatorRegistry = new CoordinatorRegistryContract(contractAddresses.coordinatorRegistry, env.provider);
 | |
|         userAddresses = await env.web3Wrapper.getAvailableAddressesAsync();
 | |
|         [
 | |
|             ,
 | |
|             makerAddress,
 | |
|             takerAddress,
 | |
|             feeRecipientAddressOne,
 | |
|             feeRecipientAddressTwo,
 | |
|             feeRecipientAddressThree,
 | |
|             feeRecipientAddressFour,
 | |
|         ] = userAddresses.slice(0, 7);
 | |
| 
 | |
|         // declare encoded asset data
 | |
|         const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
 | |
|         const feeTokenAddress = contractAddresses.zrxToken;
 | |
|         [makerAssetData, takerAssetData, feeAssetData] = [
 | |
|             encodeERC20AssetData(makerTokenAddress),
 | |
|             encodeERC20AssetData(takerTokenAddress),
 | |
|             encodeERC20AssetData(feeTokenAddress),
 | |
|         ];
 | |
| 
 | |
|         // set initial balances
 | |
|         makerToken = new DummyERC20TokenContract(makerTokenAddress, env.provider);
 | |
|         takerToken = new DummyERC20TokenContract(takerTokenAddress, env.provider);
 | |
| 
 | |
|         // Configure order defaults
 | |
|         const defaultOrderParams = {
 | |
|             ...constants.STATIC_ORDER_PARAMS,
 | |
|             makerAddress,
 | |
|             feeRecipientAddress: feeRecipientAddressOne,
 | |
|             makerAssetData,
 | |
|             takerAssetData,
 | |
|             makerFeeAssetData: feeAssetData,
 | |
|             takerFeeAssetData: feeAssetData,
 | |
|             senderAddress: contractAddresses.coordinator,
 | |
|             exchangeAddress: contractAddresses.exchange,
 | |
|             chainId,
 | |
|         };
 | |
|         const privateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
 | |
|         orderFactory = new OrderFactory(privateKey, defaultOrderParams);
 | |
| 
 | |
|         // configure mock coordinator servers
 | |
|         const coordinatorServerConfigs = {
 | |
|             HTTP_PORT: 3000, // Only used in default instantiation in 0x-coordinator-server/server.js; not used here
 | |
|             CHAIN_ID_TO_SETTINGS: {
 | |
|                 // TODO: change to CHAIN_ID_TO_SETTINGS when @0x/coordinator-server is ready
 | |
|                 [chainId]: {
 | |
|                     FEE_RECIPIENTS: [feeRecipientAddressOne, feeRecipientAddressTwo, feeRecipientAddressThree].map(
 | |
|                         address => {
 | |
|                             return {
 | |
|                                 ADDRESS: address,
 | |
|                                 PRIVATE_KEY: constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(address)].toString(
 | |
|                                     'hex',
 | |
|                                 ),
 | |
|                             };
 | |
|                         },
 | |
|                     ),
 | |
|                     // Ethereum RPC url, only used in the default instantiation in 0x-coordinator-server/server.js
 | |
|                     // Not used here when instantiating with the imported app
 | |
|                     RPC_URL: 'http://ignore',
 | |
|                 },
 | |
|             },
 | |
|             NETWORK_ID_TO_CONTRACT_ADDRESSES: {
 | |
|                 // TODO: change to CHAIN_ID_TO_CONTRACT_ADDRESSES when @0x/coordinator-server is ready
 | |
|                 [chainId]: getContractAddressesForChainOrThrow(chainId),
 | |
|             },
 | |
|             // Optional selective delay on fill requests
 | |
|             SELECTIVE_DELAY_MS: 0,
 | |
|             EXPIRATION_DURATION_SECONDS: 60, // 1 minute
 | |
|         };
 | |
| 
 | |
|         // start coordinator servers
 | |
|         const serverScenarios: Array<[string, string]> = [
 | |
|             ['coord_server_1', coordinatorPort],
 | |
|             ['coord_server_2', anotherCoordinatorPort],
 | |
|         ];
 | |
|         await Promise.all(
 | |
|             serverScenarios.map(async ([name, port]) => {
 | |
|                 const app = await getAppAsync(
 | |
|                     {
 | |
|                         [chainId]: env.provider,
 | |
|                     },
 | |
|                     coordinatorServerConfigs,
 | |
|                     {
 | |
|                         name,
 | |
|                         type: 'sqlite',
 | |
|                         database: ':memory:',
 | |
|                         entities: defaultOrmConfig.entities,
 | |
|                         cli: defaultOrmConfig.cli,
 | |
|                         logging: defaultOrmConfig.logging,
 | |
|                         synchronize: defaultOrmConfig.synchronize,
 | |
|                     },
 | |
|                 );
 | |
|                 app.listen(port, () => {
 | |
|                     logUtils.log(`Coordinator SERVER API (HTTP) listening on port ${port}!`);
 | |
|                 });
 | |
|                 return app;
 | |
|             }),
 | |
|         );
 | |
| 
 | |
|         // register coordinator servers
 | |
|         [
 | |
|             [feeRecipientAddressOne, coordinatorPort],
 | |
|             [feeRecipientAddressTwo, coordinatorPort],
 | |
|             [feeRecipientAddressThree, anotherCoordinatorPort],
 | |
|         ].forEach(async ([address, port]) => {
 | |
|             await coordinatorRegistry
 | |
|                 .setCoordinatorEndpoint(`${coordinatorEndpoint}${port}`)
 | |
|                 .awaitTransactionSuccessAsync(
 | |
|                     { from: address },
 | |
|                     { pollingIntervalMs: constants.AWAIT_TRANSACTION_MINED_MS },
 | |
|                 );
 | |
|         });
 | |
|     });
 | |
|     beforeEach(async () => {
 | |
|         signedOrder = await orderFactory.newSignedOrderAsync();
 | |
|         anotherSignedOrder = await orderFactory.newSignedOrderAsync();
 | |
|         signedOrderWithDifferentFeeRecipient = await orderFactory.newSignedOrderAsync({
 | |
|             feeRecipientAddress: feeRecipientAddressTwo,
 | |
|         });
 | |
|         signedOrderWithDifferentCoordinatorOperator = await orderFactory.newSignedOrderAsync({
 | |
|             feeRecipientAddress: feeRecipientAddressThree,
 | |
|         });
 | |
|         makerToken.setBalance(makerAddress, constants.INITIAL_ERC20_BALANCE);
 | |
|         takerToken.setBalance(takerAddress, constants.INITIAL_ERC20_BALANCE);
 | |
|     });
 | |
|     describe('test setup', () => {
 | |
|         it('should have coordinator registry which returns an endpoint', async () => {
 | |
|             const setCoordinatorEndpoint = await coordinatorRegistry
 | |
|                 .getCoordinatorEndpoint(feeRecipientAddressOne)
 | |
|                 .callAsync();
 | |
|             const anotherSetCoordinatorEndpoint = await coordinatorRegistry
 | |
|                 .getCoordinatorEndpoint(feeRecipientAddressThree)
 | |
|                 .callAsync();
 | |
|             expect(setCoordinatorEndpoint).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|             expect(anotherSetCoordinatorEndpoint).to.equal(`${coordinatorEndpoint}${anotherCoordinatorPort}`);
 | |
|         });
 | |
|         it('should have coordinator server endpoints which respond to pings', async () => {
 | |
|             let result = await fetchAsync(`${coordinatorEndpoint}${coordinatorPort}/v2/ping`);
 | |
|             expect(result.status).to.equal(200);
 | |
|             expect(await result.text()).to.equal('pong');
 | |
| 
 | |
|             result = await fetchAsync(`${coordinatorEndpoint}${anotherCoordinatorPort}/v2/ping`);
 | |
|             expect(result.status).to.equal(200);
 | |
|             expect(await result.text()).to.equal('pong');
 | |
|         });
 | |
|     });
 | |
|     // fill handling is the same for all fill methods so we can test them all through the fillOrder and batchFillOrders interfaces
 | |
|     describe('#fillOrderAsync', () => {
 | |
|         it('should fill a valid order', async () => {
 | |
|             txHash = await coordinatorClient.fillOrderAsync(
 | |
|                 signedOrder,
 | |
|                 takerTokenFillAmount,
 | |
|                 signedOrder.signature,
 | |
|                 { from: takerAddress },
 | |
|                 { shouldValidate: true },
 | |
|             );
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
|     });
 | |
|     describe('#batchFillOrdersAsync', () => {
 | |
|         it('should fill a batch of valid orders', async () => {
 | |
|             const signedOrders = [signedOrder, anotherSignedOrder];
 | |
|             const takerAssetFillAmounts = Array(2).fill(takerTokenFillAmount);
 | |
|             txHash = await coordinatorClient.batchFillOrdersAsync(
 | |
|                 signedOrders,
 | |
|                 takerAssetFillAmounts,
 | |
|                 signedOrders.map(o => o.signature),
 | |
|                 { from: takerAddress, value: DEFAULT_PROTOCOL_FEE_MULTIPLIER.times(signedOrders.length) },
 | |
|             );
 | |
| 
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
|         it('should fill a batch of orders with different feeRecipientAddresses with the same coordinator server', async () => {
 | |
|             const signedOrders = [signedOrder, anotherSignedOrder, signedOrderWithDifferentFeeRecipient];
 | |
|             const takerAssetFillAmounts = Array(3).fill(takerTokenFillAmount);
 | |
|             txHash = await coordinatorClient.batchFillOrdersAsync(
 | |
|                 signedOrders,
 | |
|                 takerAssetFillAmounts,
 | |
|                 signedOrders.map(o => o.signature),
 | |
|                 { from: takerAddress, value: DEFAULT_PROTOCOL_FEE_MULTIPLIER.times(signedOrders.length) },
 | |
|             );
 | |
| 
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
|         // coordinator-server, as currently implemented, shares a singleton database connection across
 | |
|         // all instantiations. Making the request to two different mocked server endpoints still hits the
 | |
|         // same database and fails because of the uniqueness constraint on transactions in the database.
 | |
|         it.skip('should fill a batch of orders with different feeRecipientAddresses with different coordinator servers', async () => {
 | |
|             const signedOrders = [
 | |
|                 signedOrder,
 | |
|                 anotherSignedOrder,
 | |
|                 signedOrderWithDifferentFeeRecipient,
 | |
|                 signedOrderWithDifferentCoordinatorOperator,
 | |
|             ];
 | |
|             const takerAssetFillAmounts = Array(4).fill(takerTokenFillAmount);
 | |
|             txHash = await coordinatorClient.batchFillOrdersAsync(
 | |
|                 signedOrders,
 | |
|                 takerAssetFillAmounts,
 | |
|                 signedOrders.map(o => o.signature),
 | |
|                 { from: takerAddress },
 | |
|             );
 | |
| 
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
| 
 | |
|         it('should fill a batch of mixed coordinator and non-coordinator orders', async () => {
 | |
|             const nonCoordinatorOrder = await orderFactory.newSignedOrderAsync({
 | |
|                 senderAddress: constants.NULL_ADDRESS,
 | |
|             });
 | |
|             const signedOrders = [signedOrder, nonCoordinatorOrder];
 | |
|             const takerAssetFillAmounts = Array(2).fill(takerTokenFillAmount);
 | |
|             txHash = await coordinatorClient.batchFillOrdersAsync(
 | |
|                 signedOrders,
 | |
|                 takerAssetFillAmounts,
 | |
|                 signedOrders.map(o => o.signature),
 | |
|                 { from: takerAddress, value: DEFAULT_PROTOCOL_FEE_MULTIPLIER.multipliedBy(signedOrders.length) },
 | |
|             );
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
|     });
 | |
|     describe('#softCancelAsync, #batchSoftCancelAsync', () => {
 | |
|         it('should soft cancel a valid order', async () => {
 | |
|             const response = await coordinatorClient.softCancelAsync(signedOrder);
 | |
|             expect(response.outstandingFillSignatures).to.have.lengthOf(0);
 | |
|             expect(response.cancellationSignatures).to.have.lengthOf(1);
 | |
|         });
 | |
| 
 | |
|         it('should soft cancel a batch of valid orders', async () => {
 | |
|             const orders = [signedOrder, anotherSignedOrder];
 | |
|             const response = await coordinatorClient.batchSoftCancelAsync(orders);
 | |
|             expect(response).to.have.lengthOf(1);
 | |
|             expect(response[0].outstandingFillSignatures).to.have.lengthOf(0);
 | |
|             expect(response[0].cancellationSignatures).to.have.lengthOf(1);
 | |
|         });
 | |
|         it('should soft cancel a batch of orders with different feeRecipientAddresses', async () => {
 | |
|             const orders = [signedOrder, anotherSignedOrder, signedOrderWithDifferentFeeRecipient];
 | |
|             const response = await coordinatorClient.batchSoftCancelAsync(orders);
 | |
|             expect(response).to.have.lengthOf(1);
 | |
|             expect(response[0].outstandingFillSignatures).to.have.lengthOf(0);
 | |
|             expect(response[0].cancellationSignatures).to.have.lengthOf(2);
 | |
|         });
 | |
|         it('should soft cancel a batch of orders with different coordinatorOperator and concatenate responses', async () => {
 | |
|             const orders = [
 | |
|                 signedOrder,
 | |
|                 anotherSignedOrder,
 | |
|                 signedOrderWithDifferentFeeRecipient,
 | |
|                 signedOrderWithDifferentCoordinatorOperator,
 | |
|             ];
 | |
|             const response = await coordinatorClient.batchSoftCancelAsync(orders);
 | |
|             expect(response).to.have.lengthOf(2);
 | |
|             expect(response[0].outstandingFillSignatures).to.have.lengthOf(0);
 | |
|             expect(response[0].cancellationSignatures).to.have.lengthOf(3);
 | |
|             expect(response[1].cancellationSignatures).to.have.lengthOf(3); // both coordinator servers support the same feeRecipients
 | |
|         });
 | |
|     });
 | |
|     describe('#hardCancelOrderAsync, #batchHardCancelOrdersAsync, #cancelOrdersUpToAsync', () => {
 | |
|         it('should hard cancel a valid order', async () => {
 | |
|             txHash = await coordinatorClient.hardCancelOrderAsync(signedOrder, { from: makerAddress });
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
| 
 | |
|         it('should hard cancel a batch of valid orders', async () => {
 | |
|             const orders = [signedOrder, anotherSignedOrder];
 | |
|             txHash = await coordinatorClient.batchHardCancelOrdersAsync(orders, { from: makerAddress });
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
|         });
 | |
|         it('should hard cancel orders up to target order epoch', async () => {
 | |
|             const targetOrderEpoch = new BigNumber(42);
 | |
|             txHash = await coordinatorClient.hardCancelOrdersUpToAsync(targetOrderEpoch, { from: makerAddress });
 | |
| 
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
 | |
| 
 | |
|             const exchange = new ExchangeContract(contractAddresses.exchange, env.provider);
 | |
|             const orderEpoch = await exchange.orderEpoch(makerAddress, contractAddresses.coordinator).callAsync();
 | |
|             expect(orderEpoch).to.be.bignumber.equal(targetOrderEpoch.plus(1));
 | |
|         });
 | |
|     });
 | |
|     describe('coordinator edge cases', () => {
 | |
|         let badOrder: SignedOrder;
 | |
|         beforeEach('setup order with non-registered feeRecipient', async () => {
 | |
|             badOrder = await orderFactory.newSignedOrderAsync({
 | |
|                 feeRecipientAddress: feeRecipientAddressFour,
 | |
|             });
 | |
|         });
 | |
|         it('should throw error when feeRecipientAddress is not in registry', async () => {
 | |
|             await expect(
 | |
|                 coordinatorClient.fillOrderAsync(badOrder, takerTokenFillAmount, badOrder.signature, {
 | |
|                     from: takerAddress,
 | |
|                 }),
 | |
|             ).to.eventually.be.rejected();
 | |
|         });
 | |
|         it('should throw informative error when coordinator endpoint does not work', async () => {
 | |
|             await env.web3Wrapper.awaitTransactionSuccessAsync(
 | |
|                 await coordinatorRegistry.setCoordinatorEndpoint('http://badUri.com').sendTransactionAsync({
 | |
|                     from: feeRecipientAddressFour,
 | |
|                 }),
 | |
|                 constants.AWAIT_TRANSACTION_MINED_MS,
 | |
|             );
 | |
|             await expect(
 | |
|                 coordinatorClient.fillOrderAsync(badOrder, takerTokenFillAmount, badOrder.signature, {
 | |
|                     from: takerAddress,
 | |
|                 }),
 | |
|             ).to.eventually.be.rejectedWith(CoordinatorServerErrorMsg.FillFailed);
 | |
|         });
 | |
|     });
 | |
|     describe('coordinator server errors', () => {
 | |
|         serverValidationError = {
 | |
|             code: 1004,
 | |
|             reason: '',
 | |
|             validationErrors: [
 | |
|                 {
 | |
|                     field: 'signedTransaction',
 | |
|                     code: 1004,
 | |
|                     reason:
 | |
|                         'A transaction can only be approved once. To request approval to perform the same actions, generate and sign an identical transaction with a different salt value.',
 | |
|                 },
 | |
|             ],
 | |
|         };
 | |
|         beforeEach(async () => {
 | |
|             nock(`${coordinatorEndpoint}${coordinatorPort}`)
 | |
|                 .post('/v2/request_transaction', () => true)
 | |
|                 .query({
 | |
|                     chainId: 1337,
 | |
|                 })
 | |
|                 .reply(400, serverValidationError);
 | |
|         });
 | |
|         afterEach(async () => {
 | |
|             nock.cleanAll();
 | |
|         });
 | |
|         it('should throw error when softCancel fails', async () => {
 | |
|             await coordinatorClient
 | |
|                 .softCancelAsync(signedOrder)
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.CancellationFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
| 
 | |
|         it('should throw error when batch soft cancel totally fails with single coordinator operator', async () => {
 | |
|             const orders = [signedOrder, signedOrderWithDifferentFeeRecipient];
 | |
|             await coordinatorClient
 | |
|                 .batchSoftCancelAsync(orders)
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.CancellationFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal(orders);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
|         it('should throw consolidated error when batch soft cancel partially fails with different coordinator operators', async () => {
 | |
|             const serverCancellationSuccess = {
 | |
|                 outstandingFillSignatures: [
 | |
|                     {
 | |
|                         orderHash: '0xd1dc61f3e7e5f41d72beae7863487beea108971de678ca00d903756f842ef3ce',
 | |
|                         approvalSignatures: [
 | |
|                             '0x1c7383ca8ebd6de8b5b20b1c2d49bea166df7dfe4af1932c9c52ec07334e859cf2176901da35f4480ceb3ab63d8d0339d851c31929c40d88752689b9a8a535671303',
 | |
|                         ],
 | |
|                         expirationTimeSeconds: 1552390380,
 | |
|                         takerAssetFillAmount: 100000000000000000000,
 | |
|                     },
 | |
|                 ],
 | |
|                 cancellationSignatures: [
 | |
|                     '0x2ea3117a8ebd6de8b5b20b1c2d49bea166df7dfe4af1932c9c52ec07334e859cf2176901da35f4480ceb3ab63d8d0339d851c31929c40d88752689b9a855b5a7b401',
 | |
|                 ],
 | |
|             };
 | |
|             nock(`${coordinatorEndpoint}${anotherCoordinatorPort}`)
 | |
|                 .post('/v2/request_transaction', () => true)
 | |
|                 .query({
 | |
|                     chainId: 1337,
 | |
|                 })
 | |
|                 .reply(200, serverCancellationSuccess);
 | |
| 
 | |
|             const signedOrders = [signedOrder, signedOrderWithDifferentCoordinatorOperator];
 | |
|             await coordinatorClient
 | |
|                 .batchSoftCancelAsync(signedOrders)
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.CancellationFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.deep.equal([serverCancellationSuccess]);
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
|         it('should throw consolidated error when batch soft cancel totally fails with different coordinator operators', async () => {
 | |
|             nock(`${coordinatorEndpoint}${anotherCoordinatorPort}`)
 | |
|                 .post('/v2/request_transaction', () => true)
 | |
|                 .query({
 | |
|                     chainId: 1337,
 | |
|                 })
 | |
|                 .reply(400, serverValidationError);
 | |
| 
 | |
|             const signedOrders = [signedOrder, signedOrderWithDifferentCoordinatorOperator];
 | |
|             await coordinatorClient
 | |
|                 .batchSoftCancelAsync(signedOrders)
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.CancellationFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
| 
 | |
|                     const anotherErrorBody = err.errors[1];
 | |
|                     expect(anotherErrorBody.isError).to.be.true();
 | |
|                     expect(anotherErrorBody.status).to.equal(400);
 | |
|                     expect(anotherErrorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(anotherErrorBody.orders).to.deep.equal([signedOrderWithDifferentCoordinatorOperator]);
 | |
|                     expect(anotherErrorBody.coordinatorOperator).to.equal(
 | |
|                         `${coordinatorEndpoint}${anotherCoordinatorPort}`,
 | |
|                     );
 | |
|                 });
 | |
|         });
 | |
|         it('should throw error when a fill fails', async () => {
 | |
|             await coordinatorClient
 | |
|                 .fillOrderAsync(signedOrder, takerTokenFillAmount, signedOrder.signature, { from: takerAddress })
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.FillFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
|         it('should throw error when batch fill fails with single coordinator operator', async () => {
 | |
|             const signedOrders = [signedOrder, signedOrderWithDifferentFeeRecipient];
 | |
|             const takerAssetFillAmounts = [takerTokenFillAmount, takerTokenFillAmount, takerTokenFillAmount];
 | |
|             await coordinatorClient
 | |
|                 .batchFillOrdersAsync(
 | |
|                     signedOrders,
 | |
|                     takerAssetFillAmounts,
 | |
|                     signedOrders.map(o => o.signature),
 | |
|                     {
 | |
|                         from: takerAddress,
 | |
|                     },
 | |
|                 )
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.FillFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal(signedOrders);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
|         it('should throw consolidated error when batch fill partially fails with different coordinator operators', async () => {
 | |
|             const serverApprovalSuccess = {
 | |
|                 signatures: [
 | |
|                     '0x1cc07d7ae39679690a91418d46491520f058e4fb14debdf2e98f2376b3970de8512ace44af0be6d1c65617f7aae8c2364ff63f241515ee1559c3eeecb0f671d9e903',
 | |
|                 ],
 | |
|                 expirationTimeSeconds: 1552390014,
 | |
|             };
 | |
|             nock(`${coordinatorEndpoint}${anotherCoordinatorPort}`)
 | |
|                 .post('/v2/request_transaction', () => true)
 | |
|                 .query({
 | |
|                     chainId: 1337,
 | |
|                 })
 | |
|                 .reply(200, serverApprovalSuccess);
 | |
| 
 | |
|             const signedOrders = [signedOrder, signedOrderWithDifferentCoordinatorOperator];
 | |
|             const takerAssetFillAmounts = [takerTokenFillAmount, takerTokenFillAmount, takerTokenFillAmount];
 | |
|             await coordinatorClient
 | |
|                 .batchFillOrdersAsync(
 | |
|                     signedOrders,
 | |
|                     takerAssetFillAmounts,
 | |
|                     signedOrders.map(o => o.signature),
 | |
|                     {
 | |
|                         from: takerAddress,
 | |
|                     },
 | |
|                 )
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.FillFailed);
 | |
|                     expect(err.approvedOrders).to.deep.equal([signedOrderWithDifferentCoordinatorOperator]);
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
|                 });
 | |
|         });
 | |
|         it('should throw consolidated error when batch fill totally fails with different coordinator operators', async () => {
 | |
|             nock(`${coordinatorEndpoint}${anotherCoordinatorPort}`)
 | |
|                 .post('/v2/request_transaction', () => true)
 | |
|                 .query({
 | |
|                     chainId: 1337,
 | |
|                 })
 | |
|                 .reply(400, serverValidationError);
 | |
| 
 | |
|             const signedOrders = [signedOrder, signedOrderWithDifferentCoordinatorOperator];
 | |
|             const takerAssetFillAmounts = [takerTokenFillAmount, takerTokenFillAmount, takerTokenFillAmount];
 | |
|             await coordinatorClient
 | |
|                 .batchFillOrdersAsync(
 | |
|                     signedOrders,
 | |
|                     takerAssetFillAmounts,
 | |
|                     signedOrders.map(o => o.signature),
 | |
|                     {
 | |
|                         from: takerAddress,
 | |
|                     },
 | |
|                 )
 | |
|                 .then(res => {
 | |
|                     expect(res).to.be.undefined();
 | |
|                 })
 | |
|                 .catch(err => {
 | |
|                     expect(err.message).equal(CoordinatorServerErrorMsg.FillFailed);
 | |
|                     expect(err.approvedOrders).to.be.empty('array');
 | |
|                     expect(err.cancellations).to.be.empty('array');
 | |
| 
 | |
|                     const errorBody = err.errors[0];
 | |
|                     expect(errorBody.isError).to.be.true();
 | |
|                     expect(errorBody.status).to.equal(400);
 | |
|                     expect(errorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(errorBody.orders).to.deep.equal([signedOrder]);
 | |
|                     expect(errorBody.coordinatorOperator).to.equal(`${coordinatorEndpoint}${coordinatorPort}`);
 | |
| 
 | |
|                     const anotherErrorBody = err.errors[1];
 | |
|                     expect(anotherErrorBody.isError).to.be.true();
 | |
|                     expect(anotherErrorBody.status).to.equal(400);
 | |
|                     expect(anotherErrorBody.error).to.deep.equal(serverValidationError);
 | |
|                     expect(anotherErrorBody.orders).to.deep.equal([signedOrderWithDifferentCoordinatorOperator]);
 | |
|                     expect(anotherErrorBody.coordinatorOperator).to.equal(
 | |
|                         `${coordinatorEndpoint}${anotherCoordinatorPort}`,
 | |
|                     );
 | |
|                 });
 | |
|         });
 | |
|     });
 | |
| });
 | |
| // tslint:disable:max-file-line-count
 |