@0x/contracts-exchange: Create reference functions test util.
				
					
				
			`@0x/contracts-exchange`: Use reference functions to assert fill results in `isolated_fill_order` tests.
This commit is contained in:
		@@ -23,7 +23,7 @@ const expect = chai.expect;
 | 
			
		||||
 | 
			
		||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
 | 
			
		||||
 | 
			
		||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
 | 
			
		||||
const { MAX_UINT256 } = constants;
 | 
			
		||||
 | 
			
		||||
const emptyOrder: Order = {
 | 
			
		||||
    senderAddress: constants.NULL_ADDRESS,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,117 @@
 | 
			
		||||
import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
 | 
			
		||||
import {
 | 
			
		||||
    blockchainTests,
 | 
			
		||||
    constants,
 | 
			
		||||
    expect,
 | 
			
		||||
    FillResults,
 | 
			
		||||
    hexRandom,
 | 
			
		||||
} from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper';
 | 
			
		||||
import { AssetBalances, IsolatedExchangeWrapper, Orderish } from './utils/isolated_exchange_wrapper';
 | 
			
		||||
import { calculateFillResults } from './utils/reference_functions';
 | 
			
		||||
 | 
			
		||||
blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
			
		||||
    const { ZERO_AMOUNT } = constants;
 | 
			
		||||
    const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
 | 
			
		||||
    const ERC20_ASSET_DATA_LENGTH = 24;
 | 
			
		||||
    const DEFAULT_ORDER: Order = {
 | 
			
		||||
    const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
 | 
			
		||||
    const DEFAULT_ORDER: Orderish = {
 | 
			
		||||
        senderAddress: constants.NULL_ADDRESS,
 | 
			
		||||
        makerAddress: randomAddress(),
 | 
			
		||||
        takerAddress: constants.NULL_ADDRESS,
 | 
			
		||||
        makerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
        takerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
        makerAssetAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
        takerAssetAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
        salt: constants.ZERO_AMOUNT,
 | 
			
		||||
        makerFee: ZERO_AMOUNT,
 | 
			
		||||
        takerFee: ZERO_AMOUNT,
 | 
			
		||||
        makerAssetAmount: ZERO_AMOUNT,
 | 
			
		||||
        takerAssetAmount: ZERO_AMOUNT,
 | 
			
		||||
        salt: ZERO_AMOUNT,
 | 
			
		||||
        feeRecipientAddress: constants.NULL_ADDRESS,
 | 
			
		||||
        expirationTimeSeconds: toBN(TOMORROW),
 | 
			
		||||
        expirationTimeSeconds: new BigNumber(TOMORROW),
 | 
			
		||||
        makerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
 | 
			
		||||
        takerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
 | 
			
		||||
        makerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
 | 
			
		||||
        takerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
 | 
			
		||||
    };
 | 
			
		||||
    let takerAddress: string;
 | 
			
		||||
    let testExchange: IsolatedExchangeWrapper;
 | 
			
		||||
    let exchange: IsolatedExchangeWrapper;
 | 
			
		||||
    let nextSaltValue = 1;
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        [ takerAddress ] = await env.getAccountAddressesAsync();
 | 
			
		||||
        testExchange = await IsolatedExchangeWrapper.deployAsync(
 | 
			
		||||
        exchange = await IsolatedExchangeWrapper.deployAsync(
 | 
			
		||||
            env.web3Wrapper,
 | 
			
		||||
            _.assign(env.txDefaults, { from: takerAddress }),
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function createOrder(details: Partial<Order> = {}): Order {
 | 
			
		||||
        return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
 | 
			
		||||
    function createOrder(details: Partial<Orderish> = {}): Orderish {
 | 
			
		||||
        return _.assign({}, DEFAULT_ORDER, { salt: new BigNumber(nextSaltValue++) }, details);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const i of _.times(100)) {
 | 
			
		||||
        it('works', async () => {
 | 
			
		||||
    async function fillOrderAndAssertResultsAsync(
 | 
			
		||||
        order: Orderish,
 | 
			
		||||
        takerAssetFillAmount: BigNumber,
 | 
			
		||||
    ): Promise<FillResults> {
 | 
			
		||||
        const efr = await calculateExpectedFillResultsAsync(order, takerAssetFillAmount);
 | 
			
		||||
        const efb = calculateExpectedFillBalances(order, efr);
 | 
			
		||||
        const fillResults = await exchange.fillOrderAsync(order, takerAssetFillAmount);
 | 
			
		||||
        // Check returned fillResults.
 | 
			
		||||
        expect(fillResults.makerAssetFilledAmount)
 | 
			
		||||
            .to.bignumber.eq(efr.makerAssetFilledAmount);
 | 
			
		||||
        expect(fillResults.takerAssetFilledAmount)
 | 
			
		||||
            .to.bignumber.eq(efr.takerAssetFilledAmount);
 | 
			
		||||
        expect(fillResults.makerFeePaid)
 | 
			
		||||
            .to.bignumber.eq(efr.makerFeePaid);
 | 
			
		||||
        expect(fillResults.takerFeePaid)
 | 
			
		||||
            .to.bignumber.eq(efr.takerFeePaid);
 | 
			
		||||
        // Check balances.
 | 
			
		||||
        for (const assetData of Object.keys(efb)) {
 | 
			
		||||
            for (const address of Object.keys(efb[assetData])) {
 | 
			
		||||
                expect(exchange.getBalanceChange(assetData, address))
 | 
			
		||||
                    .to.bignumber.eq(efb[assetData][address], `assetData: ${assetData}, address: ${address}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return fillResults;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async function calculateExpectedFillResultsAsync(
 | 
			
		||||
        order: Orderish,
 | 
			
		||||
        takerAssetFillAmount: BigNumber,
 | 
			
		||||
    ): Promise<FillResults> {
 | 
			
		||||
        const takerAssetFilledAmount = await exchange.getTakerAssetFilledAmountAsync(order);
 | 
			
		||||
        const remainingTakerAssetAmount = order.takerAssetAmount.minus(takerAssetFilledAmount);
 | 
			
		||||
        return calculateFillResults(
 | 
			
		||||
            order,
 | 
			
		||||
            BigNumber.min(takerAssetFillAmount, remainingTakerAssetAmount),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function calculateExpectedFillBalances(
 | 
			
		||||
        order: Orderish,
 | 
			
		||||
        fillResults: FillResults,
 | 
			
		||||
    ): AssetBalances {
 | 
			
		||||
        const balances: AssetBalances = {};
 | 
			
		||||
        const addBalance = (assetData: string, address: string, amount: BigNumber) => {
 | 
			
		||||
            balances[assetData] = balances[assetData] || {};
 | 
			
		||||
            const balance = balances[assetData][address] || ZERO_AMOUNT;
 | 
			
		||||
            balances[assetData][address] = balance.plus(amount);
 | 
			
		||||
        };
 | 
			
		||||
        addBalance(order.makerAssetData, order.makerAddress, fillResults.makerAssetFilledAmount.negated());
 | 
			
		||||
        addBalance(order.makerAssetData, takerAddress, fillResults.makerAssetFilledAmount);
 | 
			
		||||
        addBalance(order.takerAssetData, order.makerAddress, fillResults.takerAssetFilledAmount);
 | 
			
		||||
        addBalance(order.takerAssetData, takerAddress, fillResults.takerAssetFilledAmount.negated());
 | 
			
		||||
        addBalance(order.makerFeeAssetData, order.makerAddress, fillResults.makerFeePaid.negated());
 | 
			
		||||
        addBalance(order.makerFeeAssetData, order.feeRecipientAddress, fillResults.makerFeePaid);
 | 
			
		||||
        addBalance(order.takerFeeAssetData, takerAddress, fillResults.takerFeePaid.negated());
 | 
			
		||||
        addBalance(order.takerFeeAssetData, order.feeRecipientAddress, fillResults.takerFeePaid);
 | 
			
		||||
        return balances;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('can fully fill an order', async () => {
 | 
			
		||||
        const order = createOrder({
 | 
			
		||||
                makerAssetAmount: toBN(1),
 | 
			
		||||
                takerAssetAmount: toBN(2),
 | 
			
		||||
            makerAssetAmount: new BigNumber(1),
 | 
			
		||||
            takerAssetAmount: new BigNumber(2),
 | 
			
		||||
        });
 | 
			
		||||
            const results = await testExchange.fillOrderAsync(order, 2);
 | 
			
		||||
        return fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
 | 
			
		||||
    });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function toBN(num: BigNumber | string | number): BigNumber {
 | 
			
		||||
    return new BigNumber(num);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function randomAddress(): string {
 | 
			
		||||
    return hexRandom(constants.ADDRESS_LENGTH);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,8 @@ export interface IsolatedExchangeEvents {
 | 
			
		||||
    transferFromCalls: DispatchTransferFromCallArgs[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Order = OrderWithoutDomain;
 | 
			
		||||
export type Orderish = OrderWithoutDomain;
 | 
			
		||||
export type Numberish = string | number | BigNumber;
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
 | 
			
		||||
export const DEFAULT_BAD_SIGNATURE = createBadSignature();
 | 
			
		||||
@@ -65,9 +66,13 @@ export class IsolatedExchangeWrapper {
 | 
			
		||||
        this.logDecoder = new LogDecoder(web3Wrapper, artifacts);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getTakerAssetFilledAmountAsync(order: Orderish): Promise<BigNumber> {
 | 
			
		||||
        return this.instance.filled.callAsync(this.getOrderHash(order));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async fillOrderAsync(
 | 
			
		||||
        order: Order,
 | 
			
		||||
        takerAssetFillAmount: BigNumber | number,
 | 
			
		||||
        order: Orderish,
 | 
			
		||||
        takerAssetFillAmount: Numberish,
 | 
			
		||||
        signature: string = DEFAULT_GOOD_SIGNATURE,
 | 
			
		||||
        txOpts?: TxData,
 | 
			
		||||
    ): Promise<FillResults> {
 | 
			
		||||
@@ -80,7 +85,7 @@ export class IsolatedExchangeWrapper {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getOrderHash(order: Order): string {
 | 
			
		||||
    public getOrderHash(order: Orderish): string {
 | 
			
		||||
        const domain = {
 | 
			
		||||
            verifyingContractAddress: this.instance.address,
 | 
			
		||||
            chainId: IsolatedExchangeWrapper.CHAIN_ID,
 | 
			
		||||
@@ -125,14 +130,14 @@ interface TransactionContractFunction<TResult> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @dev Create a signature for the `TestIsolatedExchange` contract that will pass.
 | 
			
		||||
 * Create a signature for the `TestIsolatedExchange` contract that will pass.
 | 
			
		||||
 */
 | 
			
		||||
export function createGoodSignature(type: SignatureType = SignatureType.EIP712): string {
 | 
			
		||||
    return `0x01${Buffer.from([type]).toString('hex')}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @dev Create a signature for the `TestIsolatedExchange` contract that will fail.
 | 
			
		||||
 * Create a signature for the `TestIsolatedExchange` contract that will fail.
 | 
			
		||||
 */
 | 
			
		||||
export function createBadSignature(type: SignatureType = SignatureType.EIP712): string {
 | 
			
		||||
    return `0x00${Buffer.from([type]).toString('hex')}`;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								contracts/exchange/test/utils/reference_functions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								contracts/exchange/test/utils/reference_functions.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
import { constants, FillResults } from '@0x/contracts-test-utils';
 | 
			
		||||
import { LibMathRevertErrors } from '@0x/order-utils';
 | 
			
		||||
import { OrderWithoutDomain } from '@0x/types';
 | 
			
		||||
import { AnyRevertError, BigNumber, SafeMathRevertErrors } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
const { MAX_UINT256 } = constants;
 | 
			
		||||
 | 
			
		||||
export function isRoundingErrorFloor(
 | 
			
		||||
    numerator: BigNumber,
 | 
			
		||||
    denominator: BigNumber,
 | 
			
		||||
    target: BigNumber,
 | 
			
		||||
): boolean {
 | 
			
		||||
    if (denominator.eq(0)) {
 | 
			
		||||
        throw new LibMathRevertErrors.DivisionByZeroError();
 | 
			
		||||
    }
 | 
			
		||||
    if (numerator.eq(0)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (target.eq(0)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    const product = numerator.multipliedBy(target);
 | 
			
		||||
    const remainder = product.mod(denominator);
 | 
			
		||||
    const remainderTimes1000 = remainder.multipliedBy('1000');
 | 
			
		||||
    const isError = remainderTimes1000.gte(product);
 | 
			
		||||
    if (remainderTimes1000.isGreaterThan(MAX_UINT256)) {
 | 
			
		||||
        // Solidity implementation won't actually throw.
 | 
			
		||||
        throw new AnyRevertError();
 | 
			
		||||
    }
 | 
			
		||||
    return isError;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function IsRoundingErrorCeil(
 | 
			
		||||
    numerator: BigNumber,
 | 
			
		||||
    denominator: BigNumber,
 | 
			
		||||
    target: BigNumber,
 | 
			
		||||
): boolean {
 | 
			
		||||
    if (denominator.eq(0)) {
 | 
			
		||||
        throw new LibMathRevertErrors.DivisionByZeroError();
 | 
			
		||||
    }
 | 
			
		||||
    if (numerator.eq(0)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (target.eq(0)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    const product = numerator.multipliedBy(target);
 | 
			
		||||
    const remainder = product.mod(denominator);
 | 
			
		||||
    const error = denominator.minus(remainder).mod(denominator);
 | 
			
		||||
    const errorTimes1000 = error.multipliedBy('1000');
 | 
			
		||||
    const isError = errorTimes1000.gte(product);
 | 
			
		||||
    if (errorTimes1000.isGreaterThan(MAX_UINT256)) {
 | 
			
		||||
        // Solidity implementation won't actually throw.
 | 
			
		||||
        throw new AnyRevertError();
 | 
			
		||||
    }
 | 
			
		||||
    return isError;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function safeGetPartialAmountFloor(
 | 
			
		||||
    numerator: BigNumber,
 | 
			
		||||
    denominator: BigNumber,
 | 
			
		||||
    target: BigNumber,
 | 
			
		||||
): BigNumber {
 | 
			
		||||
    if (denominator.eq(0)) {
 | 
			
		||||
        throw new LibMathRevertErrors.DivisionByZeroError();
 | 
			
		||||
    }
 | 
			
		||||
    const isRoundingError = isRoundingErrorFloor(numerator, denominator, target);
 | 
			
		||||
    if (isRoundingError) {
 | 
			
		||||
        throw new LibMathRevertErrors.RoundingError(numerator, denominator, target);
 | 
			
		||||
    }
 | 
			
		||||
    const product = numerator.multipliedBy(target);
 | 
			
		||||
    if (product.isGreaterThan(MAX_UINT256)) {
 | 
			
		||||
        throw new SafeMathRevertErrors.SafeMathError(
 | 
			
		||||
            SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
 | 
			
		||||
            numerator,
 | 
			
		||||
            denominator,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    return product.dividedToIntegerBy(denominator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function calculateFillResults(
 | 
			
		||||
    order: OrderWithoutDomain,
 | 
			
		||||
    takerAssetFilledAmount: BigNumber,
 | 
			
		||||
): FillResults {
 | 
			
		||||
    const makerAssetFilledAmount = safeGetPartialAmountFloor(
 | 
			
		||||
        takerAssetFilledAmount,
 | 
			
		||||
        order.takerAssetAmount,
 | 
			
		||||
        order.makerAssetAmount,
 | 
			
		||||
    );
 | 
			
		||||
    const makerFeePaid = safeGetPartialAmountFloor(
 | 
			
		||||
        makerAssetFilledAmount,
 | 
			
		||||
        order.makerAssetAmount,
 | 
			
		||||
        order.makerFee,
 | 
			
		||||
    );
 | 
			
		||||
    const takerFeePaid = safeGetPartialAmountFloor(
 | 
			
		||||
        takerAssetFilledAmount,
 | 
			
		||||
        order.takerAssetAmount,
 | 
			
		||||
        order.takerFee,
 | 
			
		||||
    );
 | 
			
		||||
    return {
 | 
			
		||||
        makerAssetFilledAmount,
 | 
			
		||||
        takerAssetFilledAmount,
 | 
			
		||||
        makerFeePaid,
 | 
			
		||||
        takerFeePaid,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user