Skip to content
This repository was archived by the owner on Jul 30, 2022. It is now read-only.

Commit 38270c7

Browse files
committed
Merge branch 'liamaharon-feature/tuples'
2 parents 6d16be2 + 2c486ed commit 38270c7

13 files changed

+1371
-519
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,5 @@ tmp
104104

105105
# Files to ignore #
106106
#####################
107+
package-lock.json
108+
yarn.lock

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,36 @@ web3.eth.getTransaction(txHash, (error, txResult) => {
9090
});
9191
```
9292

93+
### Decoding tuple and tuple[] types
94+
95+
Where `OrderData` is
96+
97+
```solidity
98+
struct OrderData {
99+
uint256 amount;
100+
address buyer;
101+
}
102+
```
103+
104+
decoding input to a method `someMethod(address,OrderData,OrderData[])` returns data in format
105+
106+
```js
107+
{
108+
method: 'someMethod',
109+
types: ['address', '(uint256,address)', '(uint256,address)[]'],
110+
inputs: [
111+
'0x81c55017F7Ce6E72451cEd49FF7bAB1e3DF64d0C',
112+
[100, '0xA37dE6790861B5541b0dAa7d0C0e651F44c6f4D9']
113+
[[274, '0xea674fdde714fd979de3edf0f56aa9716b898ec8']]
114+
],
115+
names: ['sender', ['order', ['amount', 'buyer']], ['allOrders', ['amount', 'buyer']]]
116+
}
117+
```
118+
119+
- In the `types` field, tuples are represented as a string containing types contained in the tuple
120+
- In the `inputs` field, tuples are represented as an array containing values contained in the tuple
121+
- In the `names` field, tuples are represented as an array with 2 items. Item 1 is the name of the tuple, item 2 is an array containing the names of the values contained in the tuple.
122+
93123
### Decoding Big Numbers
94124

95125
All numbers are returned in [big number](https://github.com/indutny/bn.js) format to preserve precision.
@@ -213,7 +243,7 @@ npm test
213243
5. Run linter:
214244

215245
```bash
216-
npm run lint:fix
246+
npm run lint
217247
```
218248

219249
## Contributing

dist/index.js

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
44

5+
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
6+
57
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
68

79
var fs = require('fs');
@@ -98,16 +100,18 @@ var InputDataDecoder = function () {
98100
if (obj.type === 'event') return acc;
99101
var method = obj.name || null;
100102
var types = obj.inputs ? obj.inputs.map(function (x) {
101-
if (x.type === 'tuple[]') {
103+
if (x.type.includes('tuple')) {
102104
return x;
103105
} else {
104106
return x.type;
105107
}
106108
}) : [];
107109

108110
var names = obj.inputs ? obj.inputs.map(function (x) {
109-
if (x.type === 'tuple[]') {
110-
return '';
111+
if (x.type.includes('tuple')) {
112+
return [x.name, x.components.map(function (a) {
113+
return a.name;
114+
})];
111115
} else {
112116
return x.name;
113117
}
@@ -122,15 +126,44 @@ var InputDataDecoder = function () {
122126
inputsBuf = normalizeAddresses(types, inputsBuf);
123127
inputs = ethabi.rawDecode(types, inputsBuf);
124128
} catch (err) {
125-
// TODO: normalize addresses for tuples
126129
inputs = ethers.utils.defaultAbiCoder.decode(types, inputsBuf);
127-
128-
inputs = inputs[0];
130+
// defaultAbiCoder attaches some unwanted properties to the list object
131+
inputs = deepRemoveUnwantedArrayProperties(inputs);
132+
133+
// TODO: do this normalization into normalizeAddresses
134+
inputs = inputs.map(function (input, i) {
135+
if (types[i].components) {
136+
var 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(function (address) {
144+
return address.split('0x')[1];
145+
});
146+
}
147+
return input;
148+
});
129149
}
130150

151+
// Map any tuple types into arrays
152+
var typesToReturn = types.map(function (t) {
153+
if (t.components) {
154+
var arr = t.components.reduce(function (acc, cur) {
155+
return [].concat(_toConsumableArray(acc), [cur.type]);
156+
}, []);
157+
var tupleStr = '(' + arr.join(',') + ')';
158+
if (t.type === 'tuple[]') return tupleStr + '[]';
159+
return tupleStr;
160+
}
161+
return t;
162+
});
163+
131164
return {
132165
method: method,
133-
types: types,
166+
types: typesToReturn,
134167
inputs: inputs,
135168
names: names
136169
};
@@ -155,6 +188,33 @@ var InputDataDecoder = function () {
155188
return InputDataDecoder;
156189
}();
157190

191+
// remove 0x from addresses
192+
193+
194+
function deepStripTupleAddresses(input, tupleTypes) {
195+
return input.map(function (item, i) {
196+
var type = tupleTypes[i].type;
197+
if (type === 'address') {
198+
console.log(item);
199+
return item.split('0x')[1];
200+
}
201+
if (type === 'address[]') {
202+
return item.map(function (a) {
203+
return a.split('0x')[1];
204+
});
205+
}
206+
return item;
207+
});
208+
console.log({ input: input, tupleTypes: tupleTypes });
209+
}
210+
211+
function deepRemoveUnwantedArrayProperties(arr) {
212+
return [].concat(_toConsumableArray(arr.map(function (item) {
213+
if (Array.isArray(item)) return deepRemoveUnwantedArrayProperties(item);
214+
return item;
215+
})));
216+
}
217+
158218
function normalizeAddresses(types, input) {
159219
var offset = 0;
160220
for (var i = 0; i < types.length; i++) {
@@ -190,11 +250,9 @@ function isArray(type) {
190250
return type.lastIndexOf(']') === type.length - 1;
191251
}
192252

193-
function handleInputs(input) {
194-
var tupleArray = false;
253+
function handleInputs(input, tupleArray) {
195254
if (input instanceof Object && input.components) {
196255
input = input.components;
197-
tupleArray = true;
198256
}
199257

200258
if (!Array.isArray(input)) {
@@ -219,11 +277,13 @@ function handleInputs(input) {
219277
if (tupleArray) {
220278
return ret + '[]';
221279
}
280+
281+
return ret;
222282
}
223283

224284
function genMethodId(methodName, types) {
225285
var input = methodName + '(' + types.reduce(function (acc, x) {
226-
acc.push(handleInputs(x));
286+
acc.push(handleInputs(x, x.type === 'tuple[]'));
227287
return acc;
228288
}, []).join(',') + ')';
229289

index.js

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,16 @@ class InputDataDecoder {
8383
if (obj.type === 'event') return acc
8484
const method = obj.name || null
8585
let types = obj.inputs ? obj.inputs.map(x => {
86-
if (x.type === 'tuple[]') {
86+
if (x.type.includes('tuple')) {
8787
return x
8888
} else {
8989
return x.type
9090
}
9191
}) : []
9292

9393
let names = obj.inputs ? obj.inputs.map(x => {
94-
if (x.type === 'tuple[]') {
95-
return ''
94+
if (x.type.includes('tuple')) {
95+
return [x.name, x.components.map(a => a.name)]
9696
} else {
9797
return x.name
9898
}
@@ -107,15 +107,40 @@ class InputDataDecoder {
107107
inputsBuf = normalizeAddresses(types, inputsBuf)
108108
inputs = ethabi.rawDecode(types, inputsBuf)
109109
} catch (err) {
110-
// TODO: normalize addresses for tuples
111110
inputs = ethers.utils.defaultAbiCoder.decode(types, inputsBuf)
112-
113-
inputs = inputs[0]
111+
// defaultAbiCoder attaches some unwanted properties to the list object
112+
inputs = deepRemoveUnwantedArrayProperties(inputs)
113+
114+
// TODO: do this normalization into normalizeAddresses
115+
inputs = inputs.map((input, i) => {
116+
if (types[i].components) {
117+
const tupleTypes = types[i].components
118+
return deepStripTupleAddresses(input, tupleTypes)
119+
}
120+
if (types[i] === 'address') {
121+
return input.split('0x')[1]
122+
}
123+
if (types[i] === 'address[]') {
124+
return input.map(address => address.split('0x')[1])
125+
}
126+
return input
127+
})
114128
}
115129

130+
// Map any tuple types into arrays
131+
const typesToReturn = types.map(t => {
132+
if (t.components) {
133+
const arr = t.components.reduce((acc, cur) => [...acc, cur.type], [])
134+
const tupleStr = `(${arr.join(',')})`
135+
if (t.type === 'tuple[]') return tupleStr + '[]'
136+
return tupleStr
137+
}
138+
return t
139+
})
140+
116141
return {
117142
method,
118-
types,
143+
types: typesToReturn,
119144
inputs,
120145
names
121146
}
@@ -137,6 +162,27 @@ class InputDataDecoder {
137162
}
138163
}
139164

165+
// remove 0x from addresses
166+
function deepStripTupleAddresses (input, tupleTypes) {
167+
return input.map((item, i) => {
168+
const type = tupleTypes[i].type
169+
if (type === 'address' && typeof item === 'string') {
170+
return item.split('0x')[1]
171+
}
172+
if (type === 'address[]' || Array.isArray()) {
173+
return item.map(a => a.split('0x')[1])
174+
}
175+
return item
176+
})
177+
}
178+
179+
function deepRemoveUnwantedArrayProperties (arr) {
180+
return [...arr.map(item => {
181+
if (Array.isArray(item)) return deepRemoveUnwantedArrayProperties(item)
182+
return item
183+
})]
184+
}
185+
140186
function normalizeAddresses (types, input) {
141187
let offset = 0
142188
for (let i = 0; i < types.length; i++) {
@@ -172,11 +218,9 @@ function isArray (type) {
172218
return type.lastIndexOf(']') === type.length - 1
173219
}
174220

175-
function handleInputs (input) {
176-
let tupleArray = false
221+
function handleInputs (input, tupleArray) {
177222
if (input instanceof Object && input.components) {
178223
input = input.components
179-
tupleArray = true
180224
}
181225

182226
if (!Array.isArray(input)) {
@@ -201,11 +245,13 @@ function handleInputs (input) {
201245
if (tupleArray) {
202246
return ret + '[]'
203247
}
248+
249+
return ret
204250
}
205251

206252
function genMethodId (methodName, types) {
207253
const input = methodName + '(' + (types.reduce((acc, x) => {
208-
acc.push(handleInputs(x))
254+
acc.push(handleInputs(x, x.type === 'tuple[]'))
209255
return acc
210256
}, []).join(',')) + ')'
211257

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
{
22
"name": "ethereum-input-data-decoder",
3-
"version": "0.2.1",
3+
"version": "0.3.0",
44
"description": "Ethereum smart contract transaction input data decoder",
55
"main": "dist/index.js",
66
"scripts": {
77
"test": "tape ./test/index.js",
88
"client": "browserify ./example/main.js -o ./example/bundle.js",
99
"build": "babel index.js --presets babel-preset-es2015 --out-dir dist/",
10-
"lint": "standard index.js test/*.js",
11-
"lint:fix": "standard --fix index.js test/*.js",
10+
"lint": "standard --fix index.js test/*.js",
1211
"prepublishOnly": "npm run lint:fix && npm run build"
1312
},
1413
"bin": {

0 commit comments

Comments
 (0)