aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/0x.ts25
-rw-r--r--src/contract_wrappers/contract_wrapper.ts66
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts23
-rw-r--r--src/contract_wrappers/token_wrapper.ts24
-rw-r--r--src/index.ts1
-rw-r--r--src/types.ts14
-rw-r--r--src/utils/abi_decoder.ts64
-rw-r--r--src/web3_wrapper.ts22
8 files changed, 188 insertions, 51 deletions
diff --git a/src/0x.ts b/src/0x.ts
index e6fdf68e1..3180c52f6 100644
--- a/src/0x.ts
+++ b/src/0x.ts
@@ -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;
+ }
}