@0x/contracts-exchange: Allow fetching of balance of multiple assets
				
					
				
			in `TestIsolatedExchange` contract. `@0x/contracts-exchange`: Refactor `IsolatedExchangeWrapper` to be more extensible.
This commit is contained in:
		@@ -48,19 +48,22 @@ contract TestIsolatedExchange is
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    /// @dev Convenience function to get the `rawAssetBalances` for
 | 
			
		||||
    ///      multiple addresses.
 | 
			
		||||
    ///      multiple assets and addresses.
 | 
			
		||||
    function getRawAssetBalances(
 | 
			
		||||
        bytes calldata assetData,
 | 
			
		||||
        bytes[] calldata assets,
 | 
			
		||||
        address[] calldata addresses
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (int256[] memory balances)
 | 
			
		||||
        returns (int256[][] memory balances)
 | 
			
		||||
    {
 | 
			
		||||
        balances = new int256[](addresses.length);
 | 
			
		||||
        balances = new int256[][](assets.length);
 | 
			
		||||
        for (uint assetIdx = 0; assetIdx < assets.length; ++assetIdx) {
 | 
			
		||||
            balances[assetIdx] = new int256[](addresses.length);
 | 
			
		||||
            mapping(address => int256) storage assetBalances =
 | 
			
		||||
            rawAssetBalances[keccak256(assetData)];
 | 
			
		||||
        for (uint i = 0; i < addresses.length; i++) {
 | 
			
		||||
            balances[i] = assetBalances[addresses[i]];
 | 
			
		||||
                rawAssetBalances[keccak256(assets[assetIdx])];
 | 
			
		||||
            for (uint addrIdx = 0; addrIdx < addresses.length; ++addrIdx) {
 | 
			
		||||
                balances[assetIdx][addrIdx] = assetBalances[addresses[addrIdx]];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,10 @@ import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
			
		||||
    const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
 | 
			
		||||
    const ERC20_ASSET_DATA_LENGTH = 24;
 | 
			
		||||
    const DEFAULT_ORDER: Order = {
 | 
			
		||||
        senderAddress: constants.NULL_ADDRESS,
 | 
			
		||||
        makerAddress: randomAddress(),
 | 
			
		||||
@@ -14,13 +16,13 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
			
		||||
        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: toBN(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;
 | 
			
		||||
@@ -38,14 +40,16 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
			
		||||
        return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const i of _.times(100)) {
 | 
			
		||||
        it('works', async () => {
 | 
			
		||||
            const order = createOrder({
 | 
			
		||||
                makerAssetAmount: toBN(1),
 | 
			
		||||
                takerAssetAmount: toBN(2),
 | 
			
		||||
            });
 | 
			
		||||
            const results = await testExchange.fillOrderAsync(order, 2);
 | 
			
		||||
        console.log(results, testExchange.getOrderHash(order));
 | 
			
		||||
            // console.log(results, testExchange.getOrderHash(order));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function toBN(num: BigNumber | string | number): BigNumber {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { orderHashUtils } from '@0x/order-utils';
 | 
			
		||||
import { OrderWithoutDomain, SignatureType } from '@0x/types';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
 | 
			
		||||
import { LogEntry } from 'ethereum-types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
@@ -26,15 +27,22 @@ export function createBadSignature(type: SignatureType = SignatureType.EIP712):
 | 
			
		||||
    return `0x00${Buffer.from([type]).toString('hex')}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IsolatedAssetBalances {
 | 
			
		||||
export interface AssetBalances {
 | 
			
		||||
    [assetData: string]: { [address: string]: BigNumber };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IsolatedFillOrderResults {
 | 
			
		||||
    fillResults: FillResults;
 | 
			
		||||
    fillEventArgs: FillEventArgs;
 | 
			
		||||
export interface IsolatedExchangeEvents {
 | 
			
		||||
    fillEvents: FillEventArgs[];
 | 
			
		||||
    transferFromCalls: DispatchTransferFromCallArgs[];
 | 
			
		||||
    balances: IsolatedAssetBalances;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EventsAndBalances {
 | 
			
		||||
    events: IsolatedExchangeEvents;
 | 
			
		||||
    balances: AssetBalances;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IsolatedFillOrderResults extends EventsAndBalances {
 | 
			
		||||
    fillResults: FillResults;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Order = OrderWithoutDomain;
 | 
			
		||||
@@ -42,10 +50,20 @@ export type Order = OrderWithoutDomain;
 | 
			
		||||
export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
 | 
			
		||||
export const DEFAULT_BAD_SIGNATURE = createBadSignature();
 | 
			
		||||
 | 
			
		||||
interface CallAndSendResult<TResult> extends EventsAndBalances {
 | 
			
		||||
    result: TResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TransactionContractFunction<TResult> {
 | 
			
		||||
    callAsync: (...args: any[]) => Promise<TResult>;
 | 
			
		||||
    sendTransactionAsync: (...args: any[]) => Promise<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @dev Convenience wrapper for the `TestIsolatedExchange` contract.
 | 
			
		||||
 */
 | 
			
		||||
export class IsolatedExchangeWrapper {
 | 
			
		||||
    public static readonly CHAIN_ID = 1337;
 | 
			
		||||
    public instance: TestIsolatedExchangeContract;
 | 
			
		||||
    public logDecoder: LogDecoder;
 | 
			
		||||
 | 
			
		||||
@@ -84,46 +102,89 @@ export class IsolatedExchangeWrapper {
 | 
			
		||||
        txOpts?: TxData,
 | 
			
		||||
    ): Promise<IsolatedFillOrderResults> {
 | 
			
		||||
        const _takerAssetFillAmount = new BigNumber(takerAssetFillAmount);
 | 
			
		||||
        // Call to get the return value.
 | 
			
		||||
        const fillResults = await this.instance.fillOrder.callAsync(order, _takerAssetFillAmount, signature, txOpts);
 | 
			
		||||
        // Transact to execute it.
 | 
			
		||||
        const receipt = await this.logDecoder.getTxWithDecodedLogsAsync(
 | 
			
		||||
            await this.instance.fillOrder.sendTransactionAsync(order, _takerAssetFillAmount, signature, txOpts),
 | 
			
		||||
        const results = await this._callAndSendExchangeFunctionAsync<FillResults>(
 | 
			
		||||
            this.instance.fillOrder,
 | 
			
		||||
            order,
 | 
			
		||||
            _takerAssetFillAmount,
 | 
			
		||||
            signature,
 | 
			
		||||
            txOpts,
 | 
			
		||||
        );
 | 
			
		||||
        // Parse logs.
 | 
			
		||||
        const fillEventArgs = filterLogsToArguments<FillEventArgs>(receipt.logs, 'Fill')[0];
 | 
			
		||||
        const transferFromCalls = filterLogsToArguments<DispatchTransferFromCallArgs>(
 | 
			
		||||
            receipt.logs,
 | 
			
		||||
            'DispatchTransferFromCalled',
 | 
			
		||||
        );
 | 
			
		||||
        // Extract addresses involved in transfers.
 | 
			
		||||
        const addresses = _.uniq(_.flatten(transferFromCalls.map(c => [c.from, c.to])));
 | 
			
		||||
        // Extract assets involved in transfers.
 | 
			
		||||
        const assets = _.uniq(transferFromCalls.map(c => c.assetData));
 | 
			
		||||
        // Query balances of addresses and assets involved in transfers.
 | 
			
		||||
        const balances = await (async () => {
 | 
			
		||||
            const result: IsolatedAssetBalances = {};
 | 
			
		||||
            for (const assetData of assets) {
 | 
			
		||||
                result[assetData] = _.zipObject(
 | 
			
		||||
                    addresses,
 | 
			
		||||
                    await this.instance.getRawAssetBalances.callAsync(assetData, addresses),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            return result;
 | 
			
		||||
        })();
 | 
			
		||||
        return {
 | 
			
		||||
            fillResults,
 | 
			
		||||
            fillEventArgs,
 | 
			
		||||
            transferFromCalls,
 | 
			
		||||
            balances,
 | 
			
		||||
            fillResults: results.result,
 | 
			
		||||
            events: results.events,
 | 
			
		||||
            balances: results.balances,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getOrderHash(order: Order): string {
 | 
			
		||||
        const domain = {
 | 
			
		||||
            verifyingContractAddress: this.instance.address,
 | 
			
		||||
            chainId: 1337,
 | 
			
		||||
            chainId: IsolatedExchangeWrapper.CHAIN_ID,
 | 
			
		||||
        };
 | 
			
		||||
        return orderHashUtils.getOrderHashHex(_.assign(order, { domain }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getAssetBalanceAsync(assetData: string, address: string): Promise<BigNumber[]> {
 | 
			
		||||
        return this.getAssetBalancesAsync(assetData, [ address ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getAssetBalancesAsync(assetData: string, addresses: string[]): Promise<BigNumber[]> {
 | 
			
		||||
        return (await this.instance.getRawAssetBalances.callAsync([ assetData ], addresses))[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getBalancesAsync(assets: string[], addresses: string[]):
 | 
			
		||||
            Promise<AssetBalances> {
 | 
			
		||||
        const callResults = await this.instance.getRawAssetBalances.callAsync(assets, addresses);
 | 
			
		||||
        const result: AssetBalances = {};
 | 
			
		||||
        for (const i of _.times(assets.length)) {
 | 
			
		||||
            const assetData = assets[i];
 | 
			
		||||
            result[assetData] = {};
 | 
			
		||||
            for (const j of _.times(addresses.length)) {
 | 
			
		||||
                const address = addresses[j];
 | 
			
		||||
                result[assetData][address] = callResults[i][j];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async _getBalancesFromTransferFromCallsAsync(
 | 
			
		||||
        calls: DispatchTransferFromCallArgs[],
 | 
			
		||||
    ): Promise<AssetBalances> {
 | 
			
		||||
        // Extract addresses involved in transfers.
 | 
			
		||||
        const addresses = _.uniq(_.flatten(calls.map(c => [c.from, c.to])));
 | 
			
		||||
        // Extract assets involved in transfers.
 | 
			
		||||
        const assets = _.uniq(calls.map(c => c.assetData));
 | 
			
		||||
        // Query balances of addresses and assets involved in transfers.
 | 
			
		||||
        return this.getBalancesAsync(assets, addresses);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async _callAndSendExchangeFunctionAsync<TResult>(
 | 
			
		||||
        instanceMethod: TransactionContractFunction<TResult>,
 | 
			
		||||
        // tslint:disable-next-line: trailing-comma
 | 
			
		||||
        ...args: any[]
 | 
			
		||||
    ): Promise<CallAndSendResult<TResult>> {
 | 
			
		||||
        // Call to get the return value.
 | 
			
		||||
        const result = await instanceMethod.callAsync.call(this.instance, ...args);
 | 
			
		||||
        // Transact to execute it.
 | 
			
		||||
        const receipt = await this.logDecoder.getTxWithDecodedLogsAsync(
 | 
			
		||||
            await this.instance.fillOrder.sendTransactionAsync.call(this.instance, ...args),
 | 
			
		||||
        );
 | 
			
		||||
        const events = extractEvents(receipt.logs);
 | 
			
		||||
        const balances = await this._getBalancesFromTransferFromCallsAsync(events.transferFromCalls);
 | 
			
		||||
        return {
 | 
			
		||||
            result,
 | 
			
		||||
            events,
 | 
			
		||||
            balances,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function extractEvents(logs: LogEntry[]): IsolatedExchangeEvents {
 | 
			
		||||
    return {
 | 
			
		||||
        fillEvents: filterLogsToArguments<FillEventArgs>(logs, 'Fill'),
 | 
			
		||||
        transferFromCalls: filterLogsToArguments<DispatchTransferFromCallArgs>(
 | 
			
		||||
            logs,
 | 
			
		||||
            'DispatchTransferFromCalled',
 | 
			
		||||
        ),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user