forked from sambacha/abi2schema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
209 lines (184 loc) · 4.55 KB
/
index.js
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
"use strict";
const merge = require("merge");
const types = {
// uint, int: synonyms for uint256, int256 respectively. For computing the function selector, uint256 and int256 have to be used.
int: {
// 'int256'
type: "integer",
// TODO: max size
},
uint: {
// 'uint256',
type: "integer",
},
// address: equivalent to uint160, except for the assumed interpretation and language typing.
address: {
type: "string",
pattern: "0x[a-fA-F0-9]{40}",
},
// fixed, ufixed: synonyms for fixed128x18, ufixed128x18
fixed: {
// fixed128x18
type: "number",
},
ufixed: {
// ufixed128x18
type: "number",
},
bytes: {
// bytes
type: "string",
},
// bool: equivalent to uint8 restricted to the values 0 and 1. For computing the function selector, bool is used.
bool: {
type: "boolean",
},
string: {
type: "string",
},
array: {
type: "array"
},
};
const typesRegex = {
"^int\\d{1,3}$": types.int,
"^uint\\d{1,3}$": types.uint,
"^fixed\\d{1,3}x\\d{1,2}": types.fixed,
"^ufixed\\d{1,3}x\\d{1,2}": types.ufixed,
"^bytes\\d{1,3}": types.bytes,
};
function mapType(type) {
if (type.endsWith(']')) {
const withoutArray = type.substr(0, type.indexOf('['));
const itemType = mapType(withoutArray);
return merge(true, types.array, {items: itemType});
}
const mapped = types[type];
if (mapped) {
return merge(true, mapped);
}
for (let pattern in typesRegex) {
const exp = new RegExp(pattern);
if (exp.test(type)) {
return merge(true, typesRegex[pattern]);
}
}
throw new Error(`Unsupported type: ${type}`);
}
function nameForNode(node) {
if (node.type && node.name) {
return "constructor" === node.type ? node.type : node.name;
}
return node.type;
}
function mapNode(node, options) {
options = options || {};
options.as = options.as || "array";
function buildAs(subNode, property, as) {
let schema;
let adder;
if ("object" === as) {
// as object
schema = {
type: "object",
required: [],
properties: {},
};
adder = (subSchema, name) => {
schema.properties[name] = subSchema;
schema.required.push(name);
};
} else if ("array" === as) {
// as array
schema = {
type: "array",
items: [],
};
adder = (subSchema, name) => {
subSchema.description = name;
schema.items.push(subSchema);
};
} else {
throw new Error(`Unsupported argument 'as': ${options.as}`);
}
const source = subNode[property];
if (!Array.isArray(source)) {
return schema;
}
source.forEach((subNode) => {
const subSchema = mapType(subNode.type);
if (!subSchema) {
// ignore this property
return;
}
const name = nameForNode(subNode);
const description = `${subNode.type} ${name}`;
subSchema.description = description;
adder(subSchema, name);
});
return schema;
}
if (options.for) {
return buildAs(node, options.for, options.as);
}
const schema = {
type: "object",
properties: {},
};
schema.properties.inputs = buildAs(node, "inputs", options.as);
schema.properties.outputs = buildAs(node, "outputs", options.as);
return schema;
}
function convert(abi, options) {
options = options || {};
// special case - find constructor
// do this seperately to keep the others fast
if ("constructor" === options.type || "constructor" === options.name) {
let schema;
abi.forEach(function (node) {
if ("constructor" !== node.type) {
return;
}
schema = mapNode(node, options);
});
return schema;
}
// filter by type
if (options.type) {
const schema = {
type: "object",
properties: {},
};
abi.forEach(function (node) {
if (options.type !== node.type && "constructor" !== node.type) {
return;
}
const subSchema = mapNode(node, options);
const name = nameForNode(node);
subSchema.description = `${node.type} ${name}`;
schema.properties[name] = subSchema;
});
return schema;
}
// find by name
if (options.name) {
let schema;
abi.forEach(function (node) {
if (options.name !== node.name) {
return;
}
schema = mapNode(node, options);
});
return schema;
}
const schema = {
type: "object",
properties: {},
};
abi.forEach(function (node) {
const name = nameForNode(node);
schema.properties[name] = mapNode(node, options);
});
return schema;
}
module.exports = convert;