diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/0x.ts | 25 | ||||
-rw-r--r-- | src/contract_wrappers/contract_wrapper.ts | 66 | ||||
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 23 | ||||
-rw-r--r-- | src/contract_wrappers/token_wrapper.ts | 24 | ||||
-rw-r--r-- | src/index.ts | 1 | ||||
-rw-r--r-- | src/types.ts | 14 | ||||
-rw-r--r-- | src/utils/abi_decoder.ts | 64 | ||||
-rw-r--r-- | src/web3_wrapper.ts | 22 | ||||
-rw-r--r-- | test/exchange_wrapper_test.ts | 74 | ||||
-rw-r--r-- | test/token_wrapper_test.ts | 54 | ||||
-rw-r--r-- | test/utils/token_utils.ts | 4 | ||||
-rw-r--r-- | yarn.lock | 151 |
14 files changed, 367 insertions, 165 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1072f24..b3ce29f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +v0.20.0 - _October 3, 2017_ + * Add `zeroEx.token.getLogsAsync` (#178) + * Add `zeroEx.exchange.getLogsAsync` (#178) + v0.19.0 - _September 29, 2017_ * Made order validation optional (#172) * Added Ropsten testnet support (#173) diff --git a/package.json b/package.json index 6a179f97e..a1382fb82 100644 --- a/package.json +++ b/package.json @@ -82,15 +82,15 @@ "tslint-config-0xproject": "^0.0.2", "typedoc": "^0.8.0", "types-bn": "^0.0.1", - "types-ethereumjs-util": "^0.0.5", + "types-ethereumjs-util": "0xProject/types-ethereumjs-util", "typescript": "^2.4.1", "web3-provider-engine": "^13.0.1", - "web3-typescript-typings": "^0.6.0", + "web3-typescript-typings": "^0.6.2", "webpack": "^3.1.0" }, "dependencies": { "@types/bignumber.js": "^4.0.2", - "0x-json-schemas": "^0.6.0", + "0x-json-schemas": "^0.6.1", "bignumber.js": "^4.0.2", "compare-versions": "^3.0.1", "es6-promisify": "^5.0.0", @@ -31,6 +31,8 @@ import { DecodedLogArgs, TransactionReceiptWithDecodedLogs, LogWithDecodedArgs, + FilterObject, + RawLog, } from './types'; import {zeroExConfigSchema} from './schemas/zero_ex_config_schema'; @@ -200,10 +202,16 @@ export class ZeroEx { this._web3Wrapper = new Web3Wrapper(provider, defaults); this.token = new TokenWrapper( this._web3Wrapper, + this._abiDecoder, this._getTokenTransferProxyAddressAsync.bind(this), ); const exchageContractAddressIfExists = _.isUndefined(config) ? undefined : config.exchangeContractAddress; - this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token, exchageContractAddressIfExists); + this.exchange = new ExchangeWrapper( + this._web3Wrapper, + this._abiDecoder, + this.token, + exchageContractAddressIfExists, + ); this.proxy = new TokenTransferProxyWrapper( this._web3Wrapper, this._getTokenTransferProxyAddressAsync.bind(this), @@ -300,17 +308,10 @@ export class ZeroEx { const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); if (!_.isNull(transactionReceipt)) { intervalUtils.clearAsyncExcludingInterval(intervalId); - const logsWithDecodedArgs = _.map(transactionReceipt.logs, (log: Web3.LogEntry) => { - const decodedLog = this._abiDecoder.decodeLog(log); - if (_.isUndefined(decodedLog)) { - return log; - } - const logWithDecodedArgs: LogWithDecodedArgs = { - ...log, - ...decodedLog, - }; - return logWithDecodedArgs; - }); + const logsWithDecodedArgs = _.map( + transactionReceipt.logs, + this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), + ); const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { ...transactionReceipt, logs: logsWithDecodedArgs, diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 2a55b53d9..743dfc9b2 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -1,13 +1,52 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; +import * as ethUtil from 'ethereumjs-util'; import {Web3Wrapper} from '../web3_wrapper'; -import {ZeroExError, Artifact} from '../types'; +import {AbiDecoder} from '../utils/abi_decoder'; +import { + InternalZeroExError, + Artifact, + LogWithDecodedArgs, + RawLog, + ContractEvents, + SubscriptionOpts, + IndexedFilterValues, +} from '../types'; import {utils} from '../utils/utils'; +const TOPIC_LENGTH = 32; + export class ContractWrapper { protected _web3Wrapper: Web3Wrapper; - constructor(web3Wrapper: Web3Wrapper) { + private _abiDecoder?: AbiDecoder; + constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) { this._web3Wrapper = web3Wrapper; + this._abiDecoder = abiDecoder; + } + protected async _getLogsAsync(address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi): Promise<LogWithDecodedArgs[]> { + const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi; + const eventSignature = this._getEventSignatureFromAbiByName(eventAbi, eventName); + const topicForEventSignature = this._web3Wrapper.keccak256(eventSignature); + const topicsForIndexedArgs = this._getTopicsForIndexedArgs(eventAbi, indexFilterValues); + const topics = [topicForEventSignature, ...topicsForIndexedArgs]; + const filter = { + fromBlock: subscriptionOpts.fromBlock, + toBlock: subscriptionOpts.toBlock, + address, + topics, + }; + const logs = await this._web3Wrapper.getLogsAsync(filter); + const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); + return logsWithDecodedArguments; + } + protected _tryToDecodeLogOrNoop(log: Web3.LogEntry): LogWithDecodedArgs|RawLog { + if (_.isUndefined(this._abiDecoder)) { + throw new Error(InternalZeroExError.NoAbiDecoder); + } + const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); + return logWithDecodedArgs; } protected async _instantiateContractIfExistsAsync<A extends Web3.ContractInstance>(artifact: Artifact, addressIfExists?: string, @@ -16,4 +55,27 @@ export class ContractWrapper { await this._web3Wrapper.getContractInstanceFromArtifactAsync<A>(artifact, addressIfExists); return contractInstance; } + protected _getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { + const types = _.map(eventAbi.inputs, 'type'); + const signature = `${eventAbi.name}(${types.join(',')})`; + return signature; + } + private _getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string|null> { + const topics: Array<string|null> = []; + for (const eventInput of abi.inputs) { + if (!eventInput.indexed) { + continue; + } + if (_.isUndefined(indexFilterValues[eventInput.name])) { + topics.push(null); + } else { + const value = indexFilterValues[eventInput.name] as string; + const buffer = ethUtil.toBuffer(value); + const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); + const topic = ethUtil.bufferToHex(paddedBuffer); + topics.push(topic); + } + } + return topics; + } } diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index d02a6e642..b3a35d5bf 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -30,6 +30,7 @@ import { MethodOpts, ValidateOrderFillableOpts, OrderTransactionOpts, + RawLog, } from '../types'; import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; @@ -39,6 +40,7 @@ import {ContractWrapper} from './contract_wrapper'; import {constants} from '../utils/constants'; import {TokenWrapper} from './token_wrapper'; import {decorators} from '../utils/decorators'; +import {AbiDecoder} from '../utils/abi_decoder'; import {artifacts} from '../artifacts'; const SHOULD_VALIDATE_BY_DEFAULT = true; @@ -79,8 +81,9 @@ export class ExchangeWrapper extends ContractWrapper { ]; return [orderAddresses, orderValues]; } - constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, contractAddressIfExists?: string) { - super(web3Wrapper); + constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, + tokenWrapper: TokenWrapper, contractAddressIfExists?: string) { + super(web3Wrapper, abiDecoder); this._tokenWrapper = tokenWrapper; this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this); this._exchangeLogEventEmitters = []; @@ -656,6 +659,22 @@ export class ExchangeWrapper extends ContractWrapper { return eventEmitter; } /** + * Gets historical logs without creating a subscription + * @param eventName The exchange contract event you would like to subscribe to. + * @param subscriptionOpts Subscriptions options that let you configure the subscription. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts, + indexFilterValues: IndexedFilterValues): Promise<LogWithDecodedArgs[]> { + const exchangeContractAddress = await this.getContractAddressAsync(); + const logs = await this._getLogsAsync( + exchangeContractAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.ExchangeArtifact.abi, + ); + return logs; + } + /** * Stops watching for all exchange events */ public async stopWatchingAllEventsAsync(): Promise<void> { diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index f7f0a0ce3..91af223e4 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -7,6 +7,7 @@ import {utils} from '../utils/utils'; import {eventUtils} from '../utils/event_utils'; import {constants} from '../utils/constants'; import {ContractWrapper} from './contract_wrapper'; +import {AbiDecoder} from '../utils/abi_decoder'; import {artifacts} from '../artifacts'; import { TokenContract, @@ -18,6 +19,8 @@ import { ContractEventEmitter, ContractEventObj, MethodOpts, + LogWithDecodedArgs, + RawLog, } from '../types'; const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155; @@ -32,8 +35,9 @@ export class TokenWrapper extends ContractWrapper { private _tokenContractsByAddress: {[address: string]: TokenContract}; private _tokenLogEventEmitters: ContractEventEmitter[]; private _tokenTransferProxyContractAddressFetcher: () => Promise<string>; - constructor(web3Wrapper: Web3Wrapper, tokenTransferProxyContractAddressFetcher: () => Promise<string>) { - super(web3Wrapper); + constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, + tokenTransferProxyContractAddressFetcher: () => Promise<string>) { + super(web3Wrapper, abiDecoder); this._tokenContractsByAddress = {}; this._tokenLogEventEmitters = []; this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher; @@ -277,6 +281,22 @@ export class TokenWrapper extends ContractWrapper { return eventEmitter; } /** + * Gets historical logs without creating a subscription + * @param tokenAddress An address of the token that emmited the logs. + * @param eventName The token contract event you would like to subscribe to. + * @param subscriptionOpts Subscriptions options that let you configure the subscription. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts, + indexFilterValues: IndexedFilterValues): Promise<LogWithDecodedArgs[]> { + const logs = await this._getLogsAsync( + tokenAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.TokenArtifact.abi, + ); + return logs; + } + /** * Stops watching for all token events */ public async stopWatchingAllEventsAsync(): Promise<void> { diff --git a/src/index.ts b/src/index.ts index 9730d3fef..3359743e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,4 +35,5 @@ export { DecodedLogArgs, MethodOpts, OrderTransactionOpts, + FilterObject, } from './types'; diff --git a/src/types.ts b/src/types.ts index 02230b0ab..35bb6af78 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,6 @@ export enum ZeroExError { UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', InvalidSignature = 'INVALID_SIGNATURE', ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', - ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', @@ -17,6 +16,11 @@ export enum ZeroExError { NoNetworkId = 'NO_NETWORK_ID', } +export enum InternalZeroExError { + NoAbiDecoder = 'NO_ABI_DECODER', + ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', +} + /** * Elliptic Curve signature */ @@ -198,6 +202,8 @@ export interface TokenTransferProxyContract extends Web3.ContractInstance { export enum SolidityTypes { Address = 'address', Uint256 = 'uint256', + Uint8 = 'uint8', + Uint = 'uint', } export enum ExchangeContractErrCodes { @@ -234,6 +240,8 @@ export enum ExchangeContractErrs { BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', } +export type RawLog = Web3.LogEntry; + export interface ContractEvent { logIndex: number; transactionIndex: number; @@ -338,6 +346,8 @@ export enum TokenEvents { Approval = 'Approval', } +export type ContractEvents = TokenEvents|ExchangeEvents; + export interface IndexedFilterValues { [index: string]: ContractEventArg; } @@ -460,3 +470,5 @@ export interface MethodOpts { export interface OrderTransactionOpts { shouldValidate: boolean; } + +export type FilterObject = Web3.FilterObject; diff --git a/src/utils/abi_decoder.ts b/src/utils/abi_decoder.ts index 61c8eecd4..542591251 100644 --- a/src/utils/abi_decoder.ts +++ b/src/utils/abi_decoder.ts @@ -1,7 +1,7 @@ import * as Web3 from 'web3'; import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import {AbiType, DecodedLogArgs, DecodedArgs} from '../types'; +import {AbiType, DecodedLogArgs, DecodedArgs, LogWithDecodedArgs, RawLog, SolidityTypes} from '../types'; import * as SolidityCoder from 'web3/lib/solidity/coder'; export class AbiDecoder { @@ -10,40 +10,40 @@ export class AbiDecoder { constructor(abiArrays: Web3.AbiDefinition[][]) { _.map(abiArrays, this.addABI.bind(this)); } - public decodeLog(logItem: Web3.LogEntry): DecodedArgs|undefined { - const methodId = logItem.topics[0]; + // This method can only decode logs from the 0x smart contracts + public tryToDecodeLogOrNoop(log: Web3.LogEntry): LogWithDecodedArgs|RawLog { + const methodId = log.topics[0]; const event = this.methodIds[methodId]; - if (!_.isUndefined(event)) { - const logData = logItem.data; - const decodedParams: DecodedLogArgs = {}; - let dataIndex = 0; - let topicsIndex = 1; + if (_.isUndefined(event)) { + return log; + } + const logData = log.data; + const decodedParams: DecodedLogArgs = {}; + let dataIndex = 0; + let topicsIndex = 1; - const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); - const dataTypes = _.map(nonIndexedInputs, input => input.type); - const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice(2)); - _.map(event.inputs, (param: Web3.EventParameter) => { - let value; - if (param.indexed) { - value = logItem.topics[topicsIndex]; - topicsIndex++; - } else { - value = decodedData[dataIndex]; - dataIndex++; - } - if (param.type === 'address') { - value = this.padZeros(new BigNumber(value).toString(16)); - } else if (param.type === 'uint256' || param.type === 'uint8' || param.type === 'int' ) { - value = new BigNumber(value); - } - decodedParams[param.name] = value; - }); + const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); + const dataTypes = _.map(nonIndexedInputs, input => input.type); + const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); - return { - event: event.name, - args: decodedParams, - }; - } + _.map(event.inputs, (param: Web3.EventParameter) => { + // Indexed parameters are stored in topics. Non-indexed ones in decodedData + let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; + if (param.type === SolidityTypes.Address) { + value = this.padZeros(new BigNumber(value).toString(16)); + } else if (param.type === SolidityTypes.Uint256 || + param.type === SolidityTypes.Uint8 || + param.type === SolidityTypes.Uint ) { + value = new BigNumber(value); + } + decodedParams[param.name] = value; + }); + + return { + ...log, + event: event.name, + args: decodedParams, + }; } private addABI(abiArray: Web3.AbiDefinition[]): void { _.map(abiArray, (abi: Web3.AbiDefinition) => { diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index 0bf80a6e4..7576f3d40 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -9,10 +9,12 @@ export class Web3Wrapper { private web3: Web3; private defaults: Partial<Web3.TxData>; private networkIdIfExists?: number; + private jsonRpcRequestId: number; constructor(provider: Web3.Provider, defaults: Partial<Web3.TxData>) { this.web3 = new Web3(); this.web3.setProvider(provider); this.defaults = defaults; + this.jsonRpcRequestId = 0; } public setProvider(provider: Web3.Provider) { delete this.networkIdIfExists; @@ -100,6 +102,20 @@ export class Web3Wrapper { const addresses: string[] = await promisify(this.web3.eth.getAccounts)(); return addresses; } + public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> { + const payload = { + jsonrpc: '2.0', + id: this.jsonRpcRequestId++, + method: 'eth_getLogs', + params: [filter], + }; + const logs = await this.sendRawPayloadAsync(payload); + return logs; + } + public keccak256(data: string): string { + const hash = this.web3.sha3(data); + return hash; + } private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A { const web3ContractInstance = this.web3.eth.contract(abi).at(address); const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A; @@ -109,4 +125,10 @@ export class Web3Wrapper { const networkId = await promisify(this.web3.version.getNetwork)(); return networkId; } + private async sendRawPayloadAsync(payload: Web3.JSONRPCRequestPayload): Promise<any> { + const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider); + const response = await promisify(sendAsync)(payload); + const result = response.result; + return result; + } } diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 45a2d3907..71c5713ad 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -771,4 +771,78 @@ describe('ExchangeWrapper', () => { expect(zrxAddress).to.equal(zrxToken.address); }); }); + describe('#getLogsAsync', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + const subscriptionOpts: SubscriptionOpts = { + fromBlock: 'earliest', + toBlock: 'latest', + }; + let txHash: string; + before(async () => { + [, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + it('should get logs with decoded args emitted by LogFill', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(eventName, subscriptionOpts, indexFilterValues); + expect(logs).to.have.length(1); + expect(logs[0].event).to.be.equal(eventName); + }); + it('should only get the logs with the correct event name', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = ExchangeEvents.LogCancel; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(differentEventName, subscriptionOpts, indexFilterValues); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + + const differentMakerAddress = userAddresses[2]; + const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, differentMakerAddress, takerAddress, fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + anotherSignedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = { + maker: differentMakerAddress, + }; + const logs = await zeroEx.exchange.getLogsAsync( + eventName, subscriptionOpts, indexFilterValues, + ); + expect(logs).to.have.length(1); + expect(logs[0].args.maker).to.be.equal(differentMakerAddress); + }); + }); }); diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index 68dca0769..da020f714 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -14,6 +14,7 @@ import { ContractEvent, TransferContractEventArgs, ApprovalContractEventArgs, + LogWithDecodedArgs, } from '../src'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; import {TokenUtils} from './utils/token_utils'; @@ -435,4 +436,57 @@ describe('TokenWrapper', () => { })().catch(done); }); }); + describe('#getLogsAsync', () => { + let tokenAddress: string; + let tokenTransferProxyAddress: string; + const subscriptionOpts: SubscriptionOpts = { + fromBlock: 'earliest', + toBlock: 'latest', + }; + let txHash: string; + before(async () => { + const token = tokens[0]; + tokenAddress = token.address; + tokenTransferProxyAddress = await zeroEx.proxy.getContractAddressAsync(); + }); + it('should get logs with decoded args emitted by Approval', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync( + tokenAddress, eventName, subscriptionOpts, indexFilterValues, + ); + expect(logs).to.have.length(1); + expect(logs[0].event).to.be.equal(eventName); + expect(logs[0].args._owner).to.be.equal(coinbase); + expect(logs[0].args._spender).to.be.equal(tokenTransferProxyAddress); + expect(logs[0].args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should only get the logs with the correct event name', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = TokenEvents.Transfer; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync( + tokenAddress, differentEventName, subscriptionOpts, indexFilterValues, + ); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = { + _owner: coinbase, + }; + const logs = await zeroEx.token.getLogsAsync( + tokenAddress, eventName, subscriptionOpts, indexFilterValues, + ); + expect(logs).to.have.length(1); + expect(logs[0].args._owner).to.be.equal(coinbase); + }); + }); }); diff --git a/test/utils/token_utils.ts b/test/utils/token_utils.ts index 60cf4527b..51cb9411c 100644 --- a/test/utils/token_utils.ts +++ b/test/utils/token_utils.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import {Token, ZeroExError} from '../../src'; +import {Token, InternalZeroExError} from '../../src/types'; const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; @@ -11,7 +11,7 @@ export class TokenUtils { public getProtocolTokenOrThrow(): Token { const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL}); if (_.isUndefined(zrxToken)) { - throw new Error(ZeroExError.ZrxNotInTokenRegistry); + throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); } return zrxToken; } @@ -2,9 +2,9 @@ # yarn lockfile v1 -"0x-json-schemas@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/0x-json-schemas/-/0x-json-schemas-0.5.1.tgz#fce6c2962e44930884a838e01f5b605ddd217686" +"0x-json-schemas@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/0x-json-schemas/-/0x-json-schemas-0.6.1.tgz#ff1b09542422dd9292dd7618a7c5c21d41c99cbf" dependencies: jsonschema "^1.2.0" @@ -1000,10 +1000,6 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1046,7 +1042,7 @@ chai@^4.0.1: pathval "^1.0.0" type-detect "^4.0.0" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1222,15 +1218,15 @@ core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -coveralls@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.1.tgz#d70bb9acc1835ec4f063ff9dac5423c17b11f178" +coveralls@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.0.tgz#22ef730330538080d29b8c151dc9146afde88a99" dependencies: - js-yaml "3.6.1" - lcov-parse "0.0.10" - log-driver "1.2.5" - minimist "1.2.0" - request "2.79.0" + js-yaml "^3.6.1" + lcov-parse "^0.0.10" + log-driver "^1.2.5" + minimist "^1.2.0" + request "^2.79.0" create-ecdh@^4.0.0: version "4.0.0" @@ -1608,9 +1604,9 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" -esprima@^2.6.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" esrecurse@^4.1.0: version "4.2.0" @@ -2050,16 +2046,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -2185,15 +2171,6 @@ har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" @@ -2494,15 +2471,6 @@ is-hex-prefixed@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" -is-my-json-valid@^2.12.4: - version "2.16.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -2535,10 +2503,6 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - is-regex@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -2653,12 +2617,12 @@ js-tokens@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" +js-yaml@^3.6.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: argparse "^1.0.7" - esprima "^2.6.0" + esprima "^4.0.0" jsbn@~0.1.0: version "0.1.1" @@ -2728,10 +2692,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - jsonschema@*, jsonschema@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.0.tgz#d6ebaf70798db7b3a20c544f6c9ef9319b077de2" @@ -2800,7 +2760,7 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lcov-parse@0.0.10: +lcov-parse@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" @@ -2915,6 +2875,10 @@ lodash.create@3.1.1: lodash._basecreate "^3.0.0" lodash._isiterateecall "^3.0.0" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -2939,7 +2903,7 @@ lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -log-driver@1.2.5: +log-driver@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" @@ -3162,7 +3126,7 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -3236,14 +3200,15 @@ native-promise-only@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" -nise@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.0.1.tgz#0da92b10a854e97c0f496f6c2845a301280b3eef" +nise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.1.0.tgz#37e41b9bf0041ccb83d1bf03e79440bbc0db10ad" dependencies: formatio "^1.2.0" just-extend "^1.1.22" lolex "^1.6.0" path-to-regexp "^1.7.0" + text-encoding "^0.6.4" node-abi@^2.0.0: version "2.0.3" @@ -3781,10 +3746,6 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" @@ -3997,32 +3958,7 @@ request-promise-native@^1.0.4: stealthy-require "^1.1.0" tough-cookie ">=2.3.0" -request@2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - -request@^2.54.0, request@^2.67.0, request@^2.81.0: +request@^2.54.0, request@^2.67.0, request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -4255,15 +4191,16 @@ single-line-log@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-0.4.1.tgz#87a55649f749d783ec0dcd804e8140d9873c7cee" -sinon@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-3.2.0.tgz#8848a66ab6e8b80b5532e3824f59f83ea2628c77" +sinon@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.0.0.tgz#a54a5f0237aa1dd2215e5e81c89b42b50c4fdb6b" dependencies: diff "^3.1.0" formatio "1.2.0" + lodash.get "^4.4.2" lolex "^2.1.2" native-promise-only "^0.8.1" - nise "^1.0.1" + nise "^1.1.0" path-to-regexp "^1.7.0" samsam "^1.1.3" text-encoding "0.6.4" @@ -4637,7 +4574,7 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" -text-encoding@0.6.4: +text-encoding@0.6.4, text-encoding@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" @@ -4781,10 +4718,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -4829,9 +4762,9 @@ types-bn@^0.0.1: dependencies: bn.js "4.11.7" -types-ethereumjs-util@^0.0.5: +types-ethereumjs-util@0xProject/types-ethereumjs-util: version "0.0.5" - resolved "https://registry.yarnpkg.com/types-ethereumjs-util/-/types-ethereumjs-util-0.0.5.tgz#a65060741c73d1ee5157b9ba2d502b4fe4a19d1c" + resolved "https://codeload.github.com/0xProject/types-ethereumjs-util/tar.gz/b9ae55d2c2711d89f63f7fc53a78579f2d4fbd74" dependencies: bn.js "^4.11.7" buffer "^5.0.6" @@ -5005,9 +4938,9 @@ web3-provider-engine@^8.4.0: xhr "^2.2.0" xtend "^4.0.1" -web3-typescript-typings@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/web3-typescript-typings/-/web3-typescript-typings-0.6.0.tgz#ed8f01cfa3ef2c7ff2f1b0cafe7c682d0c6dc333" +web3-typescript-typings@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/web3-typescript-typings/-/web3-typescript-typings-0.6.2.tgz#5dd9bf4dcd1d6dd6897c87d055d1f5cc8f98dfbd" dependencies: bignumber.js "^4.0.2" |