@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:
parent
1030c96eec
commit
039cc6e28b
@ -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',
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user