import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs'; import { blockchainTests, constants, describe, expect, hexRandom, LogDecoder, OrderFactory, testCombinatoriallyWithReferenceFunc, uint256Values, } from '@0x/contracts-test-utils'; import { ExchangeRevertErrors, LibMathRevertErrors, orderHashUtils } from '@0x/order-utils'; import { FillResults, MatchedFillResults, OrderWithoutDomain as Order, SignedOrder } from '@0x/types'; import { BigNumber, SafeMathRevertErrors } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts, ReferenceFunctions, TestExchangeInternalsContract, TestExchangeInternalsDispatchTransferFromCalledEventArgs, TestExchangeInternalsFillEventArgs, } from '../src'; blockchainTests.only('Exchange core internal functions', env => { const CHAIN_ID = 1337; const ONE_ETHER = constants.ONE_ETHER; const EMPTY_ORDER: Order = { senderAddress: constants.NULL_ADDRESS, makerAddress: constants.NULL_ADDRESS, takerAddress: constants.NULL_ADDRESS, makerFee: constants.ZERO_AMOUNT, takerFee: constants.ZERO_AMOUNT, makerAssetAmount: constants.ZERO_AMOUNT, takerAssetAmount: constants.ZERO_AMOUNT, makerAssetData: constants.NULL_BYTES, takerAssetData: constants.NULL_BYTES, makerFeeAssetData: constants.NULL_BYTES, takerFeeAssetData: constants.NULL_BYTES, salt: constants.ZERO_AMOUNT, feeRecipientAddress: constants.NULL_ADDRESS, expirationTimeSeconds: constants.ZERO_AMOUNT, }; const EMPTY_FILL_RESULTS: FillResults = { makerAssetFilledAmount: constants.ZERO_AMOUNT, takerAssetFilledAmount: constants.ZERO_AMOUNT, makerFeePaid: constants.ZERO_AMOUNT, takerFeePaid: constants.ZERO_AMOUNT, }; const EMPTY_MATCHED_FILL_RESULTS: MatchedFillResults = { left: EMPTY_FILL_RESULTS, right: EMPTY_FILL_RESULTS, profitInLeftMakerAsset: constants.ZERO_AMOUNT, profitInRightMakerAsset: constants.ZERO_AMOUNT, }; const COMMON_MATCHED_FILL_RESULTS = { left: { makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), }, right: { makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), }, profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), profitInRightMakerAsset: constants.ZERO_AMOUNT, }; const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); const randomHash = () => hexRandom(constants.WORD_LENGTH); const randomAssetData = () => hexRandom(36); let testExchange: TestExchangeInternalsContract; let logDecoder: LogDecoder; let senderAddress: string; let makerAddressLeft: string; let makerAddressRight: string; let orderFactoryLeft: OrderFactory; let orderFactoryRight: OrderFactory; function createMatchedFillResultsFromFilled( leftMakerAssetFilledAmount: BigNumber, leftTakerAssetFilledAmount: BigNumber, rightMakerAssetFilledAmount: BigNumber, rightTakerAssetFilledAmount: BigNumber, ): MatchedFillResults { return { ...EMPTY_MATCHED_FILL_RESULTS, left: { ...EMPTY_FILL_RESULTS, makerAssetFilledAmount: leftMakerAssetFilledAmount, takerAssetFilledAmount: leftTakerAssetFilledAmount, }, right: { ...EMPTY_FILL_RESULTS, makerAssetFilledAmount: rightMakerAssetFilledAmount, takerAssetFilledAmount: rightTakerAssetFilledAmount, }, }; } function createMatchedFillResults( leftMakerAssetFilledAmount: BigNumber, leftTakerAssetFilledAmount: BigNumber, leftMakerFeePaid: BigNumber, leftTakerFeePaid: BigNumber, rightMakerAssetFilledAmount: BigNumber, rightTakerAssetFilledAmount: BigNumber, rightMakerFeePaid: BigNumber, rightTakerFeePaid: BigNumber, profitInLeftMakerAsset?: BigNumber, profitInRightMakerAsset?: BigNumber, ): MatchedFillResults { return { left: { makerAssetFilledAmount: leftMakerAssetFilledAmount, takerAssetFilledAmount: leftTakerAssetFilledAmount, makerFeePaid: leftMakerFeePaid, takerFeePaid: leftTakerFeePaid, }, right: { makerAssetFilledAmount: rightMakerAssetFilledAmount, takerAssetFilledAmount: rightTakerAssetFilledAmount, makerFeePaid: rightMakerFeePaid, takerFeePaid: rightTakerFeePaid, }, profitInLeftMakerAsset: profitInLeftMakerAsset || constants.ZERO_AMOUNT, profitInRightMakerAsset: profitInRightMakerAsset || constants.ZERO_AMOUNT, }; } before(async () => { const accounts = await env.getAccountAddressesAsync(); senderAddress = accounts[0]; makerAddressLeft = accounts[1]; makerAddressRight = accounts[2]; testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, env.provider, env.txDefaults, new BigNumber(CHAIN_ID), ); logDecoder = new LogDecoder(env.web3Wrapper, artifacts); const domain = { verifyingContractAddress: testExchange.address, chainId: 1337, // The chain id for the isolated exchange }; const leftMakerAssetData = randomAssetData(); const leftTakerAssetData = randomAssetData(); // Create default order parameters const defaultOrderParamsLeft = { ...constants.STATIC_ORDER_PARAMS, makerAddress: makerAddressLeft, makerAssetData: leftMakerAssetData, takerAssetData: leftTakerAssetData, makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), feeRecipientAddress: randomAddress(), domain, }; const defaultOrderParamsRight = { ...constants.STATIC_ORDER_PARAMS, makerAddress: makerAddressRight, makerAssetData: leftTakerAssetData, takerAssetData: leftMakerAssetData, makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), feeRecipientAddress: randomAddress(), domain, }; const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); }); blockchainTests('assertValidMatch', () => { it('should revert if the prices of the left order is less than the price of the right order', async () => { const leftOrder = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const rightOrder = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), }); const orderHashHexLeft = orderHashUtils.getOrderHashHex(leftOrder); const orderHashHexRight = orderHashUtils.getOrderHashHex(rightOrder); const expectedError = new ExchangeRevertErrors.NegativeSpreadError(orderHashHexLeft, orderHashHexRight); return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.revertWith(expectedError); }); it('should succeed if the prices of the left and right orders are equal', async () => { const leftOrder = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const rightOrder = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), }); return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.be.fulfilled(''); }); it('should succeed if the price of the left order is higher than the price of the right', async () => { const leftOrder = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const rightOrder = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), }); return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.be.fulfilled(''); }); }); blockchainTests('calculateCompleteFillBoth', () => { /** * Asserts that the actual result of calling `calculateCompleteFillBoth()` is as expected. */ async function assertCalculateCompleteFillBothAsync( expectedMatchedFillResults: MatchedFillResults, leftMakerAssetAmountRemaining: BigNumber, leftTakerAssetAmountRemaining: BigNumber, rightMakerAssetAmountRemaining: BigNumber, rightTakerAssetAmountRemaining: BigNumber, ): Promise { const actualMatchedFillResults = await testExchange.calculateCompleteFillBoth.callAsync( leftMakerAssetAmountRemaining, leftTakerAssetAmountRemaining, rightMakerAssetAmountRemaining, rightTakerAssetAmountRemaining, ); expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); } it('should assign everything to zero if all inputs are zero', async () => { await assertCalculateCompleteFillBothAsync( EMPTY_MATCHED_FILL_RESULTS, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); // Note: This call would never be able to be made through our contracts, since these orders will not actually be // fully filled. This is just a test case to make sure. it('should correctly update the fillResults with nonzero input', async () => { const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(17, 0), Web3Wrapper.toBaseUnitAmount(98, 0), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(13, 0), ); await assertCalculateCompleteFillBothAsync( expectedMatchedFillResults, new BigNumber(17), new BigNumber(98), new BigNumber(75), new BigNumber(13), ); }); it('should correctly update the fillResults with nonzero input', async () => { const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(5, 0), Web3Wrapper.toBaseUnitAmount(10, 0), Web3Wrapper.toBaseUnitAmount(10, 0), Web3Wrapper.toBaseUnitAmount(5, 0), ); await assertCalculateCompleteFillBothAsync( expectedMatchedFillResults, new BigNumber(5), new BigNumber(10), new BigNumber(10), new BigNumber(5), ); }); it('should correctly update the fillResults with nonzero input', async () => { const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(5, 18), ); await assertCalculateCompleteFillBothAsync( expectedMatchedFillResults, Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(5, 18), ); }); it('should correctly update the fillResults with nonzero input', async () => { const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), ); await assertCalculateCompleteFillBothAsync( expectedMatchedFillResults, Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), ); }); }); blockchainTests('calculateCompleteRightFill', () => { /** * Asserts that the results of calling `calculateCompleteRightFill()` are consistent with the expected results. */ async function assertCalculateCompleteRightFillAsync( expectedMatchedFillResults: MatchedFillResults, leftOrder: SignedOrder, rightMakerAssetAmountRemaining: BigNumber, rightTakerAssetAmountRemaining: BigNumber, ): Promise { const actualMatchedFillResults = await testExchange.calculateCompleteRightFill.callAsync( leftOrder, rightMakerAssetAmountRemaining, rightTakerAssetAmountRemaining, ); expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); } it('should correctly calculate the complete right fill', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), }); const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(13, 0), ); await assertCalculateCompleteRightFillAsync( expectedMatchedFillResults, signedOrderLeft, Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(13, 0), ); }); it('should correctly calculate the complete right fill', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), }); const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(11, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), ); await assertCalculateCompleteRightFillAsync( expectedMatchedFillResults, signedOrderLeft, Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), ); }); it('should correctly calculate the complete right fill', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const expectedMatchedFillResults = createMatchedFillResultsFromFilled( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), ); await assertCalculateCompleteRightFillAsync( expectedMatchedFillResults, signedOrderLeft, Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), ); }); }); blockchainTests('calculateFillResults', () => { describe.optional('combinatorial tests', () => { function makeOrder( makerAssetAmount: BigNumber, takerAssetAmount: BigNumber, makerFee: BigNumber, takerFee: BigNumber, ): Order { return { ...EMPTY_ORDER, makerAssetAmount, takerAssetAmount, makerFee, takerFee, }; } async function referenceCalculateFillResultsAsync( orderTakerAssetAmount: BigNumber, takerAssetFilledAmount: BigNumber, otherAmount: BigNumber, ): Promise { // Note(albrow): Here we are re-using the same value (otherAmount) // for order.makerAssetAmount, order.makerFee, and order.takerFee. // This should be safe because they are never used with each other // in any mathematical operation in either the reference TypeScript // implementation or the Solidity implementation of // calculateFillResults. return ReferenceFunctions.calculateFillResults( makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount), takerAssetFilledAmount, ); } async function testCalculateFillResultsAsync( orderTakerAssetAmount: BigNumber, takerAssetFilledAmount: BigNumber, otherAmount: BigNumber, ): Promise { const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); return testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount); } testCombinatoriallyWithReferenceFunc( 'calculateFillResults', referenceCalculateFillResultsAsync, testCalculateFillResultsAsync, [uint256Values, uint256Values, uint256Values], ); }); describe('explicit tests', () => { const MAX_UINT256_ROOT = constants.MAX_UINT256_ROOT; function makeOrder(details?: Partial): Order { return _.assign({}, EMPTY_ORDER, details); } it('matches the output of the reference function', async () => { const order = makeOrder({ makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER.times(2), makerFee: ONE_ETHER.times(0.0023), takerFee: ONE_ETHER.times(0.0025), }); const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3); const expected = ReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount); const actual = await testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount); expect(actual).to.deep.eq(expected); }); it('reverts if computing `fillResults.makerAssetFilledAmount` overflows', async () => { // All values need to be large to ensure we don't trigger a RoundingError. const order = makeOrder({ makerAssetAmount: MAX_UINT256_ROOT.times(2), takerAssetAmount: MAX_UINT256_ROOT, }); const takerAssetFilledAmount = MAX_UINT256_ROOT; const expectedError = new SafeMathRevertErrors.SafeMathError( SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, takerAssetFilledAmount, order.makerAssetAmount, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if computing `fillResults.makerFeePaid` overflows', async () => { // All values need to be large to ensure we don't trigger a RoundingError. const order = makeOrder({ makerAssetAmount: MAX_UINT256_ROOT, takerAssetAmount: MAX_UINT256_ROOT, makerFee: MAX_UINT256_ROOT.times(11), }); const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount, ); const expectedError = new SafeMathRevertErrors.SafeMathError( SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, makerAssetFilledAmount, order.makerFee, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if computing `fillResults.takerFeePaid` overflows', async () => { // All values need to be large to ensure we don't trigger a RoundingError. const order = makeOrder({ makerAssetAmount: MAX_UINT256_ROOT, takerAssetAmount: MAX_UINT256_ROOT, takerFee: MAX_UINT256_ROOT.times(11), }); const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); const expectedError = new SafeMathRevertErrors.SafeMathError( SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, takerAssetFilledAmount, order.takerFee, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if `order.makerAssetAmount` is 0', async () => { const order = makeOrder({ makerAssetAmount: constants.ZERO_AMOUNT, takerAssetAmount: ONE_ETHER, }); const takerAssetFilledAmount = ONE_ETHER; const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if `order.takerAssetAmount` is 0', async () => { const order = makeOrder({ makerAssetAmount: ONE_ETHER, takerAssetAmount: constants.ZERO_AMOUNT, }); const takerAssetFilledAmount = ONE_ETHER; const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', async () => { const order = makeOrder({ makerAssetAmount: new BigNumber(100), takerAssetAmount: ONE_ETHER, }); const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); const expectedError = new LibMathRevertErrors.RoundingError( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if there is a rounding error computing `makerFeePaid`', async () => { const order = makeOrder({ makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER, makerFee: new BigNumber(100), }); const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount, ); const expectedError = new LibMathRevertErrors.RoundingError( makerAssetFilledAmount, order.makerAssetAmount, order.makerFee, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); it('reverts if there is a rounding error computing `takerFeePaid`', async () => { const order = makeOrder({ makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER, takerFee: new BigNumber(100), }); const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount, ); const expectedError = new LibMathRevertErrors.RoundingError( makerAssetFilledAmount, order.makerAssetAmount, order.takerFee, ); return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( expectedError, ); }); }); }); blockchainTests('calculateMatchedFillResults', async () => { /** * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. */ async function assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults: MatchedFillResults, leftOrder: SignedOrder, rightOrder: SignedOrder, leftOrderTakerAssetFilledAmount: BigNumber, rightOrderTakerAssetFilledAmount: BigNumber, from?: string, ): Promise { const actualMatchedFillResults = await testExchange.calculateMatchedFillResults.callAsync( leftOrder, rightOrder, leftOrderTakerAssetFilledAmount, rightOrderTakerAssetFilledAmount, false, { from }, ); expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); } it('should correctly calculate the results when only the right order is fully filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should correctly calculate the results when only the left order is fully filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(14, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(15, 0), Web3Wrapper.toBaseUnitAmount(90, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(90, 0), Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.85% Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% Web3Wrapper.toBaseUnitAmount(2, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should give right maker a better price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% Web3Wrapper.toBaseUnitAmount(3, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should give left maker a better sell price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(11, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should give right maker and right taker a favorable fee price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(2650, 0), Web3Wrapper.toBaseUnitAmount(2653, 0), Web3Wrapper.toBaseUnitAmount(3, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should give left maker and left taker a favorable fee price when rounding', async () => { // Create orders to match const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(11, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(9166, 0), Web3Wrapper.toBaseUnitAmount(9175, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(1000, 0), Web3Wrapper.toBaseUnitAmount(1005, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(1005, 0), Web3Wrapper.toBaseUnitAmount(503, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% Web3Wrapper.toBaseUnitAmount(497, 0), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when orders completely fill each other', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(3, 18), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(20, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), Web3Wrapper.toBaseUnitAmount(50, 16), Web3Wrapper.toBaseUnitAmount(50, 16), Web3Wrapper.toBaseUnitAmount(3, 18), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 16), Web3Wrapper.toBaseUnitAmount(10, 16), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(3, 18), ); await assertCalculateMatchedFillResultsAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { const feeRecipientAddress = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if taker == leftMaker', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, signedOrderLeft.makerAddress, ); }); it('should transfer the correct amounts if taker == leftMaker', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, signedOrderRight.makerAddress, ); }); it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { const feeRecipientAddressLeft = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, feeRecipientAddressLeft, ); }); it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { const feeRecipientAddressRight = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress: feeRecipientAddressRight, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, feeRecipientAddressRight, ); }); it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress: makerAddressLeft, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), makerFeeAssetData: signedOrderRight.makerAssetData, feeRecipientAddress: makerAddressLeft, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), makerFeeAssetData: signedOrderLeft.makerAssetData, feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress: makerAddressLeft, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), makerFeeAssetData: signedOrderLeft.makerAssetData, feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); }); blockchainTests('calculateMatchedFillResultsWithMaximalFill', async () => { /** * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. */ async function assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults: MatchedFillResults, leftOrder: SignedOrder, rightOrder: SignedOrder, leftOrderTakerAssetFilledAmount: BigNumber, rightOrderTakerAssetFilledAmount: BigNumber, from?: string, ): Promise { const actualMatchedFillResults = await testExchange.calculateMatchedFillResults.callAsync( leftOrder, rightOrder, leftOrderTakerAssetFilledAmount, rightOrderTakerAssetFilledAmount, true, { from }, ); expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); } it('should transfer correct amounts when right order is fully filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% Web3Wrapper.toBaseUnitAmount(75, 0), Web3Wrapper.toBaseUnitAmount(13, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should transfer correct amounts when left order is fully filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(196, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(28, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(15, 0), Web3Wrapper.toBaseUnitAmount(90, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(105, 0), Web3Wrapper.toBaseUnitAmount(15, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(15, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should transfer correct amounts when left order is fully filled', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(29, 0), Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(7, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should fully fill both orders and pay out profit in both maker assets', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(7, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(8, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(6, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(7, 0), Web3Wrapper.toBaseUnitAmount(4, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(8, 0), Web3Wrapper.toBaseUnitAmount(6, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(1, 0), Web3Wrapper.toBaseUnitAmount(4, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should give left maker a better sell price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(11, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should give right maker and right taker a favorable fee price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(22, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(29, 0), Web3Wrapper.toBaseUnitAmount(16, 0), Web3Wrapper.toBaseUnitAmount(3333, 0), Web3Wrapper.toBaseUnitAmount(3333, 0), constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(7, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('Should give left maker and left taker a favorable fee price when rounding', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(11, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(9166, 0), Web3Wrapper.toBaseUnitAmount(9175, 0), Web3Wrapper.toBaseUnitAmount(89, 0), Web3Wrapper.toBaseUnitAmount(1, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const expectedMatchedFillResults = { ...COMMON_MATCHED_FILL_RESULTS, left: { ...COMMON_MATCHED_FILL_RESULTS.left, makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), }, }; await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); const signedOrderRight2 = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), }); const expectedMatchedFillResults2 = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(45, 18), Web3Wrapper.toBaseUnitAmount(90, 18), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(90, 18), Web3Wrapper.toBaseUnitAmount(45, 18), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(90, 16), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults2, signedOrderLeft, signedOrderRight2, Web3Wrapper.toBaseUnitAmount(10, 18), constants.ZERO_AMOUNT, ); }); it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAddress: makerAddressLeft, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAddress: makerAddressRight, makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(1000, 0), Web3Wrapper.toBaseUnitAmount(1005, 0), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(2000, 0), Web3Wrapper.toBaseUnitAmount(1000, 0), Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(995, 0), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), }); const expectedMatchedFillResults = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(2, 18), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(100, 16), Web3Wrapper.toBaseUnitAmount(5, 18), Web3Wrapper.toBaseUnitAmount(10, 18), Web3Wrapper.toBaseUnitAmount(10, 16), Web3Wrapper.toBaseUnitAmount(10, 16), constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(3, 18), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); // Create second left order // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" // branch in the contract twice for this test. const signedOrderLeft2 = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), }); const expectedMatchedFillResults2 = createMatchedFillResults( Web3Wrapper.toBaseUnitAmount(90, 18), Web3Wrapper.toBaseUnitAmount(45, 18), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(45, 18), Web3Wrapper.toBaseUnitAmount(90, 18), Web3Wrapper.toBaseUnitAmount(90, 16), Web3Wrapper.toBaseUnitAmount(90, 16), ); await assertCalculateMatchedFillResultsWithMaximalFillAsync( expectedMatchedFillResults2, signedOrderLeft2, signedOrderRight, constants.ZERO_AMOUNT, Web3Wrapper.toBaseUnitAmount(10, 18), ); }); it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { const feeRecipientAddress = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if taker == leftMaker', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, signedOrderLeft.makerAddress, ); }); it('should transfer the correct amounts if taker == rightMaker', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, signedOrderRight.makerAddress, ); }); it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { const feeRecipientAddress = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, feeRecipientAddress, ); }); it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { const feeRecipientAddress = randomAddress(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, feeRecipientAddress, ); }); it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), feeRecipientAddress: makerAddressLeft, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), }); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), makerFeeAssetData: signedOrderRight.makerAssetData, feeRecipientAddress: makerAddressLeft, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), makerFeeAssetData: signedOrderLeft.makerAssetData, feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { const makerFeeAssetData = randomAssetData(); const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), makerFeeAssetData, feeRecipientAddress: makerAddressLeft, }); const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), makerFeeAssetData: signedOrderLeft.makerAssetData, feeRecipientAddress: makerAddressRight, }); await assertCalculateMatchedFillResultsWithMaximalFillAsync( COMMON_MATCHED_FILL_RESULTS, signedOrderLeft, signedOrderRight, constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, ); }); }); blockchainTests.resets('updateFilledState', async () => { const ORDER_DEFAULTS = { senderAddress: randomAddress(), makerAddress: randomAddress(), takerAddress: randomAddress(), makerFee: ONE_ETHER.times(0.001), takerFee: ONE_ETHER.times(0.003), makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER.times(0.5), makerAssetData: randomAssetData(), takerAssetData: randomAssetData(), makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), salt: new BigNumber(_.random(0, 1e8)), feeRecipientAddress: randomAddress(), expirationTimeSeconds: new BigNumber(_.random(0, 1e8)), }; function makeOrder(details?: Partial): Order { return _.assign({}, ORDER_DEFAULTS, details); } async function testUpdateFilledStateAsync( order: Order, orderTakerAssetFilledAmount: BigNumber, takerAddress: string, takerAssetFillAmount: BigNumber, ): Promise { const orderHash = randomHash(); const fillResults = ReferenceFunctions.calculateFillResults(order, takerAssetFillAmount); const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount); // CAll `testUpdateFilledState()`, which will set the `filled` // state for this order to `orderTakerAssetFilledAmount` before // calling `_updateFilledState()`. const receipt = await logDecoder.getTxWithDecodedLogsAsync( await testExchange.testUpdateFilledState.sendTransactionAsync( order, takerAddress, orderHash, orderTakerAssetFilledAmount, fillResults, ), ); // Grab the new `filled` state for this order. const actualFilledState = await testExchange.filled.callAsync(orderHash); // Assert the `filled` state for this order. expect(actualFilledState).to.bignumber.eq(expectedFilledState); // Assert the logs. // tslint:disable-next-line: no-unnecessary-type-assertion const fillEvent = receipt.logs[0] as LogWithDecodedArgs; expect(fillEvent.event).to.eq('Fill'); expect(fillEvent.args.makerAddress).to.eq(order.makerAddress); expect(fillEvent.args.feeRecipientAddress).to.eq(order.feeRecipientAddress); expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData); expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData); expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData); expect(fillEvent.args.takerFeeAssetData).to.eq(order.takerFeeAssetData); expect(fillEvent.args.makerAssetFilledAmount).to.bignumber.eq(fillResults.makerAssetFilledAmount); expect(fillEvent.args.takerAssetFilledAmount).to.bignumber.eq(fillResults.takerAssetFilledAmount); expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid); expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid); expect(fillEvent.args.takerAddress).to.eq(takerAddress); expect(fillEvent.args.senderAddress).to.eq(senderAddress); expect(fillEvent.args.orderHash).to.eq(orderHash); } it('emits a `Fill` event and updates `filled` state correctly', async () => { const order = makeOrder(); return testUpdateFilledStateAsync( order, order.takerAssetAmount.times(0.1), randomAddress(), order.takerAssetAmount.times(0.25), ); }); it('reverts if `leftOrderTakerAssetFilledAmount + fillResults.takerAssetFilledAmount` overflows', async () => { const order = makeOrder(); const orderTakerAssetFilledAmount = constants.MAX_UINT256.dividedToIntegerBy(2); const takerAssetFillAmount = constants.MAX_UINT256.dividedToIntegerBy(2).plus(2); const fillResults = { makerAssetFilledAmount: constants.ZERO_AMOUNT, takerAssetFilledAmount: takerAssetFillAmount, makerFeePaid: constants.ZERO_AMOUNT, takerFeePaid: constants.ZERO_AMOUNT, }; const expectedError = new SafeMathRevertErrors.SafeMathError( SafeMathRevertErrors.SafeMathErrorCodes.Uint256AdditionOverflow, orderTakerAssetFilledAmount, takerAssetFillAmount, ); return expect( testExchange.testUpdateFilledState.awaitTransactionSuccessAsync( order, randomAddress(), randomHash(), orderTakerAssetFilledAmount, fillResults, ), ).to.revertWith(expectedError); }); }); blockchainTests('settleOrder', () => { const DEFAULT_ORDER = { senderAddress: randomAddress(), makerAddress: randomAddress(), takerAddress: randomAddress(), makerFee: ONE_ETHER.times(0.001), takerFee: ONE_ETHER.times(0.003), makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER.times(0.5), makerAssetData: randomAssetData(), takerAssetData: randomAssetData(), makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), salt: new BigNumber(_.random(0, 1e8)), feeRecipientAddress: randomAddress(), expirationTimeSeconds: new BigNumber(_.random(0, 1e8)), }; it('calls `_dispatchTransferFrom()` in the right order with the correct arguments', async () => { const order = DEFAULT_ORDER; const orderHash = randomHash(); const takerAddress = randomAddress(); const fillResults = { makerAssetFilledAmount: ONE_ETHER.times(2), takerAssetFilledAmount: ONE_ETHER.times(10), makerFeePaid: ONE_ETHER.times(0.01), takerFeePaid: ONE_ETHER.times(0.025), }; const receipt = await logDecoder.getTxWithDecodedLogsAsync( await testExchange.settleOrder.sendTransactionAsync(orderHash, order, takerAddress, fillResults), ); const logs = receipt.logs as Array< LogWithDecodedArgs >; expect(logs.length === 4); expect(_.every(logs, log => log.event === 'DispatchTransferFromCalled')).to.be.true(); // taker -> maker expect(logs[0].args.orderHash).to.eq(orderHash); expect(logs[0].args.assetData).to.eq(order.takerAssetData); expect(logs[0].args.from).to.eq(takerAddress); expect(logs[0].args.to).to.eq(order.makerAddress); expect(logs[0].args.amount).to.bignumber.eq(fillResults.takerAssetFilledAmount); // maker -> taker expect(logs[1].args.orderHash).to.eq(orderHash); expect(logs[1].args.assetData).to.eq(order.makerAssetData); expect(logs[1].args.from).to.eq(order.makerAddress); expect(logs[1].args.to).to.eq(takerAddress); expect(logs[1].args.amount).to.bignumber.eq(fillResults.makerAssetFilledAmount); // taker fee -> feeRecipient expect(logs[2].args.orderHash).to.eq(orderHash); expect(logs[2].args.assetData).to.eq(order.takerFeeAssetData); expect(logs[2].args.from).to.eq(takerAddress); expect(logs[2].args.to).to.eq(order.feeRecipientAddress); expect(logs[2].args.amount).to.bignumber.eq(fillResults.takerFeePaid); // maker fee -> feeRecipient expect(logs[3].args.orderHash).to.eq(orderHash); expect(logs[3].args.assetData).to.eq(order.makerFeeAssetData); expect(logs[3].args.from).to.eq(order.makerAddress); expect(logs[3].args.to).to.eq(order.feeRecipientAddress); expect(logs[3].args.amount).to.bignumber.eq(fillResults.makerFeePaid); }); }); blockchainTests('settleMatchOrders', () => { const getLeftOrder = () => { return { senderAddress: randomAddress(), makerAddress: randomAddress(), takerAddress: randomAddress(), makerFee: ONE_ETHER.times(0.001), takerFee: ONE_ETHER.times(0.003), makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER.times(0.5), makerAssetData: randomAssetData(), takerAssetData: randomAssetData(), makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), salt: new BigNumber(_.random(0, 1e8)), feeRecipientAddress: randomAddress(), expirationTimeSeconds: new BigNumber(_.random(0, 1e8)), }; }; const getRightOrder = () => { return { senderAddress: randomAddress(), makerAddress: randomAddress(), takerAddress: randomAddress(), makerFee: ONE_ETHER.times(0.002), takerFee: ONE_ETHER.times(0.004), makerAssetAmount: ONE_ETHER, takerAssetAmount: ONE_ETHER.times(0.6), makerAssetData: randomAssetData(), takerAssetData: randomAssetData(), makerFeeAssetData: randomAssetData(), takerFeeAssetData: randomAssetData(), salt: new BigNumber(_.random(0, 1e8)), feeRecipientAddress: randomAddress(), expirationTimeSeconds: new BigNumber(_.random(0, 1e8)), }; }; it('calls `_dispatchTransferFrom()` from in the right order when the fee recipients and taker fee asset data are the same', async () => { const leftOrder = getLeftOrder(); const rightOrder = getRightOrder(); const leftOrderHash = randomHash(); const rightOrderHash = randomHash(); const takerAddress = randomAddress(); const matchedFillResults = { left: { makerAssetFilledAmount: ONE_ETHER.times(2), takerAssetFilledAmount: ONE_ETHER.times(10), makerFeePaid: ONE_ETHER.times(0.01), takerFeePaid: ONE_ETHER.times(0.025), }, right: { takerAssetFilledAmount: ONE_ETHER.times(20), makerAssetFilledAmount: ONE_ETHER.times(4), makerFeePaid: ONE_ETHER.times(0.02), takerFeePaid: ONE_ETHER.times(0.05), }, profitInLeftMakerAsset: ONE_ETHER, profitInRightMakerAsset: ONE_ETHER.times(2), }; // Set the fee recipient addresses and the taker fee asset data fields to be the same rightOrder.feeRecipientAddress = leftOrder.feeRecipientAddress; rightOrder.takerFeeAssetData = leftOrder.takerFeeAssetData; // Call settleMatchOrders and collect the logs const receipt = await logDecoder.getTxWithDecodedLogsAsync( await testExchange.settleMatchOrders.sendTransactionAsync( leftOrderHash, rightOrderHash, leftOrder, rightOrder, takerAddress, matchedFillResults, ), ); const logs = receipt.logs as Array< LogWithDecodedArgs >; // Ensure that the logs have the correct lengths and names expect(logs.length).to.be.eq(7); expect(_.every(logs, log => log.event === 'DispatchTransferFromCalled')).to.be.true(); // Right maker asset -> left maker expect(logs[0].args.orderHash).to.be.eq(rightOrderHash); expect(logs[0].args.assetData).to.be.eq(rightOrder.makerAssetData); expect(logs[0].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[0].args.to).to.be.eq(leftOrder.makerAddress); expect(logs[0].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerAssetFilledAmount); // Left maker asset -> right maker expect(logs[1].args.orderHash).to.be.eq(leftOrderHash); expect(logs[1].args.assetData).to.be.eq(leftOrder.makerAssetData); expect(logs[1].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[1].args.to).to.be.eq(rightOrder.makerAddress); expect(logs[1].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerAssetFilledAmount); // Right maker fee -> right fee recipient expect(logs[2].args.orderHash).to.be.eq(rightOrderHash); expect(logs[2].args.assetData).to.be.eq(rightOrder.makerFeeAssetData); expect(logs[2].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[2].args.to).to.be.eq(rightOrder.feeRecipientAddress); expect(logs[2].args.amount).bignumber.to.be.eq(matchedFillResults.right.makerFeePaid); // Left maker fee -> left fee recipient expect(logs[3].args.orderHash).to.be.eq(leftOrderHash); expect(logs[3].args.assetData).to.be.eq(leftOrder.makerFeeAssetData); expect(logs[3].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[3].args.to).to.be.eq(leftOrder.feeRecipientAddress); expect(logs[3].args.amount).bignumber.to.be.eq(matchedFillResults.left.makerFeePaid); // Left maker -> taker profit expect(logs[4].args.orderHash).to.be.eq(leftOrderHash); expect(logs[4].args.assetData).to.be.eq(leftOrder.makerAssetData); expect(logs[4].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[4].args.to).to.be.eq(takerAddress); expect(logs[4].args.amount).bignumber.to.be.eq(matchedFillResults.profitInLeftMakerAsset); // right maker -> taker profit expect(logs[5].args.orderHash).to.be.eq(rightOrderHash); expect(logs[5].args.assetData).to.be.eq(rightOrder.makerAssetData); expect(logs[5].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[5].args.to).to.be.eq(takerAddress); expect(logs[5].args.amount).bignumber.to.be.eq(matchedFillResults.profitInRightMakerAsset); // taker fees -> fee recipient expect(logs[6].args.orderHash).to.be.eq(leftOrderHash); expect(logs[6].args.assetData).to.be.eq(leftOrder.takerFeeAssetData); expect(logs[6].args.from).to.be.eq(takerAddress); expect(logs[6].args.to).to.be.eq(leftOrder.feeRecipientAddress); expect(logs[6].args.amount).bignumber.to.be.eq(ONE_ETHER.times(0.075)); }); it('calls `_dispatchTransferFrom()` from in the right order when the fee recipients and taker fee asset data are not the same', async () => { const leftOrder = getLeftOrder(); const rightOrder = getRightOrder(); const leftOrderHash = randomHash(); const rightOrderHash = randomHash(); const takerAddress = randomAddress(); const matchedFillResults = { left: { makerAssetFilledAmount: ONE_ETHER.times(2), takerAssetFilledAmount: ONE_ETHER.times(10), makerFeePaid: ONE_ETHER.times(0.01), takerFeePaid: ONE_ETHER.times(0.025), }, right: { takerAssetFilledAmount: ONE_ETHER.times(20), makerAssetFilledAmount: ONE_ETHER.times(4), makerFeePaid: ONE_ETHER.times(0.02), takerFeePaid: ONE_ETHER.times(0.05), }, profitInLeftMakerAsset: ONE_ETHER, profitInRightMakerAsset: ONE_ETHER.times(2), }; // Call settleMatchOrders and collect the logs const receipt = await logDecoder.getTxWithDecodedLogsAsync( await testExchange.settleMatchOrders.sendTransactionAsync( leftOrderHash, rightOrderHash, leftOrder, rightOrder, takerAddress, matchedFillResults, ), ); const logs = receipt.logs as Array< LogWithDecodedArgs >; // Ensure that the logs have the correct lengths and names expect(logs.length).to.be.eq(8); expect(_.every(logs, log => log.event === 'DispatchTransferFromCalled')).to.be.true(); // Right maker asset -> left maker expect(logs[0].args.orderHash).to.be.eq(rightOrderHash); expect(logs[0].args.assetData).to.be.eq(rightOrder.makerAssetData); expect(logs[0].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[0].args.to).to.be.eq(leftOrder.makerAddress); expect(logs[0].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerAssetFilledAmount); // Left maker asset -> right maker expect(logs[1].args.orderHash).to.be.eq(leftOrderHash); expect(logs[1].args.assetData).to.be.eq(leftOrder.makerAssetData); expect(logs[1].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[1].args.to).to.be.eq(rightOrder.makerAddress); expect(logs[1].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerAssetFilledAmount); // Right maker fee -> right fee recipient expect(logs[2].args.orderHash).to.be.eq(rightOrderHash); expect(logs[2].args.assetData).to.be.eq(rightOrder.makerFeeAssetData); expect(logs[2].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[2].args.to).to.be.eq(rightOrder.feeRecipientAddress); expect(logs[2].args.amount).bignumber.to.be.eq(matchedFillResults.right.makerFeePaid); // Left maker fee -> left fee recipient expect(logs[3].args.orderHash).to.be.eq(leftOrderHash); expect(logs[3].args.assetData).to.be.eq(leftOrder.makerFeeAssetData); expect(logs[3].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[3].args.to).to.be.eq(leftOrder.feeRecipientAddress); expect(logs[3].args.amount).bignumber.to.be.eq(matchedFillResults.left.makerFeePaid); // Left maker -> taker profit expect(logs[4].args.orderHash).to.be.eq(leftOrderHash); expect(logs[4].args.assetData).to.be.eq(leftOrder.makerAssetData); expect(logs[4].args.from).to.be.eq(leftOrder.makerAddress); expect(logs[4].args.to).to.be.eq(takerAddress); expect(logs[4].args.amount).bignumber.to.be.eq(matchedFillResults.profitInLeftMakerAsset); // right maker -> taker profit expect(logs[5].args.orderHash).to.be.eq(rightOrderHash); expect(logs[5].args.assetData).to.be.eq(rightOrder.makerAssetData); expect(logs[5].args.from).to.be.eq(rightOrder.makerAddress); expect(logs[5].args.to).to.be.eq(takerAddress); expect(logs[5].args.amount).bignumber.to.be.eq(matchedFillResults.profitInRightMakerAsset); // Right taker fee -> right fee recipient expect(logs[6].args.orderHash).to.be.eq(rightOrderHash); expect(logs[6].args.assetData).to.be.eq(rightOrder.takerFeeAssetData); expect(logs[6].args.from).to.be.eq(takerAddress); expect(logs[6].args.to).to.be.eq(rightOrder.feeRecipientAddress); expect(logs[6].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerFeePaid); // Right taker fee -> right fee recipient expect(logs[7].args.orderHash).to.be.eq(leftOrderHash); expect(logs[7].args.assetData).to.be.eq(leftOrder.takerFeeAssetData); expect(logs[7].args.from).to.be.eq(takerAddress); expect(logs[7].args.to).to.be.eq(leftOrder.feeRecipientAddress); expect(logs[7].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerFeePaid); }); }); }); // tslint:disable-line:max-file-line-count