1 | const { assert, format } = require('../util');
|
2 | const HexStream = require('../util/HexStream');
|
3 | const namedTuple = require('../util/namedTuple');
|
4 | const abiCoder = require('../abi');
|
5 | const { signature, formatSignature, formatFullName } = require('./signature');
|
6 |
|
7 | class EventCoder {
|
8 | /**
|
9 | * Event coder
|
10 | *
|
11 | * @param options {object}
|
12 | * @param options.anonymous {boolean}
|
13 | * @param options.name {string}
|
14 | * @param options.inputs {array}
|
15 | *
|
16 | * @example
|
17 | * > abi = {
|
18 | name: 'EventName',
|
19 | anonymous: false,
|
20 | inputs: [
|
21 | {
|
22 | indexed: true,
|
23 | name: 'account',
|
24 | type: 'address',
|
25 | },
|
26 | {
|
27 | indexed: false,
|
28 | name: 'number',
|
29 | type: 'uint',
|
30 | },
|
31 | ],
|
32 | }
|
33 | * > coder = new EventCoder(abi)
|
34 | EventCoder {
|
35 | anonymous: false,
|
36 | name: 'EventName',
|
37 | inputs: [
|
38 | { indexed: true, name: 'account', type: 'address' },
|
39 | { indexed: false, name: 'number', type: 'uint' }
|
40 | ],
|
41 | type: 'EventName(address,uint256)',
|
42 | NamedTuple: [Function: NamedTuple(account,number)]
|
43 | }
|
44 | */
|
45 | constructor({ anonymous, name, inputs = [] } = {}) {
|
46 | this.anonymous = anonymous;
|
47 | this.name = name; // example: "Event"
|
48 | this.fullName = formatFullName({ name, inputs }); // example: "Event(address)"
|
49 | this.type = formatSignature({ name, inputs }); // example: "Event(address indexed account)"
|
50 | this.signature = signature(this.type); // example: "0x50d7c806d0f7913f321946784dee176a42aa55b5dd83371fc57dcedf659085e0"
|
51 |
|
52 | this.inputs = inputs;
|
53 | this.dataCoder = abiCoder({ type: 'tuple', components: inputs.filter(component => !component.indexed) });
|
54 |
|
55 | this.NamedTuple = namedTuple(...inputs.map((input, index) => input.name || `${index}`));
|
56 | }
|
57 |
|
58 | /**
|
59 | * Encode topics by params
|
60 | *
|
61 | * @param array {*[]}
|
62 | * @return {string[]}
|
63 | * @example
|
64 | * > coder = new EventCoder(abi)
|
65 | * > coder.encodeTopics(['0x0123456789012345678901234567890123456789', null])
|
66 | ['0x0000000000000000000000000123456789012345678901234567890123456789']
|
67 | */
|
68 | encodeTopics(array) {
|
69 | assert(array.length === this.inputs.length, {
|
70 | message: 'length not match',
|
71 | expect: this.inputs.length,
|
72 | got: array.length,
|
73 | coder: this,
|
74 | });
|
75 |
|
76 | const topics = [];
|
77 | this.inputs.forEach((component, index) => {
|
78 | if (component.indexed) {
|
79 | const value = array[index];
|
80 |
|
81 | topics.push(value === null ? null : format.hex(abiCoder(component).encodeIndex(value)));
|
82 | }
|
83 | });
|
84 |
|
85 | return topics;
|
86 | }
|
87 |
|
88 | /**
|
89 | * Decode log
|
90 | *
|
91 | * @param topics {array} - Array of hex sting
|
92 | * @param data {string} - Hex string
|
93 | * @return {array} NamedTuple
|
94 | *
|
95 | * @example
|
96 | * > coder = new EventCoder(abi)
|
97 | * > result = coder.decodeLog({
|
98 | data: '0x000000000000000000000000000000000000000000000000000000000000000a',
|
99 | topics: [
|
100 | '0xb0333e0e3a6b99318e4e2e0d7e5e5f93646f9cbf62da1587955a4092bf7df6e7',
|
101 | '0x0000000000000000000000000123456789012345678901234567890123456789',
|
102 | ],
|
103 | })
|
104 | NamedTuple(account,number) [ '0x0123456789012345678901234567890123456789', 10n ]
|
105 | * > console.log([...result])
|
106 | [ 0x0123456789012345678901234567890123456789, 10n ]
|
107 | * > console.log(result.account) // `account` a field name in abi
|
108 | "0x0123456789012345678901234567890123456789"
|
109 | * > console.log(result.number) // `number` a field name in abi
|
110 | 10n
|
111 | */
|
112 | decodeLog({ topics, data }) {
|
113 | assert(this.anonymous || topics[0] === this.signature, {
|
114 | message: 'decodeLog unexpected topic',
|
115 | expect: this.signature,
|
116 | got: topics[0],
|
117 | coder: this,
|
118 | });
|
119 |
|
120 | const stream = new HexStream(data);
|
121 | const notIndexedNamedTuple = this.dataCoder.decode(stream);
|
122 |
|
123 | assert(stream.eof(), {
|
124 | message: 'hex length to large',
|
125 | expect: `${stream.string.length}`,
|
126 | got: stream.index,
|
127 | coder: this,
|
128 | });
|
129 |
|
130 | let offset = this.anonymous ? 0 : 1;
|
131 | const array = this.inputs.map(component => {
|
132 | if (component.indexed) {
|
133 | return abiCoder(component).decodeIndex(topics[offset++]); // eslint-disable-line no-plusplus
|
134 | } else {
|
135 | return notIndexedNamedTuple[component.name];
|
136 | }
|
137 | });
|
138 |
|
139 | return new this.NamedTuple(...array);
|
140 | }
|
141 | }
|
142 |
|
143 | module.exports = EventCoder;
|