UNPKG

7.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.AbiDecoder = void 0;
4const ethereum_types_1 = require("ethereum-types");
5const ethers = require("ethers");
6const _ = require("lodash");
7const _1 = require(".");
8/**
9 * AbiDecoder allows you to decode event logs given a set of supplied contract ABI's. It takes the contract's event
10 * signature from the ABI and attempts to decode the logs using it.
11 */
12class AbiDecoder {
13 /**
14 * Instantiate an AbiDecoder
15 * @param abiArrays An array of contract ABI's
16 * @return AbiDecoder instance
17 */
18 constructor(abiArrays) {
19 this._eventIds = {};
20 this._selectorToFunctionInfo = {};
21 _.each(abiArrays, abi => {
22 this.addABI(abi);
23 });
24 }
25 /**
26 * Retrieves the function selector from calldata.
27 * @param calldata hex-encoded calldata.
28 * @return hex-encoded function selector.
29 */
30 static _getFunctionSelector(calldata) {
31 const functionSelectorLength = 10;
32 if (!calldata.startsWith('0x') || calldata.length < functionSelectorLength) {
33 throw new Error(`Malformed calldata. Must include a hex prefix '0x' and 4-byte function selector. Got '${calldata}'`);
34 }
35 const functionSelector = calldata.substr(0, functionSelectorLength);
36 return functionSelector;
37 }
38 /**
39 * Attempt to decode a log given the ABI's the AbiDecoder knows about.
40 * @param log The log to attempt to decode
41 * @return The decoded log if the requisite ABI was available. Otherwise the log unaltered.
42 */
43 tryToDecodeLogOrNoop(log) {
44 // Lookup event corresponding to log
45 const eventId = log.topics[0];
46 const numIndexedArgs = log.topics.length - 1;
47 if (this._eventIds[eventId] === undefined || this._eventIds[eventId][numIndexedArgs] === undefined) {
48 return log;
49 }
50 const event = this._eventIds[eventId][numIndexedArgs];
51 // Create decoders for indexed data
52 const indexedDataDecoders = _.mapValues(_.filter(event.inputs, { indexed: true }), input =>
53 // tslint:disable:next-line no-unnecessary-type-assertion
54 _1.AbiEncoder.create(input));
55 // Decode indexed data
56 const decodedIndexedData = _.map(log.topics.slice(1), // ignore first topic, which is the event id.
57 (input, i) => indexedDataDecoders[i].decode(input));
58 // Decode non-indexed data
59 const decodedNonIndexedData = _1.AbiEncoder.create(_.filter(event.inputs, { indexed: false })).decodeAsArray(log.data);
60 // Construct DecodedLogArgs struct by mapping event parameters to their respective decoded argument.
61 const decodedArgs = {};
62 let indexedOffset = 0;
63 let nonIndexedOffset = 0;
64 for (const param of event.inputs) {
65 const value = param.indexed
66 ? decodedIndexedData[indexedOffset++]
67 : decodedNonIndexedData[nonIndexedOffset++];
68 if (value === undefined) {
69 return log;
70 }
71 decodedArgs[param.name] = value;
72 }
73 // Decoding was successful. Return decoded log.
74 return Object.assign(Object.assign({}, log), { event: event.name, args: decodedArgs });
75 }
76 /**
77 * Decodes calldata for a known ABI.
78 * @param calldata hex-encoded calldata.
79 * @param contractName used to disambiguate similar ABI's (optional).
80 * @return Decoded calldata. Includes: function name and signature, along with the decoded arguments.
81 */
82 decodeCalldataOrThrow(calldata, contractName) {
83 const functionSelector = AbiDecoder._getFunctionSelector(calldata);
84 const candidateFunctionInfos = this._selectorToFunctionInfo[functionSelector];
85 if (candidateFunctionInfos === undefined) {
86 throw new Error(`No functions registered for selector '${functionSelector}'`);
87 }
88 const functionInfo = _.find(candidateFunctionInfos, candidateFunctionInfo => {
89 return (contractName === undefined || _.toLower(contractName) === _.toLower(candidateFunctionInfo.contractName));
90 });
91 if (functionInfo === undefined) {
92 throw new Error(`No function registered with selector ${functionSelector} and contract name ${contractName}.`);
93 }
94 else if (functionInfo.abiEncoder === undefined) {
95 throw new Error(`Function ABI Encoder is not defined, for function registered with selector ${functionSelector} and contract name ${contractName}.`);
96 }
97 const functionName = functionInfo.abiEncoder.getDataItem().name;
98 const functionSignature = functionInfo.abiEncoder.getSignatureType();
99 const functionArguments = functionInfo.abiEncoder.decode(calldata);
100 const decodedCalldata = {
101 functionName,
102 functionSignature,
103 functionArguments,
104 };
105 return decodedCalldata;
106 }
107 /**
108 * Adds a set of ABI definitions, after which calldata and logs targeting these ABI's can be decoded.
109 * Additional properties can be included to disambiguate similar ABI's. For example, if two functions
110 * have the same signature but different parameter names, then their ABI definitions can be disambiguated
111 * by specifying a contract name.
112 * @param abiDefinitions ABI definitions for a given contract.
113 * @param contractName Name of contract that encapsulates the ABI definitions (optional).
114 * This can be used when decoding calldata to disambiguate methods with
115 * the same signature but different parameter names.
116 */
117 addABI(abiArray, contractName) {
118 if (abiArray === undefined) {
119 return;
120 }
121 const ethersInterface = new ethers.utils.Interface(abiArray);
122 _.map(abiArray, (abi) => {
123 switch (abi.type) {
124 case ethereum_types_1.AbiType.Event:
125 // tslint:disable-next-line:no-unnecessary-type-assertion
126 this._addEventABI(abi, ethersInterface);
127 break;
128 case ethereum_types_1.AbiType.Function:
129 // tslint:disable-next-line:no-unnecessary-type-assertion
130 this._addMethodABI(abi, contractName);
131 break;
132 default:
133 // ignore other types
134 break;
135 }
136 });
137 }
138 _addEventABI(eventAbi, ethersInterface) {
139 const topic = ethersInterface.events[eventAbi.name].topic;
140 const numIndexedArgs = _.reduce(eventAbi.inputs, (sum, input) => (input.indexed ? sum + 1 : sum), 0);
141 this._eventIds[topic] = Object.assign(Object.assign({}, this._eventIds[topic]), { [numIndexedArgs]: eventAbi });
142 }
143 _addMethodABI(methodAbi, contractName) {
144 const abiEncoder = new _1.AbiEncoder.Method(methodAbi);
145 const functionSelector = abiEncoder.getSelector();
146 if (!(functionSelector in this._selectorToFunctionInfo)) {
147 this._selectorToFunctionInfo[functionSelector] = [];
148 }
149 // Recored a copy of this ABI for each deployment
150 const functionSignature = abiEncoder.getSignature();
151 this._selectorToFunctionInfo[functionSelector].push({
152 functionSignature,
153 abiEncoder,
154 contractName,
155 });
156 }
157}
158exports.AbiDecoder = AbiDecoder;
159//# sourceMappingURL=abi_decoder.js.map
\No newline at end of file