@0x/contracts-exchange: Create IsolatedExchangeWrapper class.

This commit is contained in:
Lawrence Forman 2019-07-30 16:57:29 -04:00
parent 0851c5ac8e
commit 1030c96eec
3 changed files with 145 additions and 103 deletions

View File

@ -49,7 +49,7 @@ contract TestIsolatedExchange is
/// @dev Convenience function to get the `rawAssetBalances` for /// @dev Convenience function to get the `rawAssetBalances` for
/// multiple addresses. /// multiple addresses.
function getMultipleRawAssetBalanceChanges( function getRawAssetBalances(
bytes calldata assetData, bytes calldata assetData,
address[] calldata addresses address[] calldata addresses
) )

View File

@ -1,30 +1,14 @@
import { import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
addressUtils,
blockchainTests,
constants,
expect,
FillResults,
LogDecoder,
} from '@0x/contracts-test-utils';
import { Order } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper';
artifacts,
TestIsolatedExchangeContract,
TestIsolatedExchangeDispatchTransferFromCalledEventArgs as DispatchTransferFromCallArgs,
TestIsolatedExchangeFillEventArgs as FillEventArgs,
} from '../src';
blockchainTests.resets.only('Isolated fillOrder() tests', env => { 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 TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
const DEFAULT_ORDER: Order = { const DEFAULT_ORDER: Order = {
senderAddress: constants.NULL_ADDRESS, senderAddress: constants.NULL_ADDRESS,
makerAddress: addressUtils.generatePseudoRandomAddress(), makerAddress: randomAddress(),
takerAddress: constants.NULL_ADDRESS, takerAddress: constants.NULL_ADDRESS,
makerFee: constants.ZERO_AMOUNT, makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT, takerFee: constants.ZERO_AMOUNT,
@ -37,96 +21,21 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
salt: constants.ZERO_AMOUNT, salt: constants.ZERO_AMOUNT,
feeRecipientAddress: constants.NULL_ADDRESS, feeRecipientAddress: constants.NULL_ADDRESS,
expirationTimeSeconds: toBN(TOMORROW), expirationTimeSeconds: toBN(TOMORROW),
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 1337,
},
}; };
let takerAddress: string; let takerAddress: string;
let testExchange: TestIsolatedExchangeContract; let testExchange: IsolatedExchangeWrapper;
let logDecoder: LogDecoder;
let nextSaltValue = 1; let nextSaltValue = 1;
before(async () => { before(async () => {
[takerAddress] = await env.getAccountAddressesAsync(); [takerAddress] = await env.getAccountAddressesAsync();
testExchange = await TestIsolatedExchangeContract.deployFrom0xArtifactAsync( testExchange = await IsolatedExchangeWrapper.deployAsync(
artifacts.TestIsolatedExchange, env.web3Wrapper,
env.provider, _.assign(env.txDefaults, { from: takerAddress }),
env.txDefaults,
); );
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<IsolatedFillOrderAsyncResults> {
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<FillEventArgs>).args;
const transferFromCalls =
(receipt.logs.slice(1) as Array<LogWithDecodedArgs<DispatchTransferFromCallArgs>>).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> = {}): Order { function createOrder(details: Partial<Order> = {}): Order {
return _.assign( return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
{},
DEFAULT_ORDER,
{ salt: toBN(nextSaltValue++) },
details,
);
} }
it('works', async () => { it('works', async () => {
@ -134,11 +43,15 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => {
makerAssetAmount: toBN(1), makerAssetAmount: toBN(1),
takerAssetAmount: toBN(2), takerAssetAmount: toBN(2),
}); });
const results = await isolatedFillOrderAsync(order, 2); const results = await testExchange.fillOrderAsync(order, 2);
console.log(results); console.log(results, testExchange.getOrderHash(order));
}); });
}); });
function toBN(num: BigNumber | string | number): BigNumber { function toBN(num: BigNumber | string | number): BigNumber {
return new BigNumber(num); return new BigNumber(num);
} }
function randomAddress(): string {
return hexRandom(constants.ADDRESS_LENGTH);
}

View File

@ -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<TxData> = testTxDefaults,
): Promise<IsolatedExchangeWrapper> {
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<TxData> = 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<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),
);
// 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,
};
}
public getOrderHash(order: Order): string {
const domain = {
verifyingContractAddress: this.instance.address,
chainId: 1337,
};
return orderHashUtils.getOrderHashHex(_.assign(order, { domain }));
}
}