diff --git a/index.js b/index.js index 8114772..5ff6ec3 100644 --- a/index.js +++ b/index.js @@ -1,150 +1,212 @@ var sha3 = require("crypto-js/sha3"); -var schema_version = require("./package.json").version; - -// TODO: This whole thing should have a json schema. - -var TruffleSchema = { - // Normalize options passed in to be the exact options required - // for truffle-contract. - // - // options can be three things: - // - normal object - // - contract object - // - solc output - // - // TODO: Is extra_options still necessary? - normalizeInput: function(options, extra_options) { - extra_options = extra_options || {}; - var normalized = {}; - var expected_keys = [ - "contract_name", - "abi", - "bytecode", - "deployedBytecode", - "sourceMap", - "deployedSourceMap", - "linkReferences", - "deployedLinkReferences", - "source", - "sourcePath", - "ast", - "address", - "networks", - "updated_at" - ]; - - var deprecated_key_mappings = { - "unlinked_binary": "bytecode", - "binary": "bytecode", - "srcmap": "sourceMap", - "srcmapRuntime": "deployedSourceMap", - "interface": "abi" - }; - - // Merge options/contract object first, then extra_options - expected_keys.forEach(function(key) { - var value; - - try { - // Will throw an error if key == address and address doesn't exist. - value = options[key]; - - if (value != undefined) { - normalized[key] = value; +var pkgVersion = require("./package.json").version; +var Ajv = require("ajv"); +var contractSchema = require("./spec/contract.spec.json"); + + +/** + * Property definitions for Contract Objects + * + * Describes canonical output properties as sourced from some "dirty" input + * object. Describes normalization process to account for deprecated and/or + * nonstandard keys and values. + * + * Maps (key -> property) where: + * - `key` is the top-level output key matching up with those in the schema + * - `property` is an object with optional values: + * - `sources`: list of sources (see below); default `key` + * - `transform`: function(value) -> transformed value; default x -> x + * + * Each source represents a means to select a value from dirty object. + * Allows: + * - dot-separated (`.`) string, corresponding to path to value in dirty + * object + * - function(dirtyObj) -> (cleanValue | undefined) + * + * The optional `transform` parameter standardizes value regardless of source, + * for purposes of ensuring data type and/or string schemas. + */ +var properties = { + "contractName": { + "sources": ["contractName", "contract_name"] + }, + "abi": { + "sources": ["abi", "interface"], + "transform": function(value) { + if (typeof value === "string") { + try { + value = JSON.parse(value) + } catch (e) { + value = undefined; } - } catch (e) { - // Do nothing. } - - try { - // Will throw an error if key == address and address doesn't exist. - value = extra_options[key]; - - if (value != undefined) { - normalized[key] = value; - } - } catch (e) { - // Do nothing. + return value; + } + }, + "bytecode": { + "sources": [ + "bytecode", "binary", "unlinked_binary", "evm.bytecode.object" + ], + "transform": function(value) { + if (value && value.indexOf("0x") != 0) { + value = "0x" + value; } - }); - - Object.keys(deprecated_key_mappings).forEach(function(deprecated_key) { - var mapped_key = deprecated_key_mappings[deprecated_key]; - - if (normalized[mapped_key] == null) { - normalized[mapped_key] = options[deprecated_key] || extra_options[deprecated_key]; + return value; + } + }, + "deployedBytecode": { + "sources": [ + "deployedBytecode", "runtimeBytecode", "evm.deployedBytecode.object" + ], + "transform": function(value) { + if (value && value.indexOf("0x") != 0) { + value = "0x" + value; } - }); - - if (typeof normalized.abi == "string") { - normalized.abi = JSON.parse(normalized.abi); + return value; } - - this.copyCustomOptions(options, normalized); - - return normalized; }, - - // Generate a proper binary from normalized options, and optionally - // merge it with an existing binary. - // TODO: This function needs to be renamed. Binary is a misnomer. - generateObject: function(options, existing_object, extra_options) { - existing_object = existing_object || {}; - extra_options = extra_options || {}; - - options.networks = options.networks || {}; - existing_object.networks = existing_object.networks || {}; - - // Merge networks before overwriting - Object.keys(existing_object.networks).forEach(function(network_id) { - options.networks[network_id] = existing_object.networks[network_id]; - }); - - var obj = this.normalizeInput(existing_object, options); - - if (options.overwrite == true) { - existing_object = {}; + "sourceMap": { + "sources": ["sourceMap", "srcmap", "evm.bytecode.sourceMap"] + }, + "deployedSourceMap": { + "sources": ["deployedSourceMap", "srcmapRuntime", "evm.deployedBytecode.sourceMap"] + }, + "source": {}, + "sourcePath": {}, + "ast": {}, + "networks": { + "transform": function(value) { + if (value === undefined) { + value = {} + } + return value; + } + }, + "schemaVersion": { + "sources": ["schemaVersion", "schema_version"] + }, + "updatedAt": { + "sources": ["updatedAt", "updated_at"], + "transform": function(value) { + if (typeof value === "number") { + value = new Date(value).toISOString(); + } + return value; } + } +}; + - obj.contract_name = obj.contract_name || "Contract"; +/** + * Construct a getter for a given key, possibly applying some post-retrieve + * transformation on the resulting value. + * + * @return {Function} Accepting dirty object and returning value || undefined + */ +function getter(key, transform) { + if (transform === undefined) { + transform = function(x) { return x }; + } - // Ensure unlinked binary starts with a 0x - // TODO: Remove this and enforce it through json schema - if (obj.bytecode && obj.bytecode.indexOf("0x") < 0) { - obj.bytecode = "0x" + obj.bytecode; + return function(obj) { + try { + return transform(obj[key]); + } catch (e) { + return undefined; } + } +} + + +/** + * Chains together a series of function(obj) -> value, passing resulting + * returned value to next function in chain. + * + * Accepts any number of functions passed as arguments + * @return {Function} Accepting initial object, returning end-of-chain value + * + * Assumes all intermediary values to be objects, with well-formed sequence + * of operations. + */ +function chain() { + var getters = Array.prototype.slice.call(arguments); + return function(obj) { + return getters.reduce(function (cur, get) { + return get(cur); + }, obj); + } +} - var updated_at = new Date().getTime(); - obj.schema_version = schema_version; +// Schema module +// - if (extra_options.dirty !== false) { - obj.updated_at = updated_at; +var TruffleContractSchema = { + // Return a promise to validate a contract object + // - Resolves as validated `contractObj` + // - Rejects with list of errors from schema validator + validate: function(contractObj) { + var ajv = new Ajv(); + var validate = ajv.compile(contractSchema); + if (validate(contractObj)) { + return contractObj; } else { - obj.updated_at = options.updated_at || existing_object.updated_at || updated_at; + throw validate.errors; } + }, - this.copyCustomOptions(options, obj); + // accepts as argument anything that can be turned into a contract object + // returns a contract object + normalize: function(objDirty) { + var normalized = {}; - return obj; - }, + // iterate over each property + Object.keys(properties).forEach(function(key) { + var property = properties[key]; + var value; // normalized value || undefined + + // either used the defined sources or assume the key will only ever be + // listed as its canonical name (itself) + var sources = property.sources || [key]; + + // iterate over sources until value is defined or end of list met + for (var i = 0; value === undefined && i < sources.length; i++) { + var source = sources[i]; + // string refers to path to value in objDirty, split and chain + // getters + if (typeof source === "string") { + var traversals = source.split(".") + .map(function(k) { return getter(k) }); + source = chain.apply(null, traversals); + } + + // source should be a function that takes the objDirty and returns + // value or undefined + value = source(objDirty); + } - copyCustomOptions: function(from, to) { - // Now let all x- options through. - Object.keys(from).forEach(function(key) { - if (key.indexOf("x-") != 0) return; + // run source-agnostic transform on value + // (e.g. make sure bytecode begins 0x) + if (property.transform) { + value = property.transform(value); + } - try { - value = from[key]; + // add resulting (possibly undefined) to normalized obj + normalized[key] = value; + }); - if (value != undefined) { - to[key] = value; - } - } catch (e) { - // Do nothing. + // Copy x- options + Object.keys(objDirty).forEach(function(key) { + if (key.indexOf("x-") === 0) { + normalized[key] = getter(key)(objDirty); } }); + + // update schema version + normalized.schemaVersion = pkgVersion; + + return normalized; } }; -module.exports = TruffleSchema; +module.exports = TruffleContractSchema; diff --git a/package.json b/package.json index 04a2777..f7b81f9 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ }, "homepage": "https://github.com/trufflesuite/truffle-schema#readme", "dependencies": { + "ajv": "^5.1.1", "crypto-js": "^3.1.9-1" }, "devDependencies": { - "mocha": "^3.2.0" + "mocha": "^3.2.0", + "solc": "^0.4.11" } } diff --git a/spec/contract.spec.json b/spec/contract.spec.json new file mode 100644 index 0000000..0f60d1e --- /dev/null +++ b/spec/contract.spec.json @@ -0,0 +1,116 @@ +{ + "id": "truffle-contract-schema.json", + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "contractName": { "$ref": "#/definitions/ContractName" }, + "abi": { "$ref": "#/definitions/ABI" }, + "bytecode": { "allOf": [ + { "$ref": "#/definitions/Bytecode" }, + { "description": "Bytecode sent as contract-creation transaction data" } + ]}, + "deployedBytecode": { "allOf": [ + { "$ref": "#/definitions/Bytecode" }, + { "description": "On-chain deployed contract bytecode" } + ]}, + "sourceMap": { "allOf": [ + { "$ref": "#/definitions/SourceMap" }, + { "description": "Source mapping for contract-creation transaction data bytecode" } + ]}, + "deployedSourceMap": { "allOf": [ + { "$ref": "#/definitions/SourceMap" }, + { "description": "Source mapping for contract bytecode" } + ]}, + "source": { "$ref": "#/definitions/Source" }, + "sourcePath": { "$ref": "#/definitions/SourcePath" }, + "ast": { "$ref": "#/definitions/AST" }, + "networks": { + "patternProperties": { + "^[a-zA-Z0-9]+$": { "$ref": "#/definitions/Network" } + }, + "additionalProperties": false + }, + "schemaVersion": { "$ref": "#/definitions/SchemaVersion" }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "patternProperties": { + "^x-": { "anyOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "object" }, + { "type": "array" } + ]} + + }, + "additionalProperties": false, + "required": [ + "abi" + ], + "definitions": { + "ContractName": { + "type": "string", + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" + }, + "Address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$" + }, + "ABI": { + "type": "array" + }, + "Bytecode": { + "type": "string", + "pattern": "^0x0$|^0x([a-fA-F0-9]{2}|__[a-zA-Z0-9_]{38})+$" + }, + "Source": { + "type": "string" + }, + "SourceMap": { + "type": "string" + }, + "SourcePath": { + "type": "string" + }, + "AST": { + "type": "object" + }, + "Network": { + "type": "object", + "properties": { + "address": { "$ref": "#/definitions/Address" }, + "events": { "$ref": "#/definitions/EventsCollection" }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_]*$": { "$ref": "#/definitions/Link" } + }, + "additionalProperties": false + } + } + }, + "Link": { + "type": "object", + "properties": { + "address": { "$ref": "#/definitions/Address" }, + "events": { "$ref": "#/definitions/EventsCollection" } + } + }, + "EventsCollection": { + "type": "object", + "patternProperties": { + "^0x[a-fA-F0-9]{64}$": { "$ref": "#/definitions/Event" } + }, + "additionalProperties": false + }, + "Event": { + "type": "object" + }, + "SchemaVersion": { + "type": "string", + "pattern": "[0-9]+\\.[0-9]+\\.[0-9]+" + } + } +} diff --git a/test/MetaCoin.json b/test/MetaCoin.json new file mode 100644 index 0000000..71fc614 --- /dev/null +++ b/test/MetaCoin.json @@ -0,0 +1,1040 @@ +{ + "contractName": "MetaCoin", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "addr", + "type": "address" + } + ], + "name": "getBalanceInEth", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "sendCoin", + "outputs": [ + { + "name": "sufficient", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "addr", + "type": "address" + } + ], + "name": "getBalance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } + ], + "bytecode": "0x6060604052341561000c57fe5b5b600160a060020a033216600090815260208190526040902061271090555b5b6102308061003b6000396000f300606060405263ffffffff60e060020a6000350416637bd703e8811461003757806390b98a1114610065578063f8b2cb4f14610098575bfe5b341561003f57fe5b610053600160a060020a03600435166100c6565b60408051918252519081900360200190f35b341561006d57fe5b610084600160a060020a036004351660243561014d565b604080519115158252519081900360200190f35b34156100a057fe5b610053600160a060020a03600435166101e5565b60408051918252519081900360200190f35b600073__ConvertLib____________________________6396e4ee3d6100eb846101e5565b60026000604051602001526040518363ffffffff1660e060020a028152600401808381526020018281526020019250505060206040518083038186803b151561013057fe5b6102c65a03f4151561013e57fe5b5050604051519150505b919050565b600160a060020a03331660009081526020819052604081205482901015610176575060006101df565b600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b92915050565b600160a060020a0381166000908152602081905260409020545b9190505600a165627a7a72305820f596963383bc39946413f0f0b34aee51796e6ae4b1b68b334f880b30a36ec6730029", + "deployedBytecode": "0x606060405263ffffffff60e060020a6000350416637bd703e8811461003757806390b98a1114610065578063f8b2cb4f14610098575bfe5b341561003f57fe5b610053600160a060020a03600435166100c6565b60408051918252519081900360200190f35b341561006d57fe5b610084600160a060020a036004351660243561014d565b604080519115158252519081900360200190f35b34156100a057fe5b610053600160a060020a03600435166101e5565b60408051918252519081900360200190f35b600073__ConvertLib____________________________6396e4ee3d6100eb846101e5565b60026000604051602001526040518363ffffffff1660e060020a028152600401808381526020018281526020019250505060206040518083038186803b151561013057fe5b6102c65a03f4151561013e57fe5b5050604051519150505b919050565b600160a060020a03331660009081526020819052604081205482901015610176575060006101df565b600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b92915050565b600160a060020a0381166000908152602081905260409020545b9190505600a165627a7a72305820f596963383bc39946413f0f0b34aee51796e6ae4b1b68b334f880b30a36ec6730029", + "sourceMap": "315:637:1:-;;;452:55;;;;;;;-1:-1:-1;;;;;485:9:1;476:19;:8;:19;;;;;;;;;;498:5;476:27;;452:55;315:637;;;;;;;", + "source": "pragma solidity ^0.4.4;\n\nimport \"./ConvertLib.sol\";\n\n// This is just a simple example of a coin-like contract.\n// It is not standards compatible and cannot be expected to talk to other\n// coin/token contracts. If you want to create a standards-compliant\n// token, see: https://github.com/ConsenSys/Tokens. Cheers!\n\ncontract MetaCoin {\n\tmapping (address => uint) balances;\n\n\tevent Transfer(address indexed _from, address indexed _to, uint256 _value);\n\n\tfunction MetaCoin() {\n\t\tbalances[tx.origin] = 10000;\n\t}\n\n\tfunction sendCoin(address receiver, uint amount) returns(bool sufficient) {\n\t\tif (balances[msg.sender] < amount) return false;\n\t\tbalances[msg.sender] -= amount;\n\t\tbalances[receiver] += amount;\n\t\tTransfer(msg.sender, receiver, amount);\n\t\treturn true;\n\t}\n\n\tfunction getBalanceInEth(address addr) returns(uint){\n\t\treturn ConvertLib.convert(getBalance(addr),2);\n\t}\n\n\tfunction getBalance(address addr) returns(uint) {\n\t\treturn balances[addr];\n\t}\n}\n", + "sourcePath": "/Users/gnidan/tmp/metacoin/contracts/MetaCoin.sol", + "ast": { + "children": [ + { + "attributes": { + "literals": [ + "solidity", + "^", + "0.4", + ".4" + ] + }, + "id": 18, + "name": "PragmaDirective", + "src": "0:23:1" + }, + { + "attributes": { + "file": "./ConvertLib.sol" + }, + "id": 19, + "name": "ImportDirective", + "src": "25:26:1" + }, + { + "attributes": { + "fullyImplemented": true, + "isLibrary": false, + "linearizedBaseContracts": [ + 112 + ], + "name": "MetaCoin" + }, + "children": [ + { + "attributes": { + "constant": false, + "name": "balances", + "storageLocation": "default", + "type": "mapping(address => uint256)", + "visibility": "internal" + }, + "children": [ + { + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 20, + "name": "ElementaryTypeName", + "src": "345:7:1" + }, + { + "attributes": { + "name": "uint" + }, + "id": 21, + "name": "ElementaryTypeName", + "src": "356:4:1" + } + ], + "id": 22, + "name": "Mapping", + "src": "336:25:1" + } + ], + "id": 23, + "name": "VariableDeclaration", + "src": "336:34:1" + }, + { + "attributes": { + "name": "Transfer" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "indexed": true, + "name": "_from", + "storageLocation": "default", + "type": "address", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 24, + "name": "ElementaryTypeName", + "src": "389:7:1" + } + ], + "id": 25, + "name": "VariableDeclaration", + "src": "389:21:1" + }, + { + "attributes": { + "constant": false, + "indexed": true, + "name": "_to", + "storageLocation": "default", + "type": "address", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 26, + "name": "ElementaryTypeName", + "src": "412:7:1" + } + ], + "id": 27, + "name": "VariableDeclaration", + "src": "412:19:1" + }, + { + "attributes": { + "constant": false, + "indexed": false, + "name": "_value", + "storageLocation": "default", + "type": "uint256", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint256" + }, + "id": 28, + "name": "ElementaryTypeName", + "src": "433:7:1" + } + ], + "id": 29, + "name": "VariableDeclaration", + "src": "433:14:1" + } + ], + "id": 30, + "name": "ParameterList", + "src": "388:60:1" + } + ], + "id": 31, + "name": "EventDefinition", + "src": "374:75:1" + }, + { + "attributes": { + "constant": false, + "name": "MetaCoin", + "payable": false, + "visibility": "public" + }, + "children": [ + { + "children": [], + "id": 32, + "name": "ParameterList", + "src": "469:2:1" + }, + { + "children": [], + "id": 33, + "name": "ParameterList", + "src": "472:0:1" + }, + { + "children": [ + { + "children": [ + { + "attributes": { + "operator": "=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 34, + "name": "Identifier", + "src": "476:8:1" + }, + { + "attributes": { + "member_name": "origin", + "type": "address" + }, + "children": [ + { + "attributes": { + "type": "tx", + "value": "tx" + }, + "id": 35, + "name": "Identifier", + "src": "485:2:1" + } + ], + "id": 36, + "name": "MemberAccess", + "src": "485:9:1" + } + ], + "id": 37, + "name": "IndexAccess", + "src": "476:19:1" + }, + { + "attributes": { + "hexvalue": "3130303030", + "subdenomination": null, + "token": null, + "type": "int_const 10000", + "value": "10000" + }, + "id": 38, + "name": "Literal", + "src": "498:5:1" + } + ], + "id": 39, + "name": "Assignment", + "src": "476:27:1" + } + ], + "id": 40, + "name": "ExpressionStatement", + "src": "476:27:1" + } + ], + "id": 41, + "name": "Block", + "src": "472:35:1" + } + ], + "id": 42, + "name": "FunctionDefinition", + "src": "452:55:1" + }, + { + "attributes": { + "constant": false, + "name": "sendCoin", + "payable": false, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "receiver", + "storageLocation": "default", + "type": "address", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 43, + "name": "ElementaryTypeName", + "src": "528:7:1" + } + ], + "id": 44, + "name": "VariableDeclaration", + "src": "528:16:1" + }, + { + "attributes": { + "constant": false, + "name": "amount", + "storageLocation": "default", + "type": "uint256", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint" + }, + "id": 45, + "name": "ElementaryTypeName", + "src": "546:4:1" + } + ], + "id": 46, + "name": "VariableDeclaration", + "src": "546:11:1" + } + ], + "id": 47, + "name": "ParameterList", + "src": "527:31:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "sufficient", + "storageLocation": "default", + "type": "bool", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "bool" + }, + "id": 48, + "name": "ElementaryTypeName", + "src": "567:4:1" + } + ], + "id": 49, + "name": "VariableDeclaration", + "src": "567:15:1" + } + ], + "id": 50, + "name": "ParameterList", + "src": "566:17:1" + }, + { + "children": [ + { + "children": [ + { + "attributes": { + "operator": "<", + "type": "bool" + }, + "children": [ + { + "attributes": { + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 51, + "name": "Identifier", + "src": "592:8:1" + }, + { + "attributes": { + "member_name": "sender", + "type": "address" + }, + "children": [ + { + "attributes": { + "type": "msg", + "value": "msg" + }, + "id": 52, + "name": "Identifier", + "src": "601:3:1" + } + ], + "id": 53, + "name": "MemberAccess", + "src": "601:10:1" + } + ], + "id": 54, + "name": "IndexAccess", + "src": "592:20:1" + }, + { + "attributes": { + "type": "uint256", + "value": "amount" + }, + "id": 55, + "name": "Identifier", + "src": "615:6:1" + } + ], + "id": 56, + "name": "BinaryOperation", + "src": "592:29:1" + }, + { + "children": [ + { + "attributes": { + "hexvalue": "66616c7365", + "subdenomination": null, + "token": "false", + "type": "bool", + "value": "false" + }, + "id": 57, + "name": "Literal", + "src": "630:5:1" + } + ], + "id": 58, + "name": "Return", + "src": "623:12:1" + } + ], + "id": 59, + "name": "IfStatement", + "src": "588:47:1" + }, + { + "children": [ + { + "attributes": { + "operator": "-=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 60, + "name": "Identifier", + "src": "639:8:1" + }, + { + "attributes": { + "member_name": "sender", + "type": "address" + }, + "children": [ + { + "attributes": { + "type": "msg", + "value": "msg" + }, + "id": 61, + "name": "Identifier", + "src": "648:3:1" + } + ], + "id": 62, + "name": "MemberAccess", + "src": "648:10:1" + } + ], + "id": 63, + "name": "IndexAccess", + "src": "639:20:1" + }, + { + "attributes": { + "type": "uint256", + "value": "amount" + }, + "id": 64, + "name": "Identifier", + "src": "663:6:1" + } + ], + "id": 65, + "name": "Assignment", + "src": "639:30:1" + } + ], + "id": 66, + "name": "ExpressionStatement", + "src": "639:30:1" + }, + { + "children": [ + { + "attributes": { + "operator": "+=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 67, + "name": "Identifier", + "src": "673:8:1" + }, + { + "attributes": { + "type": "address", + "value": "receiver" + }, + "id": 68, + "name": "Identifier", + "src": "682:8:1" + } + ], + "id": 69, + "name": "IndexAccess", + "src": "673:18:1" + }, + { + "attributes": { + "type": "uint256", + "value": "amount" + }, + "id": 70, + "name": "Identifier", + "src": "695:6:1" + } + ], + "id": 71, + "name": "Assignment", + "src": "673:28:1" + } + ], + "id": 72, + "name": "ExpressionStatement", + "src": "673:28:1" + }, + { + "children": [ + { + "attributes": { + "type": "tuple()", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "type": "function (address,address,uint256) constant", + "value": "Transfer" + }, + "id": 73, + "name": "Identifier", + "src": "705:8:1" + }, + { + "attributes": { + "member_name": "sender", + "type": "address" + }, + "children": [ + { + "attributes": { + "type": "msg", + "value": "msg" + }, + "id": 74, + "name": "Identifier", + "src": "714:3:1" + } + ], + "id": 75, + "name": "MemberAccess", + "src": "714:10:1" + }, + { + "attributes": { + "type": "address", + "value": "receiver" + }, + "id": 76, + "name": "Identifier", + "src": "726:8:1" + }, + { + "attributes": { + "type": "uint256", + "value": "amount" + }, + "id": 77, + "name": "Identifier", + "src": "736:6:1" + } + ], + "id": 78, + "name": "FunctionCall", + "src": "705:38:1" + } + ], + "id": 79, + "name": "ExpressionStatement", + "src": "705:38:1" + }, + { + "children": [ + { + "attributes": { + "hexvalue": "74727565", + "subdenomination": null, + "token": "true", + "type": "bool", + "value": "true" + }, + "id": 80, + "name": "Literal", + "src": "754:4:1" + } + ], + "id": 81, + "name": "Return", + "src": "747:11:1" + } + ], + "id": 82, + "name": "Block", + "src": "584:178:1" + } + ], + "id": 83, + "name": "FunctionDefinition", + "src": "510:252:1" + }, + { + "attributes": { + "constant": false, + "name": "getBalanceInEth", + "payable": false, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "addr", + "storageLocation": "default", + "type": "address", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 84, + "name": "ElementaryTypeName", + "src": "790:7:1" + } + ], + "id": 85, + "name": "VariableDeclaration", + "src": "790:12:1" + } + ], + "id": 86, + "name": "ParameterList", + "src": "789:14:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "", + "storageLocation": "default", + "type": "uint256", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint" + }, + "id": 87, + "name": "ElementaryTypeName", + "src": "812:4:1" + } + ], + "id": 88, + "name": "VariableDeclaration", + "src": "812:4:1" + } + ], + "id": 89, + "name": "ParameterList", + "src": "811:6:1" + }, + { + "children": [ + { + "children": [ + { + "attributes": { + "type": "uint256", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "member_name": "convert", + "type": "function (uint256,uint256) returns (uint256)" + }, + "children": [ + { + "attributes": { + "type": "type(library ConvertLib)", + "value": "ConvertLib" + }, + "id": 90, + "name": "Identifier", + "src": "828:10:1" + } + ], + "id": 91, + "name": "MemberAccess", + "src": "828:18:1" + }, + { + "attributes": { + "type": "uint256", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "type": "function (address) returns (uint256)", + "value": "getBalance" + }, + "id": 92, + "name": "Identifier", + "src": "847:10:1" + }, + { + "attributes": { + "type": "address", + "value": "addr" + }, + "id": 93, + "name": "Identifier", + "src": "858:4:1" + } + ], + "id": 94, + "name": "FunctionCall", + "src": "847:16:1" + }, + { + "attributes": { + "hexvalue": "32", + "subdenomination": null, + "token": null, + "type": "int_const 2", + "value": "2" + }, + "id": 95, + "name": "Literal", + "src": "864:1:1" + } + ], + "id": 96, + "name": "FunctionCall", + "src": "828:38:1" + } + ], + "id": 97, + "name": "Return", + "src": "821:45:1" + } + ], + "id": 98, + "name": "Block", + "src": "817:53:1" + } + ], + "id": 99, + "name": "FunctionDefinition", + "src": "765:105:1" + }, + { + "attributes": { + "constant": false, + "name": "getBalance", + "payable": false, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "addr", + "storageLocation": "default", + "type": "address", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address" + }, + "id": 100, + "name": "ElementaryTypeName", + "src": "893:7:1" + } + ], + "id": 101, + "name": "VariableDeclaration", + "src": "893:12:1" + } + ], + "id": 102, + "name": "ParameterList", + "src": "892:14:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "", + "storageLocation": "default", + "type": "uint256", + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint" + }, + "id": 103, + "name": "ElementaryTypeName", + "src": "915:4:1" + } + ], + "id": 104, + "name": "VariableDeclaration", + "src": "915:4:1" + } + ], + "id": 105, + "name": "ParameterList", + "src": "914:6:1" + }, + { + "children": [ + { + "children": [ + { + "attributes": { + "type": "uint256" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 106, + "name": "Identifier", + "src": "932:8:1" + }, + { + "attributes": { + "type": "address", + "value": "addr" + }, + "id": 107, + "name": "Identifier", + "src": "941:4:1" + } + ], + "id": 108, + "name": "IndexAccess", + "src": "932:14:1" + } + ], + "id": 109, + "name": "Return", + "src": "925:21:1" + } + ], + "id": 110, + "name": "Block", + "src": "921:29:1" + } + ], + "id": 111, + "name": "FunctionDefinition", + "src": "873:77:1" + } + ], + "id": 112, + "name": "ContractDefinition", + "src": "315:637:1" + } + ], + "name": "SourceUnit" + }, + "networks": { + "1494889277189": { + "address": "0x657b316c4c7df70999a69c2475e59152f87a04aa", + "links": { + "ConvertLib": { + "address": "0x7bcc63d45790e23f6e9bc3514e1ab5af649302d0", + "events": { + "0xa163a6249e860c278ef4049759a7f7c7e8c141d30fd634fda9b5a6a95d111a30": { + "anonymous": false, + "inputs": [], + "name": "Test", + "type": "event" + } + } + } + } + } + }, + "updatedAt": "2017-05-15T20:46:00Z", + "schemaVersion": "0.0.5" +} diff --git a/test/options.js b/test/options.js index 23a4acf..69905f6 100644 --- a/test/options.js +++ b/test/options.js @@ -7,13 +7,7 @@ describe("options", function() { "x-from-dependency": "adder/Adder.sol" } - options = Schema.normalizeOptions(options); + options = Schema.normalize(options); assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - - options = Schema.generateBinary(options); - assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - - options = Schema.generateBinary(options, {"x-another-option": "exists"}); - assert.equal(options["x-another-option"], "exists"); }); }); diff --git a/test/schema.js b/test/schema.js new file mode 100644 index 0000000..cafc00c --- /dev/null +++ b/test/schema.js @@ -0,0 +1,50 @@ +var fs = require("fs"); +var spec = require("../spec/contract.spec.json"); +var Ajv = require("ajv"); +var assert = require("assert"); +var Schema = require("../index.js"); + +var MetaCoin = require("./MetaCoin.json"); + +describe("Schema", function() { + var validator; + var invalidSchemaError; + + before("load schema library", function() { + var ajv = new Ajv(); + try { + validator = ajv.compile(spec); + } catch (e) { + invalidSchemaError = e; + } + }); + + it("validates as json-schema", function() { + assert.ifError(invalidSchemaError); + }); + + it("validates a simple example", function() { + var valid = validator(MetaCoin); + assert.ifError(validator.errors); + }); + + it("validates correct input", function() { + Schema.validate(MetaCoin) + }); + + it("throws exception on invalid input", function() { + var invalid = { + "abi": -1 + }; + + try { + Schema.validate(invalid) + } catch (errors) { + var abiErrors = errors.filter(function(error) { + return error.dataPath === ".abi" + }); + assert(abiErrors); + } + }); + +}); diff --git a/test/solc.js b/test/solc.js new file mode 100644 index 0000000..c3b8061 --- /dev/null +++ b/test/solc.js @@ -0,0 +1,67 @@ +var assert = require("assert"); +var solc = require("solc"); +var Schema = require("../"); + +describe("solc", function() { + var exampleSolidity = "contract A { function doStuff() {} } \n\n contract B { function somethingElse() {} }"; + + it("processes solc compile output correctly", function(done) { + this.timeout(5000); + var result = solc.compile(exampleSolidity, 1); + + var data = result.contracts[":A"]; + + var A = Schema.normalize(data); + + assert.deepEqual(A.abi, JSON.parse(data.interface)); + assert.equal(A.bytecode, "0x" + data.bytecode); + assert.equal(A.deployedBytecode, "0x" + data.runtimeBytecode); + assert.equal(A.sourceMap, data.srcmap); + assert.equal(A.deployedSourceMap, data.srcmapRuntime); + + done(); + }); + + it("processes solc compileStandard output correctly", function(done) { + this.timeout(5000); + + var solcIn = JSON.stringify({ + language: "Solidity", + sources: { + "A.sol": { + "content": exampleSolidity + } + } + }); + var solcOut = solc.compileStandard(solcIn); + + // contracts now grouped by solidity source file + var rawA = JSON.parse(solcOut).contracts["A.sol"].A; + + var A = Schema.normalize(rawA); + + var expected = { + abi: rawA.abi, + bytecode: "0x" + rawA.evm.bytecode.object, + deployedBytecode: "0x" + rawA.evm.deployedBytecode.object, + sourceMap: rawA.evm.bytecode.sourceMap, + deployedSourceMap: rawA.evm.deployedBytecode.sourceMap + }; + + Object.keys(expected).forEach(function (key) { + var expectedValue = expected[key]; + var actualValue = A[key]; + + assert.deepEqual( + actualValue, expectedValue, + "Mismatched schema output for key `" + key + "` (" + + JSON.stringify(actualValue) + " != " + JSON.stringify(expectedValue) + + ")" + ); + }); + + // throws error if invalid + Schema.validate(A); + done(); + }); +});