From e2bd80253b871a949d4c730082abb5c3b7fbf6ba Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 31 Jul 2019 10:24:00 -0400 Subject: [PATCH] `@0x/contracts-exchange`: More `TestIsolatedExchange` rework. --- .../contracts/test/TestIsolatedExchange.sol | 42 +----- .../exchange/test/isolated_fill_order.ts | 2 - .../test/utils/isolated_exchange_wrapper.ts | 141 ++++++++---------- 3 files changed, 61 insertions(+), 124 deletions(-) diff --git a/contracts/exchange/contracts/test/TestIsolatedExchange.sol b/contracts/exchange/contracts/test/TestIsolatedExchange.sol index 4d53567334..04c531a57b 100644 --- a/contracts/exchange/contracts/test/TestIsolatedExchange.sol +++ b/contracts/exchange/contracts/test/TestIsolatedExchange.sol @@ -36,38 +36,13 @@ contract TestIsolatedExchange is uint256 amount ); - /// @dev Raw asset balances of addresses based on asset data hash. - /// These start at 0 and are allowed to be negative. - /// Updated by `_dispatchTransferFrom()`. - mapping(bytes32 => mapping(address => int256)) public rawAssetBalances; - // solhint-disable no-empty-blocks constructor () public Exchange(1337) {} - /// @dev Convenience function to get the `rawAssetBalances` for - /// multiple assets and addresses. - function getRawAssetBalances( - bytes[] calldata assets, - address[] calldata addresses - ) - external - returns (int256[][] memory balances) - { - 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]]; - } - } - } - - /// @dev Overriden to only log arguments and track raw asset balances. + /// @dev Overriden to only log arguments. function _dispatchTransferFrom( bytes32 orderHash, bytes memory assetData, @@ -84,11 +59,6 @@ contract TestIsolatedExchange is to, amount ); - - mapping(address => int256) storage assetBalances = - rawAssetBalances[keccak256(assetData)]; - assetBalances[from] = _subAssetAmount(assetBalances[from], amount); - assetBalances[to] = _addAssetAmount(assetBalances[to], amount); } /// @dev Overriden to simplify signature validation. @@ -106,14 +76,4 @@ contract TestIsolatedExchange is // '0x01' in the first byte is valid. return signature.length == 2 && signature[0] == 0x01; } - - function _subAssetAmount(int256 a, uint256 b) private pure returns (int256 r) { - r = a - int256(b); - require(r <= a, "ASSET_AMOUNT_UNDERFLOW"); - } - - function _addAssetAmount(int256 a, uint256 b) private pure returns (int256 r) { - r = a + int256(b); - require(r >= a, "ASSET_AMOUNT_OVERFLOW"); - } } diff --git a/contracts/exchange/test/isolated_fill_order.ts b/contracts/exchange/test/isolated_fill_order.ts index b1f1cf53f7..9c0079d2b5 100644 --- a/contracts/exchange/test/isolated_fill_order.ts +++ b/contracts/exchange/test/isolated_fill_order.ts @@ -4,7 +4,6 @@ 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; @@ -47,7 +46,6 @@ blockchainTests.resets.only('Isolated fillOrder() tests', env => { takerAssetAmount: toBN(2), }); const results = await testExchange.fillOrderAsync(order, 2); - // console.log(results, testExchange.getOrderHash(order)); }); } }); diff --git a/contracts/exchange/test/utils/isolated_exchange_wrapper.ts b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts index d6c6af1da2..1572dcb807 100644 --- a/contracts/exchange/test/utils/isolated_exchange_wrapper.ts +++ b/contracts/exchange/test/utils/isolated_exchange_wrapper.ts @@ -1,4 +1,4 @@ -import { FillResults, filterLogsToArguments, LogDecoder, txDefaults as testTxDefaults } from '@0x/contracts-test-utils'; +import { constants, 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'; @@ -13,20 +13,6 @@ import { 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 AssetBalances { [assetData: string]: { [address: string]: BigNumber }; } @@ -36,29 +22,11 @@ export interface IsolatedExchangeEvents { transferFromCalls: DispatchTransferFromCallArgs[]; } -export interface EventsAndBalances { - events: IsolatedExchangeEvents; - balances: AssetBalances; -} - -export interface IsolatedFillOrderResults extends EventsAndBalances { - fillResults: FillResults; -} - 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. */ @@ -66,6 +34,8 @@ export class IsolatedExchangeWrapper { public static readonly CHAIN_ID = 1337; public instance: TestIsolatedExchangeContract; public logDecoder: LogDecoder; + public lastTxEvents: IsolatedExchangeEvents = createEmptyEvents(); + public lastTxBalanceChanges: AssetBalances = {}; public static async deployAsync( web3Wrapper: Web3Wrapper, @@ -100,20 +70,14 @@ export class IsolatedExchangeWrapper { takerAssetFillAmount: BigNumber | number, signature: string = DEFAULT_GOOD_SIGNATURE, txOpts?: TxData, - ): Promise { - const _takerAssetFillAmount = new BigNumber(takerAssetFillAmount); - const results = await this._callAndSendExchangeFunctionAsync( + ): Promise { + return this._callAndSendExchangeFunctionAsync( this.instance.fillOrder, order, - _takerAssetFillAmount, + new BigNumber(takerAssetFillAmount), signature, txOpts, ); - return { - fillResults: results.result, - events: results.events, - balances: results.balances, - }; } public getOrderHash(order: Order): string { @@ -124,61 +88,60 @@ export class IsolatedExchangeWrapper { 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]; + public getBalanceChange(assetData: string, address: string): BigNumber { + if (assetData in this.lastTxBalanceChanges) { + const balances = this.lastTxBalanceChanges[assetData]; + if (address in balances) { + return balances[address]; } } - 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); + return constants.ZERO_AMOUNT; } protected async _callAndSendExchangeFunctionAsync( instanceMethod: TransactionContractFunction, // tslint:disable-next-line: trailing-comma ...args: any[] - ): Promise> { + ): Promise { + this.lastTxEvents = createEmptyEvents(); + this.lastTxBalanceChanges = {}; // Call to get the return value. - const result = await instanceMethod.callAsync.call(this.instance, ...args); + const result = await instanceMethod.callAsync(...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, - }; + this.lastTxEvents = extractEvents(receipt.logs); + this.lastTxBalanceChanges = getBalanceChangesFromTransferFromCalls( + this.lastTxEvents.transferFromCalls, + ); + return result; } } +interface TransactionContractFunction { + callAsync: (...args: any[]) => Promise; + sendTransactionAsync: (...args: any[]) => Promise; +} + +/** + * @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')}`; +} + +function createEmptyEvents(): IsolatedExchangeEvents { + return { fillEvents: [], transferFromCalls: [] }; +} + function extractEvents(logs: LogEntry[]): IsolatedExchangeEvents { return { fillEvents: filterLogsToArguments(logs, 'Fill'), @@ -188,3 +151,19 @@ function extractEvents(logs: LogEntry[]): IsolatedExchangeEvents { ), }; } + +// Executes transferFrom calls to compute relative balances for addresses. +function getBalanceChangesFromTransferFromCalls( + calls: DispatchTransferFromCallArgs[], +): AssetBalances { + const changes: AssetBalances = {}; + for (const call of calls) { + const { assetData, from, to, amount } = call; + const balances = changes[assetData] = changes[assetData ] || {}; + const fromBalance = balances[from] || constants.ZERO_AMOUNT; + const toBalance = balances[to] || constants.ZERO_AMOUNT; + balances[from] = fromBalance.minus(amount); + balances[to] = toBalance.plus(amount); + } + return changes; +}