import {
    DydxBridgeAction,
    DydxBridgeActionType,
    DydxBridgeData,
    dydxBridgeDataEncoder,
    IAssetDataContract,
} from '@0x/contracts-asset-proxy';
import {
    blockchainTests,
    constants,
    expect,
    getRandomFloat,
    getRandomInteger,
    Numberish,
    randomAddress,
} from '@0x/contracts-test-utils';
import { Order } from '@0x/types';
import { BigNumber, fromTokenUnitAmount, toTokenUnitAmount } from '@0x/utils';

import { artifacts as devUtilsArtifacts } from './artifacts';
import { TestDydxContract, TestLibDydxBalanceContract } from './wrappers';

blockchainTests('LibDydxBalance', env => {
    interface TestDydxConfig {
        marginRatio: BigNumber;
        operators: Array<{
            owner: string;
            operator: string;
        }>;
        accounts: Array<{
            owner: string;
            accountId: BigNumber;
            balances: BigNumber[];
        }>;
        markets: Array<{
            token: string;
            decimals: number;
            price: BigNumber;
        }>;
    }

    const MARGIN_RATIO = 1.5;
    const PRICE_DECIMALS = 18;
    const MAKER_DECIMALS = 6;
    const TAKER_DECIMALS = 18;
    const INITIAL_TAKER_TOKEN_BALANCE = fromTokenUnitAmount(1000, TAKER_DECIMALS);
    const BRIDGE_ADDRESS = randomAddress();
    const ACCOUNT_OWNER = randomAddress();
    const MAKER_PRICE = 150;
    const TAKER_PRICE = 100;
    const SOLVENT_ACCOUNT_IDX = 0;
    // const MIN_SOLVENT_ACCOUNT_IDX = 1;
    const INSOLVENT_ACCOUNT_IDX = 2;
    const ZERO_BALANCE_ACCOUNT_IDX = 3;
    const DYDX_CONFIG: TestDydxConfig = {
        marginRatio: fromTokenUnitAmount(MARGIN_RATIO - 1, PRICE_DECIMALS),
        operators: [{ owner: ACCOUNT_OWNER, operator: BRIDGE_ADDRESS }],
        accounts: [
            {
                owner: ACCOUNT_OWNER,
                accountId: getRandomInteger(1, 2 ** 64),
                // Account exceeds collateralization.
                balances: [fromTokenUnitAmount(10, TAKER_DECIMALS), fromTokenUnitAmount(-1, MAKER_DECIMALS)],
            },
            {
                owner: ACCOUNT_OWNER,
                accountId: getRandomInteger(1, 2 ** 64),
                // Account is at minimum collateralization.
                balances: [
                    fromTokenUnitAmount((MAKER_PRICE / TAKER_PRICE) * MARGIN_RATIO * 5, TAKER_DECIMALS),
                    fromTokenUnitAmount(-5, MAKER_DECIMALS),
                ],
            },
            {
                owner: ACCOUNT_OWNER,
                accountId: getRandomInteger(1, 2 ** 64),
                // Account is undercollateralized..
                balances: [fromTokenUnitAmount(1, TAKER_DECIMALS), fromTokenUnitAmount(-2, MAKER_DECIMALS)],
            },
            {
                owner: ACCOUNT_OWNER,
                accountId: getRandomInteger(1, 2 ** 64),
                // Account has no balance.
                balances: [fromTokenUnitAmount(0, TAKER_DECIMALS), fromTokenUnitAmount(0, MAKER_DECIMALS)],
            },
        ],
        markets: [
            {
                token: constants.NULL_ADDRESS, // TBD
                decimals: TAKER_DECIMALS,
                price: fromTokenUnitAmount(TAKER_PRICE, PRICE_DECIMALS),
            },
            {
                token: constants.NULL_ADDRESS, // TBD
                decimals: MAKER_DECIMALS,
                price: fromTokenUnitAmount(MAKER_PRICE, PRICE_DECIMALS),
            },
        ],
    };

    let dydx: TestDydxContract;
    let testContract: TestLibDydxBalanceContract;
    let assetDataContract: IAssetDataContract;
    let takerTokenAddress: string;
    let makerTokenAddress: string;

    before(async () => {
        assetDataContract = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);

        testContract = await TestLibDydxBalanceContract.deployWithLibrariesFrom0xArtifactAsync(
            devUtilsArtifacts.TestLibDydxBalance,
            devUtilsArtifacts,
            env.provider,
            env.txDefaults,
            {},
        );

        // Create tokens.
        takerTokenAddress = await testContract.createToken(TAKER_DECIMALS).callAsync();
        await testContract.createToken(TAKER_DECIMALS).awaitTransactionSuccessAsync();
        makerTokenAddress = await testContract.createToken(MAKER_DECIMALS).callAsync();
        await testContract.createToken(MAKER_DECIMALS).awaitTransactionSuccessAsync();

        DYDX_CONFIG.markets[0].token = takerTokenAddress;
        DYDX_CONFIG.markets[1].token = makerTokenAddress;

        dydx = await TestDydxContract.deployFrom0xArtifactAsync(
            devUtilsArtifacts.TestDydx,
            env.provider,
            env.txDefaults,
            {},
            DYDX_CONFIG,
        );

        // Mint taker tokens.
        await testContract
            .setTokenBalance(takerTokenAddress, ACCOUNT_OWNER, INITIAL_TAKER_TOKEN_BALANCE)
            .awaitTransactionSuccessAsync();
        // Approve the Dydx contract to spend takerToken.
        await testContract
            .setTokenApproval(takerTokenAddress, ACCOUNT_OWNER, dydx.address, constants.MAX_UINT256)
            .awaitTransactionSuccessAsync();
    });

    interface BalanceCheckInfo {
        dydx: string;
        bridgeAddress: string;
        makerAddress: string;
        makerTokenAddress: string;
        takerTokenAddress: string;
        orderMakerToTakerRate: BigNumber;
        accounts: BigNumber[];
        actions: DydxBridgeAction[];
    }

    function createBalanceCheckInfo(fields: Partial<BalanceCheckInfo> = {}): BalanceCheckInfo {
        return {
            dydx: dydx.address,
            bridgeAddress: BRIDGE_ADDRESS,
            makerAddress: ACCOUNT_OWNER,
            makerTokenAddress: DYDX_CONFIG.markets[1].token,
            takerTokenAddress: DYDX_CONFIG.markets[0].token,
            orderMakerToTakerRate: fromTokenUnitAmount(
                fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(5, MAKER_DECIMALS)),
            ),
            accounts: [DYDX_CONFIG.accounts[SOLVENT_ACCOUNT_IDX].accountId],
            actions: [],
            ...fields,
        };
    }

    function getFilledAccountCollateralizations(
        config: TestDydxConfig,
        checkInfo: BalanceCheckInfo,
        makerAssetFillAmount: BigNumber,
    ): BigNumber[] {
        const values: BigNumber[][] = checkInfo.accounts.map((accountId, accountIdx) => {
            const accountBalances = config.accounts[accountIdx].balances.slice();
            for (const action of checkInfo.actions) {
                const actionMarketId = action.marketId.toNumber();
                const actionAccountIdx = action.accountIdx.toNumber();
                if (checkInfo.accounts[actionAccountIdx] !== accountId) {
                    continue;
                }
                const rate = action.conversionRateDenominator.eq(0)
                    ? new BigNumber(1)
                    : action.conversionRateNumerator.div(action.conversionRateDenominator);
                const change = makerAssetFillAmount.times(
                    action.actionType === DydxBridgeActionType.Deposit ? rate : rate.negated(),
                );
                accountBalances[actionMarketId] = change.plus(accountBalances[actionMarketId]);
            }
            return accountBalances.map((b, marketId) =>
                toTokenUnitAmount(b, config.markets[marketId].decimals).times(
                    toTokenUnitAmount(config.markets[marketId].price, PRICE_DECIMALS),
                ),
            );
        });
        return values
            .map(accountValues => {
                return [
                    // supply
                    BigNumber.sum(...accountValues.filter(b => b.gte(0))),
                    // borrow
                    BigNumber.sum(...accountValues.filter(b => b.lt(0))).abs(),
                ];
            })
            .map(([supply, borrow]) => supply.div(borrow));
    }

    function getRandomRate(): BigNumber {
        return getRandomFloat(0, 1);
    }

    // Computes a deposit rate that is the minimum to keep an account solvent
    // perpetually.
    function getBalancedDepositRate(withdrawRate: BigNumber, scaling: Numberish = 1): BigNumber {
        // Add a small amount to the margin ratio to stay just above insolvency.
        return withdrawRate.times((MAKER_PRICE / TAKER_PRICE) * (MARGIN_RATIO + 1.1e-4)).times(scaling);
    }

    function takerToMakerAmount(takerAmount: BigNumber): BigNumber {
        return takerAmount.times(new BigNumber(10).pow(MAKER_DECIMALS - TAKER_DECIMALS));
    }

    describe('_getSolventMakerAmount()', () => {
        it('computes fillable amount for a solvent maker', async () => {
            // Deposit collateral at a rate low enough to steadily reduce the
            // withdraw account's collateralization ratio.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate, Math.random());
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // The collateralization ratio after filling `makerAssetFillAmount`
            // should be exactly at `MARGIN_RATIO`.
            const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
            expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
        });

        it('computes fillable amount for a solvent maker with zero-sized deposits', async () => {
            const withdrawRate = getRandomRate();
            const depositRate = new BigNumber(0);
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // The collateralization ratio after filling `makerAssetFillAmount`
            // should be exactly at `MARGIN_RATIO`.
            const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
            expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
        });

        it('computes fillable amount for a solvent maker with no deposits', async () => {
            const withdrawRate = getRandomRate();
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // The collateralization ratio after filling `makerAssetFillAmount`
            // should be exactly at `MARGIN_RATIO`.
            const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
            expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
        });

        it('computes fillable amount for a solvent maker with multiple deposits', async () => {
            // Deposit collateral at a rate low enough to steadily reduce the
            // withdraw account's collateralization ratio.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate, Math.random());
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.75), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.25), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // The collateralization ratio after filling `makerAssetFillAmount`
            // should be exactly at `MARGIN_RATIO`.
            const cr = getFilledAccountCollateralizations(DYDX_CONFIG, checkInfo, makerAssetFillAmount);
            expect(cr[0].dp(2)).to.bignumber.eq(MARGIN_RATIO);
        });

        it('returns infinite amount for a perpetually solvent maker', async () => {
            // Deposit collateral at a rate that keeps the withdraw account's
            // collateralization ratio constant.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate);
            const checkInfo = createBalanceCheckInfo({
                // Deposit/Withdraw at a rate == marginRatio.
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
        });

        it('returns infinite amount for a perpetually solvent maker with multiple deposits', async () => {
            // Deposit collateral at a rate that keeps the withdraw account's
            // collateralization ratio constant.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate);
            const checkInfo = createBalanceCheckInfo({
                // Deposit/Withdraw at a rate == marginRatio.
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.25), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.75), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
        });

        it('does not count deposits to other accounts', async () => {
            // Deposit collateral at a rate that keeps the withdraw account's
            // collateralization ratio constant, BUT we split it in two deposits
            // and one will go into a different account.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate);
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.5), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        // Deposit enough to balance out withdraw, but
                        // into a different account.
                        accountIdx: new BigNumber(1),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate.times(0.5), TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
        });

        it('returns zero on an account that is under-collateralized', async () => {
            // Even though the deposit rate is enough to meet the minimum collateralization ratio,
            // the account is under-collateralized from the start, so cannot be filled.
            const withdrawRate = getRandomRate();
            const depositRate = getBalancedDepositRate(withdrawRate);
            const checkInfo = createBalanceCheckInfo({
                accounts: [DYDX_CONFIG.accounts[INSOLVENT_ACCOUNT_IDX].accountId],
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                        conversionRateDenominator: fromTokenUnitAmount(1),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(0);
        });

        it(
            'returns zero on an account that has no balance if deposit ' +
                'to withdraw ratio is < the minimum collateralization rate',
            async () => {
                // If the deposit rate is not enough to meet the minimum collateralization ratio,
                // the fillable maker amount is zero because it will become insolvent as soon as
                // the withdraw occurs.
                const withdrawRate = getRandomRate();
                const depositRate = getBalancedDepositRate(withdrawRate, 0.99);
                const checkInfo = createBalanceCheckInfo({
                    accounts: [DYDX_CONFIG.accounts[ZERO_BALANCE_ACCOUNT_IDX].accountId],
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Deposit,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(0),
                            conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                            conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                        },
                        {
                            actionType: DydxBridgeActionType.Withdraw,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(1),
                            conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                            conversionRateDenominator: fromTokenUnitAmount(1),
                        },
                    ],
                });
                const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
                expect(makerAssetFillAmount).to.bignumber.eq(0);
            },
        );

        it(
            'returns infinite on an account that has no balance if deposit ' +
                'to withdraw ratio is >= the minimum collateralization rate',
            async () => {
                const withdrawRate = getRandomRate();
                const depositRate = getBalancedDepositRate(withdrawRate);
                const checkInfo = createBalanceCheckInfo({
                    accounts: [DYDX_CONFIG.accounts[ZERO_BALANCE_ACCOUNT_IDX].accountId],
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Deposit,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(0),
                            conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                            conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                        },
                        {
                            actionType: DydxBridgeActionType.Withdraw,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(1),
                            conversionRateNumerator: fromTokenUnitAmount(withdrawRate),
                            conversionRateDenominator: fromTokenUnitAmount(1),
                        },
                    ],
                });
                const makerAssetFillAmount = await testContract.getSolventMakerAmount(checkInfo).callAsync();
                expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
            },
        );
    });

    blockchainTests.resets('_getDepositableMakerAmount()', () => {
        it('returns infinite if no deposit action', async () => {
            const checkInfo = createBalanceCheckInfo({
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
                ),
                actions: [],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
        });

        it('returns infinite if deposit rate is zero', async () => {
            const checkInfo = createBalanceCheckInfo({
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
                ),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(0, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
        });

        it('returns infinite if taker tokens cover the deposit rate', async () => {
            const checkInfo = createBalanceCheckInfo({
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
                ),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(Math.random() * 0.1, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(constants.MAX_UINT256);
        });

        it('returns correct amount if taker tokens only partially cover deposit rate', async () => {
            // The taker tokens getting exchanged in will only partially cover the deposit.
            const exchangeRate = 0.1;
            const depositRate = Math.random() + exchangeRate;
            const checkInfo = createBalanceCheckInfo({
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
                ),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // Compute the equivalent taker asset fill amount.
            const takerAssetFillAmount = fromTokenUnitAmount(
                toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
                    // Reduce the deposit rate by the exchange rate.
                    .times(depositRate - exchangeRate),
                TAKER_DECIMALS,
            );
            // Which should equal the entire taker token balance of the account owner.
            // We do some rounding to account for integer vs FP vs symbolic precision differences.
            expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(5)).to.bignumber.eq(
                toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(5),
            );
        });

        it('returns correct amount if the taker asset not an ERC20', async () => {
            const depositRate = 0.1;
            const checkInfo = createBalanceCheckInfo({
                // The `takerTokenAddress` will be zero if the asset is not an ERC20.
                takerTokenAddress: constants.NULL_ADDRESS,
                orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(0.1, MAKER_DECIMALS)),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // Compute the equivalent taker asset fill amount.
            const takerAssetFillAmount = fromTokenUnitAmount(
                toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
                    // Reduce the deposit rate by the exchange rate.
                    .times(depositRate),
                TAKER_DECIMALS,
            );
            // Which should equal the entire taker token balance of the account owner.
            // We do some rounding to account for integer vs FP vs symbolic precision differences.
            expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(6)).to.bignumber.eq(
                toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(6),
            );
        });

        it('returns the correct amount if taker:maker deposit rate is 1:1 and' + 'token != taker token', async () => {
            const checkInfo = createBalanceCheckInfo({
                takerTokenAddress: randomAddress(),
                // These amounts should be effectively ignored in the final computation
                // because the token being deposited is not the taker token.
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
                ),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(1, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.bignumber.eq(takerToMakerAmount(INITIAL_TAKER_TOKEN_BALANCE));
        });

        it('returns the smallest viable maker amount with multiple deposits', async () => {
            // The taker tokens getting exchanged in will only partially cover the deposit.
            const exchangeRate = 0.1;
            const checkInfo = createBalanceCheckInfo({
                orderMakerToTakerRate: fromTokenUnitAmount(
                    fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
                ),
                actions: [
                    // Technically, deposits of the same token are not allowed, but the
                    // check isn't done in this function so we'll do this to simulate
                    // two deposits to distinct tokens.
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(Math.random() + exchangeRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: fromTokenUnitAmount(Math.random() + exchangeRate, TAKER_DECIMALS),
                        conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                    },
                ],
            });
            const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
            expect(makerAssetFillAmount).to.not.bignumber.eq(constants.MAX_UINT256);
            // Extract the deposit rates.
            const depositRates = checkInfo.actions.map(a =>
                toTokenUnitAmount(a.conversionRateNumerator, TAKER_DECIMALS).div(
                    toTokenUnitAmount(a.conversionRateDenominator, MAKER_DECIMALS),
                ),
            );
            // The largest deposit rate will result in the smallest maker asset fill amount.
            const maxDepositRate = BigNumber.max(...depositRates);
            // Compute the equivalent taker asset fill amounts.
            const takerAssetFillAmount = fromTokenUnitAmount(
                toTokenUnitAmount(makerAssetFillAmount, MAKER_DECIMALS)
                    // Reduce the deposit rate by the exchange rate.
                    .times(maxDepositRate.minus(exchangeRate)),
                TAKER_DECIMALS,
            );
            // Which should equal the entire taker token balance of the account owner.
            // We do some rounding to account for integer vs FP vs symbolic precision differences.
            expect(toTokenUnitAmount(takerAssetFillAmount, TAKER_DECIMALS).dp(5)).to.bignumber.eq(
                toTokenUnitAmount(INITIAL_TAKER_TOKEN_BALANCE, TAKER_DECIMALS).dp(5),
            );
        });

        it(
            'returns zero if the maker has no taker tokens and the deposit rate is' + 'greater than the exchange rate',
            async () => {
                await testContract
                    .setTokenBalance(takerTokenAddress, ACCOUNT_OWNER, constants.ZERO_AMOUNT)
                    .awaitTransactionSuccessAsync();
                // The taker tokens getting exchanged in will only partially cover the deposit.
                const exchangeRate = 0.1;
                const depositRate = Math.random() + exchangeRate;
                const checkInfo = createBalanceCheckInfo({
                    orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Deposit,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(0),
                            conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                            conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                        },
                    ],
                });
                const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
                expect(makerAssetFillAmount).to.bignumber.eq(0);
            },
        );

        it(
            'returns zero if dydx has no taker token allowance and the deposit rate is' +
                'greater than the exchange rate',
            async () => {
                await testContract
                    .setTokenApproval(takerTokenAddress, ACCOUNT_OWNER, dydx.address, constants.ZERO_AMOUNT)
                    .awaitTransactionSuccessAsync();
                // The taker tokens getting exchanged in will only partially cover the deposit.
                const exchangeRate = 0.1;
                const depositRate = Math.random() + exchangeRate;
                const checkInfo = createBalanceCheckInfo({
                    orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Deposit,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(0),
                            conversionRateNumerator: fromTokenUnitAmount(depositRate, TAKER_DECIMALS),
                            conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                        },
                    ],
                });
                const makerAssetFillAmount = await testContract.getDepositableMakerAmount(checkInfo).callAsync();
                expect(makerAssetFillAmount).to.bignumber.eq(0);
            },
        );
    });

    describe('_areActionsWellFormed()', () => {
        it('Returns false if no actions', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if there is an account index out of range in deposits', async () => {
            const checkInfo = createBalanceCheckInfo({
                accounts: DYDX_CONFIG.accounts.slice(0, 2).map(a => a.accountId),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(2),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if a market is not unique among deposits', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if no withdraw at the end', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if a withdraw comes before a deposit', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if more than one withdraw', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if withdraw is not for maker token', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Returns false if withdraw is for an out of range account', async () => {
            const checkInfo = createBalanceCheckInfo({
                accounts: DYDX_CONFIG.accounts.slice(0, 2).map(a => a.accountId),
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(2),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.false();
        });

        it('Can return true if no deposit', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.true();
        });

        it('Can return true if no deposit', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.true();
        });

        it('Can return true with multiple deposits', async () => {
            const checkInfo = createBalanceCheckInfo({
                actions: [
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(0),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Deposit,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                    {
                        actionType: DydxBridgeActionType.Withdraw,
                        accountIdx: new BigNumber(0),
                        marketId: new BigNumber(1),
                        conversionRateNumerator: new BigNumber(0),
                        conversionRateDenominator: new BigNumber(0),
                    },
                ],
            });
            const r = await testContract.areActionsWellFormed(checkInfo).callAsync();
            expect(r).to.be.true();
        });
    });

    function createERC20AssetData(tokenAddress: string): string {
        return assetDataContract.ERC20Token(tokenAddress).getABIEncodedTransactionData();
    }

    function createERC721AssetData(tokenAddress: string, tokenId: BigNumber): string {
        return assetDataContract.ERC721Token(tokenAddress, tokenId).getABIEncodedTransactionData();
    }

    function createBridgeAssetData(
        makerTokenAddress_: string,
        bridgeAddress: string,
        data: Partial<DydxBridgeData> = {},
    ): string {
        return assetDataContract
            .ERC20Bridge(
                makerTokenAddress_,
                bridgeAddress,
                dydxBridgeDataEncoder.encode({
                    bridgeData: {
                        accountNumbers: DYDX_CONFIG.accounts.slice(0, 1).map(a => a.accountId),
                        actions: [
                            {
                                actionType: DydxBridgeActionType.Deposit,
                                accountIdx: new BigNumber(0),
                                marketId: new BigNumber(0),
                                conversionRateNumerator: fromTokenUnitAmount(1, TAKER_DECIMALS),
                                conversionRateDenominator: fromTokenUnitAmount(1, MAKER_DECIMALS),
                            },
                            {
                                actionType: DydxBridgeActionType.Withdraw,
                                accountIdx: new BigNumber(0),
                                marketId: new BigNumber(1),
                                conversionRateNumerator: new BigNumber(0),
                                conversionRateDenominator: new BigNumber(0),
                            },
                        ],
                        ...data,
                    },
                }),
            )
            .getABIEncodedTransactionData();
    }

    function createOrder(orderFields: Partial<Order> = {}): Order {
        return {
            chainId: 1,
            exchangeAddress: randomAddress(),
            salt: getRandomInteger(1, constants.MAX_UINT256),
            expirationTimeSeconds: getRandomInteger(1, constants.MAX_UINT256),
            feeRecipientAddress: randomAddress(),
            makerAddress: ACCOUNT_OWNER,
            takerAddress: constants.NULL_ADDRESS,
            senderAddress: constants.NULL_ADDRESS,
            makerFee: getRandomInteger(1, constants.MAX_UINT256),
            takerFee: getRandomInteger(1, constants.MAX_UINT256),
            makerAssetAmount: fromTokenUnitAmount(100, MAKER_DECIMALS),
            takerAssetAmount: fromTokenUnitAmount(10, TAKER_DECIMALS),
            makerAssetData: createBridgeAssetData(makerTokenAddress, BRIDGE_ADDRESS),
            takerAssetData: createERC20AssetData(takerTokenAddress),
            makerFeeAssetData: constants.NULL_BYTES,
            takerFeeAssetData: constants.NULL_BYTES,
            ...orderFields,
        };
    }

    describe('getDydxMakerBalance()', () => {
        it('returns nonzero with valid order', async () => {
            const order = createOrder();
            const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
            expect(r).to.not.bignumber.eq(0);
        });

        it('returns nonzero with valid order with an ERC721 taker asset', async () => {
            const order = createOrder({
                takerAssetData: createERC721AssetData(randomAddress(), getRandomInteger(1, constants.MAX_UINT256)),
            });
            const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
            expect(r).to.not.bignumber.eq(0);
        });

        it('returns 0 if bridge is not a local operator', async () => {
            const order = createOrder({
                makerAssetData: createBridgeAssetData(ACCOUNT_OWNER, randomAddress()),
            });
            const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
            expect(r).to.bignumber.eq(0);
        });

        it('returns 0 if bridge data does not have well-formed actions', async () => {
            const order = createOrder({
                makerAssetData: createBridgeAssetData(takerTokenAddress, BRIDGE_ADDRESS, {
                    // Two withdraw actions is invalid.
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Withdraw,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(0),
                            conversionRateNumerator: new BigNumber(0),
                            conversionRateDenominator: new BigNumber(0),
                        },
                        {
                            actionType: DydxBridgeActionType.Withdraw,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(1),
                            conversionRateNumerator: new BigNumber(0),
                            conversionRateDenominator: new BigNumber(0),
                        },
                    ],
                }),
            });
            const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
            expect(r).to.bignumber.eq(0);
        });

        it('returns 0 if the maker token withdraw rate is < 1', async () => {
            const order = createOrder({
                makerAssetData: createBridgeAssetData(takerTokenAddress, BRIDGE_ADDRESS, {
                    actions: [
                        {
                            actionType: DydxBridgeActionType.Withdraw,
                            accountIdx: new BigNumber(0),
                            marketId: new BigNumber(1),
                            conversionRateNumerator: new BigNumber(0.99e18),
                            conversionRateDenominator: new BigNumber(1e18),
                        },
                    ],
                }),
            });
            const r = await testContract.getDydxMakerBalance(order, dydx.address).callAsync();
            expect(r).to.bignumber.eq(0);
        });
    });
});
// tslint:disable-next-line: max-file-line-count
