diff --git a/contracts/exchange/contracts/test/TestIsolatedExchange.sol b/contracts/exchange/contracts/test/TestIsolatedExchange.sol index 7a6a4ea1f3..bf5e8adff8 100644 --- a/contracts/exchange/contracts/test/TestIsolatedExchange.sol +++ b/contracts/exchange/contracts/test/TestIsolatedExchange.sol @@ -49,7 +49,7 @@ contract TestIsolatedExchange is /// @dev Convenience function to get the `rawAssetBalances` for /// multiple addresses. - function getMultipleRawAssetBalanceChanges( + function getRawAssetBalances( bytes calldata assetData, address[] calldata addresses ) diff --git a/contracts/exchange/test/isolated_fill_order.ts b/contracts/exchange/test/isolated_fill_order.ts index ccf25534bc..a775370f79 100644 --- a/contracts/exchange/test/isolated_fill_order.ts +++ b/contracts/exchange/test/isolated_fill_order.ts @@ -1,30 +1,14 @@ -import { - addressUtils, - blockchainTests, - constants, - expect, - FillResults, - LogDecoder, -} from '@0x/contracts-test-utils'; -import { Order } from '@0x/types'; +import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; -import { LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; -import { - artifacts, - TestIsolatedExchangeContract, - TestIsolatedExchangeDispatchTransferFromCalledEventArgs as DispatchTransferFromCallArgs, - TestIsolatedExchangeFillEventArgs as FillEventArgs, -} from '../src'; +import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper'; blockchainTests.resets.only('Isolated fillOrder() tests', env => { - const GOOD_SIGNATURE = '0x0101'; - const BAD_SIGNATURE = '0x0001'; const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24; const DEFAULT_ORDER: Order = { senderAddress: constants.NULL_ADDRESS, - makerAddress: addressUtils.generatePseudoRandomAddress(), + makerAddress: randomAddress(), takerAddress: constants.NULL_ADDRESS, makerFee: constants.ZERO_AMOUNT, takerFee: constants.ZERO_AMOUNT, @@ -37,96 +21,21 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => { salt: constants.ZERO_AMOUNT, feeRecipientAddress: constants.NULL_ADDRESS, expirationTimeSeconds: toBN(TOMORROW), - domain: { - verifyingContractAddress: constants.NULL_ADDRESS, - chainId: 1337, - }, }; let takerAddress: string; - let testExchange: TestIsolatedExchangeContract; - let logDecoder: LogDecoder; + let testExchange: IsolatedExchangeWrapper; let nextSaltValue = 1; before(async () => { - [ takerAddress ] = await env.getAccountAddressesAsync(); - testExchange = await TestIsolatedExchangeContract.deployFrom0xArtifactAsync( - artifacts.TestIsolatedExchange, - env.provider, - env.txDefaults, + [takerAddress] = await env.getAccountAddressesAsync(); + testExchange = await IsolatedExchangeWrapper.deployAsync( + env.web3Wrapper, + _.assign(env.txDefaults, { from: takerAddress }), ); - logDecoder = new LogDecoder(env.web3Wrapper, artifacts); }); - interface IsolatedExchangeAssetBalances { - [assetData: string]: {[address: string]: BigNumber}; - } - - interface IsolatedFillOrderAsyncResults { - fillResults: FillResults; - fillEventArgs: FillEventArgs; - transferFromCalls: DispatchTransferFromCallArgs[]; - balances: IsolatedExchangeAssetBalances; - } - - async function isolatedFillOrderAsync( - order: Order, - takerAssetFillAmount: BigNumber | number, - signature: string = GOOD_SIGNATURE, - ): Promise { - const _takerAssetFillAmount = toBN(takerAssetFillAmount); - // Call to get the return value. - const fillResults = await testExchange.fillOrder.callAsync( - order, - _takerAssetFillAmount, - signature, - ); - // Transact to execute it. - const receipt = await logDecoder.getTxWithDecodedLogsAsync( - await testExchange.fillOrder.sendTransactionAsync( - order, - _takerAssetFillAmount, - signature, - ), - ); - // Parse logs. - const fillEventArgs = (receipt.logs[0] as LogWithDecodedArgs).args; - const transferFromCalls = - (receipt.logs.slice(1) as Array>).map( - log => log.args, - ); - // 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: IsolatedExchangeAssetBalances = {}; - for (const assetData of assets) { - result[assetData] = _.zipObject( - addresses, - await testExchange.getMultipleRawAssetBalanceChanges.callAsync( - assetData, - addresses, - ), - ); - } - return result; - })(); - return { - fillResults, - fillEventArgs, - transferFromCalls, - balances, - }; - } - function createOrder(details: Partial = {}): Order { - return _.assign( - {}, - DEFAULT_ORDER, - { salt: toBN(nextSaltValue++) }, - details, - ); + return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details); } it('works', async () => { @@ -134,11 +43,15 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => { makerAssetAmount: toBN(1), takerAssetAmount: toBN(2), }); - const results = await isolatedFillOrderAsync(order, 2); - console.log(results); + const results = await testExchange.fillOrderAsync(order, 2); + console.log(results, testExchange.getOrderHash(order)); }); }); function toBN(num: BigNumber | string | number): BigNumber { return new BigNumber(num); } + +function randomAddress(): string { + return hexRandom(constants.ADDRESS_LENGTH); +} diff --git a/contracts/exchange/test/utils/isolated_exchange_wrapper.ts b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts new file mode 100644 index 0000000000..c20002e693 --- /dev/null +++ b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts @@ -0,0 +1,129 @@ +import { FillResults, filterLogsToArguments, LogDecoder, txDefaults as testTxDefaults } from '@0x/contracts-test-utils'; +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 * as _ from 'lodash'; + +import { + artifacts, + TestIsolatedExchangeContract, + TestIsolatedExchangeDispatchTransferFromCalledEventArgs as DispatchTransferFromCallArgs, + TestIsolatedExchangeFillEventArgs as FillEventArgs, +} from '../../src'; + +/** + * @dev Create a signature for the `TestIsolatedExchange` contract that will pass. + */ +export function createGoodSignature(type: SignatureType = SignatureType.EIP712): string { + return `0x01${Buffer.from([type]).toString('hex')}`; +} + +/** + * @dev Create a signature for the `TestIsolatedExchange` contract that will fail. + */ +export function createBadSignature(type: SignatureType = SignatureType.EIP712): string { + return `0x00${Buffer.from([type]).toString('hex')}`; +} + +export interface IsolatedAssetBalances { + [assetData: string]: { [address: string]: BigNumber }; +} + +export interface IsolatedFillOrderResults { + fillResults: FillResults; + fillEventArgs: FillEventArgs; + transferFromCalls: DispatchTransferFromCallArgs[]; + balances: IsolatedAssetBalances; +} + +export type Order = OrderWithoutDomain; + +export const DEFAULT_GOOD_SIGNATURE = createGoodSignature(); +export const DEFAULT_BAD_SIGNATURE = createBadSignature(); + +/** + * @dev Convenience wrapper for the `TestIsolatedExchange` contract. + */ +export class IsolatedExchangeWrapper { + public instance: TestIsolatedExchangeContract; + public logDecoder: LogDecoder; + + public static async deployAsync( + web3Wrapper: Web3Wrapper, + txDefaults: Partial = testTxDefaults, + ): Promise { + const provider = web3Wrapper.getProvider(); + const instance = await TestIsolatedExchangeContract.deployFrom0xArtifactAsync( + artifacts.TestIsolatedExchange, + provider, + txDefaults, + ); + return new IsolatedExchangeWrapper(web3Wrapper, instance); + } + + public static fromAddress( + address: string, + web3Wrapper: Web3Wrapper, + txDefaults: Partial = testTxDefaults, + ): IsolatedExchangeWrapper { + const provider = web3Wrapper.getProvider(); + const instance = new TestIsolatedExchangeContract(address, provider, txDefaults); + return new IsolatedExchangeWrapper(web3Wrapper, instance); + } + + public constructor(web3Wrapper: Web3Wrapper, instance: TestIsolatedExchangeContract) { + this.instance = instance; + this.logDecoder = new LogDecoder(web3Wrapper, artifacts); + } + + public async fillOrderAsync( + order: Order, + takerAssetFillAmount: BigNumber | number, + signature: string = DEFAULT_GOOD_SIGNATURE, + 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), + ); + // 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, + }; + } + + public getOrderHash(order: Order): string { + const domain = { + verifyingContractAddress: this.instance.address, + chainId: 1337, + }; + return orderHashUtils.getOrderHashHex(_.assign(order, { domain })); + } +}