diff --git a/contracts/exchange/contracts/test/TestIsolatedExchange.sol b/contracts/exchange/contracts/test/TestIsolatedExchange.sol index bf5e8adff8..4d53567334 100644 --- a/contracts/exchange/contracts/test/TestIsolatedExchange.sol +++ b/contracts/exchange/contracts/test/TestIsolatedExchange.sol @@ -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); - mapping(address => int256) storage assetBalances = - rawAssetBalances[keccak256(assetData)]; - for (uint i = 0; i < addresses.length; i++) { - balances[i] = assetBalances[addresses[i]]; + 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(assets[assetIdx])]; + for (uint addrIdx = 0; addrIdx < addresses.length; ++addrIdx) { + balances[assetIdx][addrIdx] = assetBalances[addresses[addrIdx]]; + } } } diff --git a/contracts/exchange/test/isolated_fill_order.ts b/contracts/exchange/test/isolated_fill_order.ts index a775370f79..b1f1cf53f7 100644 --- a/contracts/exchange/test/isolated_fill_order.ts +++ b/contracts/exchange/test/isolated_fill_order.ts @@ -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); } - it('works', async () => { - const order = createOrder({ - makerAssetAmount: toBN(1), - takerAssetAmount: toBN(2), + 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)); }); - const results = await testExchange.fillOrderAsync(order, 2); - console.log(results, testExchange.getOrderHash(order)); - }); + } }); function toBN(num: BigNumber | string | number): BigNumber { diff --git a/contracts/exchange/test/utils/isolated_exchange_wrapper.ts b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts index c20002e693..d6c6af1da2 100644 --- a/contracts/exchange/test/utils/isolated_exchange_wrapper.ts +++ b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts @@ -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 extends EventsAndBalances { + result: TResult; +} + +interface TransactionContractFunction { + callAsync: (...args: any[]) => Promise; + sendTransactionAsync: (...args: any[]) => Promise; +} + /** * @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 { 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( + this.instance.fillOrder, + order, + _takerAssetFillAmount, + signature, + txOpts, ); - // Parse logs. - const fillEventArgs = filterLogsToArguments(receipt.logs, 'Fill')[0]; - const transferFromCalls = filterLogsToArguments( - 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 { + return this.getAssetBalancesAsync(assetData, [ address ]); + } + + public async getAssetBalancesAsync(assetData: string, addresses: string[]): Promise { + return (await this.instance.getRawAssetBalances.callAsync([ assetData ], addresses))[0]; + } + + public async getBalancesAsync(assets: string[], addresses: string[]): + Promise { + 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 { + // 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( + instanceMethod: TransactionContractFunction, + // tslint:disable-next-line: trailing-comma + ...args: any[] + ): Promise> { + // 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(logs, 'Fill'), + transferFromCalls: filterLogsToArguments( + logs, + 'DispatchTransferFromCalled', + ), + }; }