aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/utils/filter_utils.ts
blob: c5df7321e079ec00023d09e3f47aec6cf8dc4133 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import {
    ConstructorAbi,
    ContractAbi,
    EventAbi,
    FallbackAbi,
    FilterObject,
    LogEntry,
    MethodAbi,
} from '@0xproject/types';
import * as ethUtil from 'ethereumjs-util';
import * as jsSHA3 from 'js-sha3';
import * as _ from 'lodash';
import * as uuid from 'uuid/v4';

import { BlockRange, ContractEvents, IndexedFilterValues } from '../types';

const TOPIC_LENGTH = 32;

export const filterUtils = {
    generateUUID(): string {
        return uuid();
    },
    getFilter(
        address: string,
        eventName: ContractEvents,
        indexFilterValues: IndexedFilterValues,
        abi: ContractAbi,
        blockRange?: BlockRange,
    ): FilterObject {
        const eventAbi = _.find(abi, { name: eventName }) as EventAbi;
        const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
        const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
        const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
        const topics = [topicForEventSignature, ...topicsForIndexedArgs];
        let filter: FilterObject = {
            address,
            topics,
        };
        if (!_.isUndefined(blockRange)) {
            filter = {
                ...blockRange,
                ...filter,
            };
        }
        return filter;
    },
    getEventSignatureFromAbiByName(eventAbi: EventAbi, eventName: ContractEvents): string {
        const types = _.map(eventAbi.inputs, 'type');
        const signature = `${eventAbi.name}(${types.join(',')})`;
        return signature;
    },
    getTopicsForIndexedArgs(abi: 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])) {
                // Null is a wildcard topic in a JSON-RPC call
                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;
    },
    matchesFilter(log: LogEntry, filter: FilterObject): boolean {
        if (!_.isUndefined(filter.address) && log.address !== filter.address) {
            return false;
        }
        if (!_.isUndefined(filter.topics)) {
            return filterUtils.matchesTopics(log.topics, filter.topics);
        }
        return true;
    },
    matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
        const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
        const matchesTopics = _.every(matchesTopic);
        return matchesTopics;
    },
    matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
        if (_.isArray(filterTopic)) {
            return _.includes(filterTopic, logTopic);
        }
        if (_.isString(filterTopic)) {
            return filterTopic === logTopic;
        }
        // null topic is a wildcard
        return true;
    },
};