UNPKG

6.48 kBJavaScriptView Raw
1var sha3 = require("crypto-js/sha3");
2var pkgVersion = require("./package.json").version;
3var Ajv = require("ajv");
4
5var contractObjectSchema = require("./spec/contract-object.spec.json");
6var networkObjectSchema = require("./spec/network-object.spec.json");
7var abiSchema = require("./spec/abi.spec.json");
8
9
10/**
11 * Property definitions for Contract Objects
12 *
13 * Describes canonical output properties as sourced from some "dirty" input
14 * object. Describes normalization process to account for deprecated and/or
15 * nonstandard keys and values.
16 *
17 * Maps (key -> property) where:
18 * - `key` is the top-level output key matching up with those in the schema
19 * - `property` is an object with optional values:
20 * - `sources`: list of sources (see below); default `key`
21 * - `transform`: function(value) -> transformed value; default x -> x
22 *
23 * Each source represents a means to select a value from dirty object.
24 * Allows:
25 * - dot-separated (`.`) string, corresponding to path to value in dirty
26 * object
27 * - function(dirtyObj) -> (cleanValue | undefined)
28 *
29 * The optional `transform` parameter standardizes value regardless of source,
30 * for purposes of ensuring data type and/or string schemas.
31 */
32var properties = {
33 "contractName": {
34 "sources": ["contractName", "contract_name"]
35 },
36 "abi": {
37 "sources": ["abi", "interface"],
38 "transform": function(value) {
39 if (typeof value === "string") {
40 try {
41 value = JSON.parse(value)
42 } catch (e) {
43 value = undefined;
44 }
45 }
46 return value;
47 }
48 },
49 "bytecode": {
50 "sources": [
51 "bytecode", "binary", "unlinked_binary", "evm.bytecode.object"
52 ],
53 "transform": function(value) {
54 if (value && value.indexOf("0x") != 0) {
55 value = "0x" + value;
56 }
57 return value;
58 }
59 },
60 "deployedBytecode": {
61 "sources": [
62 "deployedBytecode", "runtimeBytecode", "evm.deployedBytecode.object"
63 ],
64 "transform": function(value) {
65 if (value && value.indexOf("0x") != 0) {
66 value = "0x" + value;
67 }
68 return value;
69 }
70 },
71 "sourceMap": {
72 "sources": ["sourceMap", "srcmap", "evm.bytecode.sourceMap"]
73 },
74 "deployedSourceMap": {
75 "sources": ["deployedSourceMap", "srcmapRuntime", "evm.deployedBytecode.sourceMap"]
76 },
77 "source": {},
78 "sourcePath": {},
79 "ast": {},
80 "legacyAST": {
81 "transform": function(value, obj) {
82 var schemaVersion = obj.schemaVersion || "0.0.0";
83
84 // legacyAST introduced in v2.0.0
85 if (schemaVersion[0] < 2) {
86 return obj.ast;
87 } else {
88 return value
89 }
90 }
91 },
92 "compiler": {},
93 "networks": {
94 "transform": function(value) {
95 if (value === undefined) {
96 value = {}
97 }
98 return value;
99 }
100 },
101 "schemaVersion": {
102 "sources": ["schemaVersion", "schema_version"]
103 },
104 "updatedAt": {
105 "sources": ["updatedAt", "updated_at"],
106 "transform": function(value) {
107 if (typeof value === "number") {
108 value = new Date(value).toISOString();
109 }
110 return value;
111 }
112 }
113};
114
115
116/**
117 * Construct a getter for a given key, possibly applying some post-retrieve
118 * transformation on the resulting value.
119 *
120 * @return {Function} Accepting dirty object and returning value || undefined
121 */
122function getter(key, transform) {
123 if (transform === undefined) {
124 transform = function(x) { return x };
125 }
126
127 return function(obj) {
128 try {
129 return transform(obj[key]);
130 } catch (e) {
131 return undefined;
132 }
133 }
134}
135
136
137/**
138 * Chains together a series of function(obj) -> value, passing resulting
139 * returned value to next function in chain.
140 *
141 * Accepts any number of functions passed as arguments
142 * @return {Function} Accepting initial object, returning end-of-chain value
143 *
144 * Assumes all intermediary values to be objects, with well-formed sequence
145 * of operations.
146 */
147function chain() {
148 var getters = Array.prototype.slice.call(arguments);
149 return function(obj) {
150 return getters.reduce(function (cur, get) {
151 return get(cur);
152 }, obj);
153 }
154}
155
156
157// Schema module
158//
159
160var TruffleContractSchema = {
161 // Return a promise to validate a contract object
162 // - Resolves as validated `contractObj`
163 // - Rejects with list of errors from schema validator
164 validate: function(contractObj) {
165 var ajv = new Ajv({ useDefaults: true });
166 ajv.addSchema(abiSchema);
167 ajv.addSchema(networkObjectSchema);
168 ajv.addSchema(contractObjectSchema);
169 if (ajv.validate("contract-object.spec.json", contractObj)) {
170 return contractObj;
171 } else {
172 throw ajv.errors;
173 }
174 },
175
176 // accepts as argument anything that can be turned into a contract object
177 // returns a contract object
178 normalize: function(objDirty, options) {
179 options = options || {};
180 var normalized = {};
181
182 // iterate over each property
183 Object.keys(properties).forEach(function(key) {
184 var property = properties[key];
185 var value; // normalized value || undefined
186
187 // either used the defined sources or assume the key will only ever be
188 // listed as its canonical name (itself)
189 var sources = property.sources || [key];
190
191 // iterate over sources until value is defined or end of list met
192 for (var i = 0; value === undefined && i < sources.length; i++) {
193 var source = sources[i];
194 // string refers to path to value in objDirty, split and chain
195 // getters
196 if (typeof source === "string") {
197 var traversals = source.split(".")
198 .map(function(k) { return getter(k) });
199 source = chain.apply(null, traversals);
200 }
201
202 // source should be a function that takes the objDirty and returns
203 // value or undefined
204 value = source(objDirty);
205 }
206
207 // run source-agnostic transform on value
208 // (e.g. make sure bytecode begins 0x)
209 if (property.transform) {
210 value = property.transform(value, objDirty);
211 }
212
213 // add resulting (possibly undefined) to normalized obj
214 normalized[key] = value;
215 });
216
217 // Copy x- options
218 Object.keys(objDirty).forEach(function(key) {
219 if (key.indexOf("x-") === 0) {
220 normalized[key] = getter(key)(objDirty);
221 }
222 });
223
224 // update schema version
225 normalized.schemaVersion = pkgVersion;
226
227 if (options.validate) {
228 this.validate(normalized);
229 }
230
231 return normalized
232 }
233};
234
235module.exports = TruffleContractSchema;