@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
 | 
					    /// @dev Convenience function to get the `rawAssetBalances` for
 | 
				
			||||||
    ///      multiple addresses.
 | 
					    ///      multiple assets and addresses.
 | 
				
			||||||
    function getRawAssetBalances(
 | 
					    function getRawAssetBalances(
 | 
				
			||||||
        bytes calldata assetData,
 | 
					        bytes[] calldata assets,
 | 
				
			||||||
        address[] calldata addresses
 | 
					        address[] calldata addresses
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
        external
 | 
					        external
 | 
				
			||||||
        returns (int256[] memory balances)
 | 
					        returns (int256[][] memory balances)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        balances = new int256[](addresses.length);
 | 
					        balances = new int256[][](assets.length);
 | 
				
			||||||
        mapping(address => int256) storage assetBalances =
 | 
					        for (uint assetIdx = 0; assetIdx < assets.length; ++assetIdx) {
 | 
				
			||||||
            rawAssetBalances[keccak256(assetData)];
 | 
					            balances[assetIdx] = new int256[](addresses.length);
 | 
				
			||||||
        for (uint i = 0; i < addresses.length; i++) {
 | 
					            mapping(address => int256) storage assetBalances =
 | 
				
			||||||
            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';
 | 
					import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
					blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
				
			||||||
    const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
 | 
					    const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
 | 
				
			||||||
 | 
					    const ERC20_ASSET_DATA_LENGTH = 24;
 | 
				
			||||||
    const DEFAULT_ORDER: Order = {
 | 
					    const DEFAULT_ORDER: Order = {
 | 
				
			||||||
        senderAddress: constants.NULL_ADDRESS,
 | 
					        senderAddress: constants.NULL_ADDRESS,
 | 
				
			||||||
        makerAddress: randomAddress(),
 | 
					        makerAddress: randomAddress(),
 | 
				
			||||||
@@ -14,13 +16,13 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
				
			|||||||
        takerFee: constants.ZERO_AMOUNT,
 | 
					        takerFee: constants.ZERO_AMOUNT,
 | 
				
			||||||
        makerAssetAmount: constants.ZERO_AMOUNT,
 | 
					        makerAssetAmount: constants.ZERO_AMOUNT,
 | 
				
			||||||
        takerAssetAmount: 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,
 | 
					        salt: constants.ZERO_AMOUNT,
 | 
				
			||||||
        feeRecipientAddress: constants.NULL_ADDRESS,
 | 
					        feeRecipientAddress: constants.NULL_ADDRESS,
 | 
				
			||||||
        expirationTimeSeconds: toBN(TOMORROW),
 | 
					        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 takerAddress: string;
 | 
				
			||||||
    let testExchange: IsolatedExchangeWrapper;
 | 
					    let testExchange: IsolatedExchangeWrapper;
 | 
				
			||||||
@@ -38,14 +40,16 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
 | 
				
			|||||||
        return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
 | 
					        return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('works', async () => {
 | 
					    for (const i of _.times(100)) {
 | 
				
			||||||
        const order = createOrder({
 | 
					        it('works', async () => {
 | 
				
			||||||
            makerAssetAmount: toBN(1),
 | 
					            const order = createOrder({
 | 
				
			||||||
            takerAssetAmount: toBN(2),
 | 
					                makerAssetAmount: toBN(1),
 | 
				
			||||||
 | 
					                takerAssetAmount: toBN(2),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            const results = await testExchange.fillOrderAsync(order, 2);
 | 
				
			||||||
 | 
					            // console.log(results, testExchange.getOrderHash(order));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        const results = await testExchange.fillOrderAsync(order, 2);
 | 
					    }
 | 
				
			||||||
        console.log(results, testExchange.getOrderHash(order));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toBN(num: BigNumber | string | number): BigNumber {
 | 
					function toBN(num: BigNumber | string | number): BigNumber {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { orderHashUtils } from '@0x/order-utils';
 | 
				
			|||||||
import { OrderWithoutDomain, SignatureType } from '@0x/types';
 | 
					import { OrderWithoutDomain, SignatureType } from '@0x/types';
 | 
				
			||||||
import { BigNumber } from '@0x/utils';
 | 
					import { BigNumber } from '@0x/utils';
 | 
				
			||||||
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
 | 
					import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
 | 
				
			||||||
 | 
					import { LogEntry } from 'ethereum-types';
 | 
				
			||||||
import * as _ from 'lodash';
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@@ -26,15 +27,22 @@ export function createBadSignature(type: SignatureType = SignatureType.EIP712):
 | 
				
			|||||||
    return `0x00${Buffer.from([type]).toString('hex')}`;
 | 
					    return `0x00${Buffer.from([type]).toString('hex')}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IsolatedAssetBalances {
 | 
					export interface AssetBalances {
 | 
				
			||||||
    [assetData: string]: { [address: string]: BigNumber };
 | 
					    [assetData: string]: { [address: string]: BigNumber };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IsolatedFillOrderResults {
 | 
					export interface IsolatedExchangeEvents {
 | 
				
			||||||
    fillResults: FillResults;
 | 
					    fillEvents: FillEventArgs[];
 | 
				
			||||||
    fillEventArgs: FillEventArgs;
 | 
					 | 
				
			||||||
    transferFromCalls: DispatchTransferFromCallArgs[];
 | 
					    transferFromCalls: DispatchTransferFromCallArgs[];
 | 
				
			||||||
    balances: IsolatedAssetBalances;
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface EventsAndBalances {
 | 
				
			||||||
 | 
					    events: IsolatedExchangeEvents;
 | 
				
			||||||
 | 
					    balances: AssetBalances;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IsolatedFillOrderResults extends EventsAndBalances {
 | 
				
			||||||
 | 
					    fillResults: FillResults;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Order = OrderWithoutDomain;
 | 
					export type Order = OrderWithoutDomain;
 | 
				
			||||||
@@ -42,10 +50,20 @@ export type Order = OrderWithoutDomain;
 | 
				
			|||||||
export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
 | 
					export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
 | 
				
			||||||
export const DEFAULT_BAD_SIGNATURE = createBadSignature();
 | 
					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.
 | 
					 * @dev Convenience wrapper for the `TestIsolatedExchange` contract.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class IsolatedExchangeWrapper {
 | 
					export class IsolatedExchangeWrapper {
 | 
				
			||||||
 | 
					    public static readonly CHAIN_ID = 1337;
 | 
				
			||||||
    public instance: TestIsolatedExchangeContract;
 | 
					    public instance: TestIsolatedExchangeContract;
 | 
				
			||||||
    public logDecoder: LogDecoder;
 | 
					    public logDecoder: LogDecoder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,46 +102,89 @@ export class IsolatedExchangeWrapper {
 | 
				
			|||||||
        txOpts?: TxData,
 | 
					        txOpts?: TxData,
 | 
				
			||||||
    ): Promise<IsolatedFillOrderResults> {
 | 
					    ): Promise<IsolatedFillOrderResults> {
 | 
				
			||||||
        const _takerAssetFillAmount = new BigNumber(takerAssetFillAmount);
 | 
					        const _takerAssetFillAmount = new BigNumber(takerAssetFillAmount);
 | 
				
			||||||
        // Call to get the return value.
 | 
					        const results = await this._callAndSendExchangeFunctionAsync<FillResults>(
 | 
				
			||||||
        const fillResults = await this.instance.fillOrder.callAsync(order, _takerAssetFillAmount, signature, txOpts);
 | 
					            this.instance.fillOrder,
 | 
				
			||||||
        // Transact to execute it.
 | 
					            order,
 | 
				
			||||||
        const receipt = await this.logDecoder.getTxWithDecodedLogsAsync(
 | 
					            _takerAssetFillAmount,
 | 
				
			||||||
            await this.instance.fillOrder.sendTransactionAsync(order, _takerAssetFillAmount, signature, txOpts),
 | 
					            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 {
 | 
					        return {
 | 
				
			||||||
            fillResults,
 | 
					            fillResults: results.result,
 | 
				
			||||||
            fillEventArgs,
 | 
					            events: results.events,
 | 
				
			||||||
            transferFromCalls,
 | 
					            balances: results.balances,
 | 
				
			||||||
            balances,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public getOrderHash(order: Order): string {
 | 
					    public getOrderHash(order: Order): string {
 | 
				
			||||||
        const domain = {
 | 
					        const domain = {
 | 
				
			||||||
            verifyingContractAddress: this.instance.address,
 | 
					            verifyingContractAddress: this.instance.address,
 | 
				
			||||||
            chainId: 1337,
 | 
					            chainId: IsolatedExchangeWrapper.CHAIN_ID,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        return orderHashUtils.getOrderHashHex(_.assign(order, { domain }));
 | 
					        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