From d1014f3f731ab9feaf04b0c83efc2ed3d0a4f95c Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 12 May 2017 14:05:11 -0400 Subject: [PATCH 01/19] Update object generation re: deployedBytecode - Add runtimeBytecode -> deployedBytecode normalization - Ensure deployedBytecode starts with 0x in generated output --- index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 8114772..0f79076 100644 --- a/index.js +++ b/index.js @@ -38,7 +38,8 @@ var TruffleSchema = { "binary": "bytecode", "srcmap": "sourceMap", "srcmapRuntime": "deployedSourceMap", - "interface": "abi" + "interface": "abi", + "runtimeBytecode": "deployedBytecode" }; // Merge options/contract object first, then extra_options @@ -108,11 +109,14 @@ var TruffleSchema = { obj.contract_name = obj.contract_name || "Contract"; - // Ensure unlinked binary starts with a 0x + // Ensure bytecode/deployedBytecode start with 0x // TODO: Remove this and enforce it through json schema if (obj.bytecode && obj.bytecode.indexOf("0x") < 0) { obj.bytecode = "0x" + obj.bytecode; } + if (obj.deployedBytecode && obj.deployedBytecode.indexOf("0x") < 0) { + obj.deployedBytecode = "0x" + obj.deployedBytecode; + } var updated_at = new Date().getTime(); From a4c39c5d9d9ce8ea009f6b8c8ac8234aba2ede40 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 12 May 2017 13:44:47 -0400 Subject: [PATCH 02/19] Fix test for updated Schema interface --- test/options.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/options.js b/test/options.js index 23a4acf..2da5ea5 100644 --- a/test/options.js +++ b/test/options.js @@ -7,13 +7,13 @@ describe("options", function() { "x-from-dependency": "adder/Adder.sol" } - options = Schema.normalizeOptions(options); + options = Schema.normalizeInput(options); assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - options = Schema.generateBinary(options); + options = Schema.generateObject(options); assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - options = Schema.generateBinary(options, {"x-another-option": "exists"}); + options = Schema.generateObject(options, {"x-another-option": "exists"}); assert.equal(options["x-another-option"], "exists"); }); }); From 022b56df2b22168af85c6979c9ef96fb2aa7ccd7 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Mon, 15 May 2017 20:56:29 -0400 Subject: [PATCH 03/19] Add initial pass writing json-schema, basic smoketests --- package.json | 1 + spec/contract.spec.json | 105 ++++ test/MetaCoin.json | 1041 +++++++++++++++++++++++++++++++++++++++ test/schema.js | 30 ++ 4 files changed, 1177 insertions(+) create mode 100644 spec/contract.spec.json create mode 100644 test/MetaCoin.json create mode 100644 test/schema.js diff --git a/package.json b/package.json index 04a2777..43fbbf9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "homepage": "https://github.com/trufflesuite/truffle-schema#readme", "dependencies": { + "ajv": "^5.1.1", "crypto-js": "^3.1.9-1" }, "devDependencies": { diff --git a/spec/contract.spec.json b/spec/contract.spec.json new file mode 100644 index 0000000..1ffff9d --- /dev/null +++ b/spec/contract.spec.json @@ -0,0 +1,105 @@ +{ + "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" }, + "address": { "$ref": "#/definitions/Address" }, + "networks": { + "patternProperties": { + "^[a-zA-Z0-9]+$": { "$ref": "#/definitions/Network" } + }, + "additionalProperties": false + }, + "schemaVersion": { "$ref": "#/definitions/SchemaVersion" }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + ], + "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" }, + "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": { + "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..4cdb0e3 --- /dev/null +++ b/test/MetaCoin.json @@ -0,0 +1,1041 @@ +{ + "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" + }, + "address": "0x4a5092ae2f237d76f25691fff90955545ba2dc78", + "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/schema.js b/test/schema.js new file mode 100644 index 0000000..e7f9589 --- /dev/null +++ b/test/schema.js @@ -0,0 +1,30 @@ +var fs = require("fs"); +var spec = require("../spec/contract.spec.json"); +var Ajv = require("ajv"); +var assert = require("assert"); + +var MetaCoinJSON = 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(MetaCoinJSON); + assert.ifError(validator.errors); + }); + +}); From 724323d5167a5b5d2149a498c56a48600fa0e774 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Tue, 16 May 2017 14:18:36 -0400 Subject: [PATCH 04/19] Define Schema.validateContractObject --- index.js | 17 +++++++++++++++++ test/schema.js | 27 +++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 0f79076..8c5653d 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,26 @@ var sha3 = require("crypto-js/sha3"); var schema_version = require("./package.json").version; +var Ajv = require("ajv"); +var contractSchema = require("./spec/contract.spec.json"); // TODO: This whole thing should have a json schema. var TruffleSchema = { + // Return a promise to validate a contract object + // - Resolves as validated `contractObj` + // - Rejects with list of errors from schema validator + validateContractObject: function(contractObj) { + return new Promise(function (resolve, reject) { + var ajv = new Ajv(); + var validate = ajv.compile(contractSchema); + if (validate(contractObj)) { + resolve(contractObj); + } else { + reject(validate.errors); + } + }); + }, + // Normalize options passed in to be the exact options required // for truffle-contract. // diff --git a/test/schema.js b/test/schema.js index e7f9589..df637a7 100644 --- a/test/schema.js +++ b/test/schema.js @@ -2,8 +2,9 @@ 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 MetaCoinJSON = require("./MetaCoin.json"); +var MetaCoin = require("./MetaCoin.json"); describe("Schema", function() { var validator; @@ -23,8 +24,30 @@ describe("Schema", function() { }); it("validates a simple example", function() { - var valid = validator(MetaCoinJSON); + var valid = validator(MetaCoin); assert.ifError(validator.errors); }); + it("returns a validation promise with successful `then` behavior", function(done) { + Schema.validateContractObject(MetaCoin) + .then(function() { + done(); + }); + }); + + it("returns a validation promise with successful `catch` behavior", function(done) { + var invalid = { + "address": -1 + }; + + Schema.validateContractObject(invalid) + .catch(function(errors) { + var addressErrors = errors.filter(function(error) { + return error.dataPath === ".address" + }); + assert(addressErrors); + done(); + }); + }); + }); From e1723c0764bfc9362dda3ea3d501197789e1c9f3 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 12 May 2017 14:07:01 -0400 Subject: [PATCH 05/19] Migrate solc test from truffle-contract --- package.json | 3 ++- test/solc.js | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/solc.js diff --git a/package.json b/package.json index 43fbbf9..f7b81f9 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "crypto-js": "^3.1.9-1" }, "devDependencies": { - "mocha": "^3.2.0" + "mocha": "^3.2.0", + "solc": "^0.4.11" } } diff --git a/test/solc.js b/test/solc.js new file mode 100644 index 0000000..d1e26ea --- /dev/null +++ b/test/solc.js @@ -0,0 +1,22 @@ +var assert = require("assert"); +var solc = require("solc"); +var Schema = require("../"); + +describe("solc", function() { + it("Will save files using input directly from solc", function(done) { + this.timeout(5000); + var result = solc.compile("contract A { function doStuff() {} } \n\n contract B { function somethingElse() {} }", 1); + + var data = result.contracts[":A"]; + + var A = Schema.generateObject(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(); + }); +}); From 328783e6586287f7d81f33236dbab1e4cd6d6e46 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Tue, 16 May 2017 17:27:47 -0400 Subject: [PATCH 06/19] Refactor normalization / object generation - Use separate methods for different sources (solc, abstraction, regular opts) - Make `networks`, `address`, etc. (properties related to instance or truffle specifically) explicitly not a part of normalization process - Move code around in `generateObject` to ensure truffle-option copying happens (now that it's gone from normalize) - Switch to camelCase for fields in schema - Add initial test for solc standard output - Remove `x-` check for `normalizeInput`, as that's no longer part of that. --- index.js | 109 +++++++++++++++++++++++++++++++----------------- test/options.js | 3 -- test/solc.js | 51 +++++++++++++++++++++- 3 files changed, 120 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 8c5653d..4992457 100644 --- a/index.js +++ b/index.js @@ -21,36 +21,42 @@ 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 || {}; + normalizeSolcContract: function(standardContract) { + return { + "abi": standardContract.abi, + "bytecode": "0x" + standardContract.evm.bytecode.object, + "deployedBytecode": "0x" + standardContract.evm.deployedBytecode.object, + "sourceMap": standardContract.evm.bytecode.sourceMap, + "deployedSourceMap": standardContract.evm.deployedBytecode.sourceMap, + "ast": undefined // how to get? unsupported by solc right now + } + }, + + normalizeAbstraction: function(abstraction) { + var abstraction = abstraction.toJSON(); + return { + "abi": abstraction.abi, + "bytecode": abstraction.bytecode, + "deployedBytecode": abstraction.deployedBytecode, + "sourceMap": abstraction.sourceMap, + "deployedSourceMap": abstraction.deployedSourceMap, + "ast": abstraction.ast, + } + }, + + normalizeOptions: function(options, extraOptions) { + extraOptions = extraOptions || {}; var normalized = {}; - var expected_keys = [ - "contract_name", + var expectedKeys = [ "abi", "bytecode", "deployedBytecode", "sourceMap", "deployedSourceMap", - "linkReferences", - "deployedLinkReferences", - "source", - "sourcePath", - "ast", - "address", - "networks", - "updated_at" + "ast" ]; - var deprecated_key_mappings = { + var deprecatedKeyMappings = { "unlinked_binary": "bytecode", "binary": "bytecode", "srcmap": "sourceMap", @@ -60,7 +66,7 @@ var TruffleSchema = { }; // Merge options/contract object first, then extra_options - expected_keys.forEach(function(key) { + expectedKeys.forEach(function(key) { var value; try { @@ -76,7 +82,7 @@ var TruffleSchema = { try { // Will throw an error if key == address and address doesn't exist. - value = extra_options[key]; + value = extraOptions[key]; if (value != undefined) { normalized[key] = value; @@ -86,11 +92,11 @@ var TruffleSchema = { } }); - Object.keys(deprecated_key_mappings).forEach(function(deprecated_key) { - var mapped_key = deprecated_key_mappings[deprecated_key]; + Object.keys(deprecatedKeyMappings).forEach(function(deprecatedKey) { + var mappedKey = deprecatedKeyMappings[deprecatedKey]; - if (normalized[mapped_key] == null) { - normalized[mapped_key] = options[deprecated_key] || extra_options[deprecated_key]; + if (normalized[mappedKey] == null) { + normalized[mappedKey] = options[deprecatedKey] || extraOptions[deprecatedKey]; } }); @@ -98,33 +104,60 @@ var TruffleSchema = { normalized.abi = JSON.parse(normalized.abi); } - this.copyCustomOptions(options, normalized); - return normalized; }, + isSolcOutput: function(obj) { + var matches; + try { + matches = JSON.parse(obj.metadata).language === "Solidity"; + } catch (e) { + matches = false; + } + return matches; + }, + + isAbstraction: function(obj) { + try { + return obj.contract; + } catch (e) { + return false; + } + }, + // 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) { + var obj; + existing_object = existing_object || {}; extra_options = extra_options || {}; options.networks = options.networks || {}; - existing_object.networks = existing_object.networks || {}; + if (this.isSolcOutput(existing_object)) { + obj = this.normalizeSolcContract(existing_object); + } else if (this.isAbstraction(existing_object)) { + obj = this.normalizeAbstraction(existing_object); + } else { + obj = this.normalizeOptions(options, extra_options); + } + + 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); + this.copyCustomOptions(options, obj); + this.copyCustomOptions(existing_object, obj); + if (options.overwrite == true) { existing_object = {}; } - obj.contract_name = obj.contract_name || "Contract"; + obj.contractName = obj.contractName || "Contract"; // Ensure bytecode/deployedBytecode start with 0x // TODO: Remove this and enforce it through json schema @@ -135,14 +168,14 @@ var TruffleSchema = { obj.deployedBytecode = "0x" + obj.deployedBytecode; } - var updated_at = new Date().getTime(); + var updatedAt = new Date().toISOString(); - obj.schema_version = schema_version; + obj.schemaVersion = schema_version; if (extra_options.dirty !== false) { - obj.updated_at = updated_at; + obj.updatedAt = updatedAt; } else { - obj.updated_at = options.updated_at || existing_object.updated_at || updated_at; + obj.updatedAt = options.updatedAt || existing_object.updatedAt || updatedAt; } this.copyCustomOptions(options, obj); diff --git a/test/options.js b/test/options.js index 2da5ea5..3305c17 100644 --- a/test/options.js +++ b/test/options.js @@ -7,9 +7,6 @@ describe("options", function() { "x-from-dependency": "adder/Adder.sol" } - options = Schema.normalizeInput(options); - assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - options = Schema.generateObject(options); assert.equal(options["x-from-dependency"], "adder/Adder.sol"); diff --git a/test/solc.js b/test/solc.js index d1e26ea..9a9f767 100644 --- a/test/solc.js +++ b/test/solc.js @@ -3,9 +3,11 @@ var solc = require("solc"); var Schema = require("../"); describe("solc", function() { - it("Will save files using input directly from solc", function(done) { + 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("contract A { function doStuff() {} } \n\n contract B { function somethingElse() {} }", 1); + var result = solc.compile(exampleSolidity, 1); var data = result.contracts[":A"]; @@ -19,4 +21,49 @@ describe("solc", function() { done(); }); + + it("processes solc compileStandard output correctly", function() { + 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.generateObject({}, 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) + + ")" + ); + }); + + return Schema + .validateContractObject(A) + .catch(function (errors) { + assert.ifError(errors); + }); + }); }); From ed7f0d962350f9e204374f9a952f44c78ed24d1e Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Thu, 18 May 2017 15:13:16 -0400 Subject: [PATCH 07/19] Refactor data normalization process - Use property compatibility / data transformation mapping to identify where data should come from in a uniform manner. --- index.js | 294 +++++++++++++++++++++++----------------- spec/contract.spec.json | 9 ++ test/schema.js | 4 +- test/solc.js | 4 +- 4 files changed, 179 insertions(+), 132 deletions(-) diff --git a/index.js b/index.js index 4992457..a11c200 100644 --- a/index.js +++ b/index.js @@ -3,13 +3,134 @@ var schema_version = require("./package.json").version; var Ajv = require("ajv"); var contractSchema = require("./spec/contract.spec.json"); -// TODO: This whole thing should have a json schema. + +// some data functions +// + +var getter = function(key, transform) { + if (transform === undefined) { + transform = function(x) { return x }; + } + + return function(obj) { + try { + return transform(obj[key]); + } catch (e) { + return undefined; + } + } +} + +var chain = function() { + var getters = Array.prototype.slice.call(arguments); + return function(obj) { + return getters.reduce(function (cur, get) { + return get(cur); + }, obj); + } +} + +var properties = { + "contractName": { + "additionalSources": [getter("contract_name")] + }, + "abi": { + "additionalSources": [getter("interface")], + "transform": function(value) { + if (typeof value === "string") { + try { + value = JSON.parse(value) + } catch (e) { + value = undefined; + } + } + return value; + } + }, + "bytecode": { + "additionalSources": [ + chain(getter("evm"), getter("bytecode"), getter("object")), + getter("binary"), + getter("unlinked_binary"), + ], + "transform": function(value) { + if (value && value.indexOf("0x") != 0) { + return "0x" + value; + } + } + }, + "deployedBytecode": { + "additionalSources": [ + getter("runtimeBytecode"), + chain(getter("evm"), getter("deployedBytecode"), getter("object")) + ], + "transform": function(value) { + if (value && value.indexOf("0x") != 0) { + return "0x" + value; + } + } + }, + "sourceMap": { + "additionalSources": [ + getter("srcmap"), + chain(getter("evm"), getter("bytecode"), getter("sourceMap")), + ] + }, + "deployedSourceMap": { + "additionalSources": [ + getter("srcmapRuntime"), + chain(getter("evm"), getter("deployedBytecode"), getter("sourceMap")), + ] + }, + "source": { + "additionalSources": [] + }, + "sourcePath": { + "additionalSources": [] + }, + "ast": { + "additionalSources": [] + }, + "address": { + "additionalSources": [] + }, + "networks": { + "additionalSources": [ + // can infer blank network from being given network_id + getter("network_id", function(network_id) { + if (network_id !== undefined) { + var networks = {} + networks[network_id] = {"events": {}, "links": {}}; + return networks; + } + }) + ], + "transform": function(value) { + if (value === undefined) { + return {} + } + } + }, + "schemaVersion": { + "additionalSources": [getter("schema_version")] + }, + "updatedAt": { + "additionalSources": [ + getter("updated_at", function(ms) { + return new Date(ms).toISOString() + }) + ] + } +}; + +// Schema module +// var TruffleSchema = { // Return a promise to validate a contract object // - Resolves as validated `contractObj` // - Rejects with list of errors from schema validator - validateContractObject: function(contractObj) { + validate: function(contractObj) { return new Promise(function (resolve, reject) { var ajv = new Ajv(); var validate = ajv.compile(contractSchema); @@ -21,165 +142,82 @@ var TruffleSchema = { }); }, - normalizeSolcContract: function(standardContract) { - return { - "abi": standardContract.abi, - "bytecode": "0x" + standardContract.evm.bytecode.object, - "deployedBytecode": "0x" + standardContract.evm.deployedBytecode.object, - "sourceMap": standardContract.evm.bytecode.sourceMap, - "deployedSourceMap": standardContract.evm.deployedBytecode.sourceMap, - "ast": undefined // how to get? unsupported by solc right now - } - }, - - normalizeAbstraction: function(abstraction) { - var abstraction = abstraction.toJSON(); - return { - "abi": abstraction.abi, - "bytecode": abstraction.bytecode, - "deployedBytecode": abstraction.deployedBytecode, - "sourceMap": abstraction.sourceMap, - "deployedSourceMap": abstraction.deployedSourceMap, - "ast": abstraction.ast, - } - }, - - normalizeOptions: function(options, extraOptions) { - extraOptions = extraOptions || {}; + // accepts as argument anything that can be turned into a contract object + // returns a contract object + normalize: function(objectable) { + // construct normalized obj var normalized = {}; - var expectedKeys = [ - "abi", - "bytecode", - "deployedBytecode", - "sourceMap", - "deployedSourceMap", - "ast" - ]; - - var deprecatedKeyMappings = { - "unlinked_binary": "bytecode", - "binary": "bytecode", - "srcmap": "sourceMap", - "srcmapRuntime": "deployedSourceMap", - "interface": "abi", - "runtimeBytecode": "deployedBytecode" - }; - - // Merge options/contract object first, then extra_options - expectedKeys.forEach(function(key) { - var value; + Object.keys(properties).forEach(function(key) { + var property = properties[key]; - try { - // Will throw an error if key == address and address doesn't exist. - value = options[key]; + var value; - if (value != undefined) { - normalized[key] = value; - } - } catch (e) { - // Do nothing. + // try the key itself first and then additional sources + var sources = [getter(key)] + if (property.additionalSources) { + sources = sources.concat(property.additionalSources); } - try { - // Will throw an error if key == address and address doesn't exist. - value = extraOptions[key]; - - if (value != undefined) { - normalized[key] = value; - } - } catch (e) { - // Do nothing. + // 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]; + value = source(objectable); } - }); - Object.keys(deprecatedKeyMappings).forEach(function(deprecatedKey) { - var mappedKey = deprecatedKeyMappings[deprecatedKey]; - - if (normalized[mappedKey] == null) { - normalized[mappedKey] = options[deprecatedKey] || extraOptions[deprecatedKey]; + // run source-agnostic transform on value + // (e.g. make sure bytecode begins 0x) + if (property.transform) { + value = property.transform(value); } + + // add resulting (possibly undefined) to normalized obj + normalized[key] = value; }); - if (typeof normalized.abi == "string") { - normalized.abi = JSON.parse(normalized.abi); - } + // copy custom options + this.copyCustomOptions(objectable, normalized); return normalized; }, - isSolcOutput: function(obj) { - var matches; - try { - matches = JSON.parse(obj.metadata).language === "Solidity"; - } catch (e) { - matches = false; - } - return matches; - }, - - isAbstraction: function(obj) { - try { - return obj.contract; - } catch (e) { - return false; - } - }, - // Generate a proper binary from normalized options, and optionally // merge it with an existing binary. - generateObject: function(options, existing_object, extra_options) { - var obj; + generateObject: function(objectable, existingObjectable, options) { + objectable = objectable || {}; + existingObjectable = existingObjectable || {}; - existing_object = existing_object || {}; - extra_options = extra_options || {}; + options = options || {}; - options.networks = options.networks || {}; + obj = this.normalize(objectable); + existingObj = this.normalize(existingObjectable); - if (this.isSolcOutput(existing_object)) { - obj = this.normalizeSolcContract(existing_object); - } else if (this.isAbstraction(existing_object)) { - obj = this.normalizeAbstraction(existing_object); - } else { - obj = this.normalizeOptions(options, extra_options); - } - - 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]; + Object.keys(existingObj).forEach(function(key) { + // networks will be skipped because normalize replaces undefined with {} + if (obj[key] === undefined) { + obj[key] = existingObj[key]; + } }); - this.copyCustomOptions(options, obj); - this.copyCustomOptions(existing_object, obj); - + Object.keys(existingObj.networks).forEach(function(network_id) { + obj.networks[network_id] = existingObj.networks[network_id]; + }); - if (options.overwrite == true) { - existing_object = {}; - } + // if (options.overwrite == true) { + // e = {}; + // } obj.contractName = obj.contractName || "Contract"; - // Ensure bytecode/deployedBytecode start with 0x - // TODO: Remove this and enforce it through json schema - if (obj.bytecode && obj.bytecode.indexOf("0x") < 0) { - obj.bytecode = "0x" + obj.bytecode; - } - if (obj.deployedBytecode && obj.deployedBytecode.indexOf("0x") < 0) { - obj.deployedBytecode = "0x" + obj.deployedBytecode; - } - var updatedAt = new Date().toISOString(); obj.schemaVersion = schema_version; - if (extra_options.dirty !== false) { + if (options.dirty !== false) { obj.updatedAt = updatedAt; } else { - obj.updatedAt = options.updatedAt || existing_object.updatedAt || updatedAt; + obj.updatedAt = obj.updatedAt || updatedAt; } - this.copyCustomOptions(options, obj); - return obj; }, diff --git a/spec/contract.spec.json b/spec/contract.spec.json index 1ffff9d..cd7478c 100644 --- a/spec/contract.spec.json +++ b/spec/contract.spec.json @@ -37,6 +37,15 @@ "format": "date-time" } }, + "patternProperties": { + "^x-": { "anyOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "object" }, + { "type": "array" } + ]} + + }, "additionalProperties": false, "required": [ ], diff --git a/test/schema.js b/test/schema.js index df637a7..9589903 100644 --- a/test/schema.js +++ b/test/schema.js @@ -29,7 +29,7 @@ describe("Schema", function() { }); it("returns a validation promise with successful `then` behavior", function(done) { - Schema.validateContractObject(MetaCoin) + Schema.validate(MetaCoin) .then(function() { done(); }); @@ -40,7 +40,7 @@ describe("Schema", function() { "address": -1 }; - Schema.validateContractObject(invalid) + Schema.validate(invalid) .catch(function(errors) { var addressErrors = errors.filter(function(error) { return error.dataPath === ".address" diff --git a/test/solc.js b/test/solc.js index 9a9f767..228495a 100644 --- a/test/solc.js +++ b/test/solc.js @@ -38,7 +38,7 @@ describe("solc", function() { // contracts now grouped by solidity source file var rawA = JSON.parse(solcOut).contracts["A.sol"].A; - var A = Schema.generateObject({}, rawA); + var A = Schema.generateObject(rawA); var expected = { abi: rawA.abi, @@ -61,7 +61,7 @@ describe("solc", function() { }); return Schema - .validateContractObject(A) + .validate(A) .catch(function (errors) { assert.ifError(errors); }); From c4b4913ec78081cac0d14a7e703ed34ccb261797 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Thu, 18 May 2017 19:02:41 -0400 Subject: [PATCH 08/19] Clean up properties descriptions a bit --- index.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index a11c200..f33cdfb 100644 --- a/index.js +++ b/index.js @@ -49,14 +49,15 @@ var properties = { }, "bytecode": { "additionalSources": [ - chain(getter("evm"), getter("bytecode"), getter("object")), getter("binary"), getter("unlinked_binary"), + chain(getter("evm"), getter("bytecode"), getter("object")) ], "transform": function(value) { if (value && value.indexOf("0x") != 0) { - return "0x" + value; + value = "0x" + value; } + return value; } }, "deployedBytecode": { @@ -66,8 +67,9 @@ var properties = { ], "transform": function(value) { if (value && value.indexOf("0x") != 0) { - return "0x" + value; + value = "0x" + value; } + return value; } }, "sourceMap": { @@ -82,18 +84,9 @@ var properties = { chain(getter("evm"), getter("deployedBytecode"), getter("sourceMap")), ] }, - "source": { - "additionalSources": [] - }, - "sourcePath": { - "additionalSources": [] - }, - "ast": { - "additionalSources": [] - }, - "address": { - "additionalSources": [] - }, + "source": {}, + "sourcePath": {}, + "ast": {}, "networks": { "additionalSources": [ // can infer blank network from being given network_id @@ -107,8 +100,9 @@ var properties = { ], "transform": function(value) { if (value === undefined) { - return {} + value = {} } + return value; } }, "schemaVersion": { From c65b1106b2ebcd55e996118c2af5034bc9baaa11 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Thu, 18 May 2017 19:03:09 -0400 Subject: [PATCH 09/19] Remove address from top-level schema def --- spec/contract.spec.json | 17 +++++++++-------- test/MetaCoin.json | 1 - test/schema.js | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/contract.spec.json b/spec/contract.spec.json index cd7478c..c39bac2 100644 --- a/spec/contract.spec.json +++ b/spec/contract.spec.json @@ -24,7 +24,6 @@ "source": { "$ref": "#/definitions/Source" }, "sourcePath": { "$ref": "#/definitions/SourcePath" }, "ast": { "$ref": "#/definitions/AST" }, - "address": { "$ref": "#/definitions/Address" }, "networks": { "patternProperties": { "^[a-zA-Z0-9]+$": { "$ref": "#/definitions/Network" } @@ -81,6 +80,7 @@ "type": "object", "properties": { "address": { "$ref": "#/definitions/Address" }, + "events": { "$ref": "#/definitions/EventsCollection" }, "links": { "type": "object", "patternProperties": { @@ -94,15 +94,16 @@ "type": "object", "properties": { "address": { "$ref": "#/definitions/Address" }, - "events": { - "type": "object", - "patternProperties": { - "^0x[a-fA-F0-9]{64}$": { "$ref": "#/definitions/Event" } - }, - "additionalProperties": false - } + "events": { "$ref": "#/definitions/EventsCollection" } } }, + "EventsCollection": { + "type": "object", + "patternProperties": { + "^0x[a-fA-F0-9]{64}$": { "$ref": "#/definitions/Event" } + }, + "additionalProperties": false + }, "Event": { "type": "object" }, diff --git a/test/MetaCoin.json b/test/MetaCoin.json index 4cdb0e3..71fc614 100644 --- a/test/MetaCoin.json +++ b/test/MetaCoin.json @@ -1017,7 +1017,6 @@ ], "name": "SourceUnit" }, - "address": "0x4a5092ae2f237d76f25691fff90955545ba2dc78", "networks": { "1494889277189": { "address": "0x657b316c4c7df70999a69c2475e59152f87a04aa", diff --git a/test/schema.js b/test/schema.js index 9589903..a5cf513 100644 --- a/test/schema.js +++ b/test/schema.js @@ -37,15 +37,15 @@ describe("Schema", function() { it("returns a validation promise with successful `catch` behavior", function(done) { var invalid = { - "address": -1 + "abi": -1 }; Schema.validate(invalid) .catch(function(errors) { - var addressErrors = errors.filter(function(error) { - return error.dataPath === ".address" + var abiErrors = errors.filter(function(error) { + return error.dataPath === ".abi" }); - assert(addressErrors); + assert(abiErrors); done(); }); }); From 4e23b1419bafb1168738de40d3895d87397e95fd Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 12:06:49 -0400 Subject: [PATCH 10/19] Update property sourcing - Drop notion of "additional sources" - define properties to list sources explicitly including their canonical name, or default to source only canonical name when `"sources":` is omitted - Allow string values for sources in addition to functions. For string values, assume possible chain operation (e.g. "evm.bytecode.object") and split/chain accordingly --- index.js | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index f33cdfb..214e3d2 100644 --- a/index.js +++ b/index.js @@ -32,10 +32,10 @@ var chain = function() { var properties = { "contractName": { - "additionalSources": [getter("contract_name")] + "sources": ["contractName", "contract_name"] }, "abi": { - "additionalSources": [getter("interface")], + "sources": ["abi", "interface"], "transform": function(value) { if (typeof value === "string") { try { @@ -48,10 +48,8 @@ var properties = { } }, "bytecode": { - "additionalSources": [ - getter("binary"), - getter("unlinked_binary"), - chain(getter("evm"), getter("bytecode"), getter("object")) + "sources": [ + "bytecode", "binary", "unlinked_binary", "evm.bytecode.object" ], "transform": function(value) { if (value && value.indexOf("0x") != 0) { @@ -61,9 +59,8 @@ var properties = { } }, "deployedBytecode": { - "additionalSources": [ - getter("runtimeBytecode"), - chain(getter("evm"), getter("deployedBytecode"), getter("object")) + "sources": [ + "deployedBytecode", "runtimeBytecode", "evm.deployedBytecode.object" ], "transform": function(value) { if (value && value.indexOf("0x") != 0) { @@ -73,22 +70,16 @@ var properties = { } }, "sourceMap": { - "additionalSources": [ - getter("srcmap"), - chain(getter("evm"), getter("bytecode"), getter("sourceMap")), - ] + "sources": ["sourceMap", "srcmap", "evm.bytecode.sourceMap"] }, "deployedSourceMap": { - "additionalSources": [ - getter("srcmapRuntime"), - chain(getter("evm"), getter("deployedBytecode"), getter("sourceMap")), - ] + "sources": ["deployedSourceMap", "srcmapRuntime", "evm.deployedBytecode.sourceMap"] }, "source": {}, "sourcePath": {}, "ast": {}, "networks": { - "additionalSources": [ + "sources": ["networks", // can infer blank network from being given network_id getter("network_id", function(network_id) { if (network_id !== undefined) { @@ -106,11 +97,10 @@ var properties = { } }, "schemaVersion": { - "additionalSources": [getter("schema_version")] + "sources": ["schemaVersion", "schema_version"] }, "updatedAt": { - "additionalSources": [ - getter("updated_at", function(ms) { + "sources": ["updatedAt", getter("updated_at", function(ms) { return new Date(ms).toISOString() }) ] @@ -146,15 +136,23 @@ var TruffleSchema = { var value; - // try the key itself first and then additional sources - var sources = [getter(key)] - if (property.additionalSources) { - sources = sources.concat(property.additionalSources); - } + // 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 objectable, 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 objectable and returns + // value or undefined value = source(objectable); } From e3ddc1dc6d0a4ce02b6a03915584d3f8cf45dfe7 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 12:43:56 -0400 Subject: [PATCH 11/19] Clean-up property transformation magic - Change `objectable` (whatever that means) to `objDirty` - Move `properties` to the top and give it a docstring - Move getter/chain combinators lower, also document --- index.js | 139 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 52 deletions(-) diff --git a/index.js b/index.js index 214e3d2..d0d78a9 100644 --- a/index.js +++ b/index.js @@ -4,32 +4,28 @@ var Ajv = require("ajv"); var contractSchema = require("./spec/contract.spec.json"); -// some data functions -// - -var getter = function(key, transform) { - if (transform === undefined) { - transform = function(x) { return x }; - } - - return function(obj) { - try { - return transform(obj[key]); - } catch (e) { - return undefined; - } - } -} - -var chain = function() { - var getters = Array.prototype.slice.call(arguments); - return function(obj) { - return getters.reduce(function (cur, get) { - return get(cur); - }, obj); - } -} - +/** + * 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"] @@ -79,16 +75,14 @@ var properties = { "sourcePath": {}, "ast": {}, "networks": { - "sources": ["networks", - // can infer blank network from being given network_id - getter("network_id", function(network_id) { - if (network_id !== undefined) { - var networks = {} - networks[network_id] = {"events": {}, "links": {}}; - return networks; - } - }) - ], + // infers blank network from network_id + "sources": ["networks", getter("network_id", function(network_id) { + if (network_id !== undefined) { + var networks = {} + networks[network_id] = {"events": {}, "links": {}}; + return networks; + } + })], "transform": function(value) { if (value === undefined) { value = {} @@ -101,12 +95,53 @@ var properties = { }, "updatedAt": { "sources": ["updatedAt", getter("updated_at", function(ms) { - return new Date(ms).toISOString() - }) - ] + return new Date(ms).toISOString() + })] } }; + +/** + * 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 }; + } + + 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); + } +} + + // Schema module // @@ -128,13 +163,13 @@ var TruffleSchema = { // accepts as argument anything that can be turned into a contract object // returns a contract object - normalize: function(objectable) { - // construct normalized obj + normalize: function(objDirty) { var normalized = {}; + + // iterate over each property Object.keys(properties).forEach(function(key) { var property = properties[key]; - - var value; + var value; // normalized value || undefined // either used the defined sources or assume the key will only ever be // listed as its canonical name (itself) @@ -143,7 +178,7 @@ var TruffleSchema = { // 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 objectable, split and chain + // string refers to path to value in objDirty, split and chain // getters if (typeof source === "string") { var traversals = source.split(".") @@ -151,9 +186,9 @@ var TruffleSchema = { source = chain.apply(null, traversals); } - // source should be a function that takes the objectable and returns + // source should be a function that takes the objDirty and returns // value or undefined - value = source(objectable); + value = source(objDirty); } // run source-agnostic transform on value @@ -167,21 +202,21 @@ var TruffleSchema = { }); // copy custom options - this.copyCustomOptions(objectable, normalized); + this.copyCustomOptions(objDirty, normalized); return normalized; }, // Generate a proper binary from normalized options, and optionally // merge it with an existing binary. - generateObject: function(objectable, existingObjectable, options) { - objectable = objectable || {}; - existingObjectable = existingObjectable || {}; + generateObject: function(objDirty, existingObjDirty, options) { + objDirty = objDirty || {}; + existingObjDirty = existingObjDirty || {}; options = options || {}; - obj = this.normalize(objectable); - existingObj = this.normalize(existingObjectable); + obj = this.normalize(objDirty); + existingObj = this.normalize(existingObjDirty); Object.keys(existingObj).forEach(function(key) { // networks will be skipped because normalize replaces undefined with {} From 206e412250eb9365825a296ad46fdbab010862ea Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 12:47:01 -0400 Subject: [PATCH 12/19] Remove meaningless comment block --- index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/index.js b/index.js index d0d78a9..3f2b6c9 100644 --- a/index.js +++ b/index.js @@ -229,10 +229,6 @@ var TruffleSchema = { obj.networks[network_id] = existingObj.networks[network_id]; }); - // if (options.overwrite == true) { - // e = {}; - // } - obj.contractName = obj.contractName || "Contract"; var updatedAt = new Date().toISOString(); From a7327c1417cf12fe69cab7ab7ce4f6a8c6a843b9 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 13:24:52 -0400 Subject: [PATCH 13/19] Remove manual getter invocations from properties --- index.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 3f2b6c9..bbfb029 100644 --- a/index.js +++ b/index.js @@ -76,13 +76,6 @@ var properties = { "ast": {}, "networks": { // infers blank network from network_id - "sources": ["networks", getter("network_id", function(network_id) { - if (network_id !== undefined) { - var networks = {} - networks[network_id] = {"events": {}, "links": {}}; - return networks; - } - })], "transform": function(value) { if (value === undefined) { value = {} @@ -94,9 +87,13 @@ var properties = { "sources": ["schemaVersion", "schema_version"] }, "updatedAt": { - "sources": ["updatedAt", getter("updated_at", function(ms) { - return new Date(ms).toISOString() - })] + "sources": ["updatedAt", "updated_at"], + "transform": function(value) { + if (typeof value === "number") { + value = new Date(value).toISOString(); + } + return value; + } } }; From f8db9233b7f95c0413d34d37e136c77b3a1e218f Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 17:32:14 -0400 Subject: [PATCH 14/19] Minor cleanup for normalize - Rename schema_version import to pkgVersion - Inline copyCustomOptions (not needed separately) - Eliminate contractName default --- index.js | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index bbfb029..f21570a 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ var sha3 = require("crypto-js/sha3"); -var schema_version = require("./package.json").version; +var pkgVersion = require("./package.json").version; var Ajv = require("ajv"); var contractSchema = require("./spec/contract.spec.json"); @@ -75,7 +75,6 @@ var properties = { "sourcePath": {}, "ast": {}, "networks": { - // infers blank network from network_id "transform": function(value) { if (value === undefined) { value = {} @@ -198,8 +197,15 @@ var TruffleSchema = { normalized[key] = value; }); - // copy custom options - this.copyCustomOptions(objDirty, normalized); + // 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; }, @@ -226,11 +232,8 @@ var TruffleSchema = { obj.networks[network_id] = existingObj.networks[network_id]; }); - obj.contractName = obj.contractName || "Contract"; - var updatedAt = new Date().toISOString(); - obj.schemaVersion = schema_version; if (options.dirty !== false) { obj.updatedAt = updatedAt; @@ -239,23 +242,6 @@ var TruffleSchema = { } return obj; - }, - - copyCustomOptions: function(from, to) { - // Now let all x- options through. - Object.keys(from).forEach(function(key) { - if (key.indexOf("x-") != 0) return; - - try { - value = from[key]; - - if (value != undefined) { - to[key] = value; - } - } catch (e) { - // Do nothing. - } - }); } }; From 8c059415feb299cdba8e2f00b178a869e9377cdf Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 17:34:18 -0400 Subject: [PATCH 15/19] Rename TruffleSchema to TruffleContractSchema --- index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index f21570a..d0129bf 100644 --- a/index.js +++ b/index.js @@ -141,7 +141,7 @@ function chain() { // Schema module // -var TruffleSchema = { +var TruffleContractSchema = { // Return a promise to validate a contract object // - Resolves as validated `contractObj` // - Rejects with list of errors from schema validator @@ -234,7 +234,6 @@ var TruffleSchema = { var updatedAt = new Date().toISOString(); - if (options.dirty !== false) { obj.updatedAt = updatedAt; } else { @@ -245,4 +244,4 @@ var TruffleSchema = { } }; -module.exports = TruffleSchema; +module.exports = TruffleContractSchema; From d74a420d8ba902afa02858899bf94f87adaa782d Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 17:46:13 -0400 Subject: [PATCH 16/19] Remove usage of generateObject in tests --- test/options.js | 5 +---- test/solc.js | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/test/options.js b/test/options.js index 3305c17..69905f6 100644 --- a/test/options.js +++ b/test/options.js @@ -7,10 +7,7 @@ describe("options", function() { "x-from-dependency": "adder/Adder.sol" } - options = Schema.generateObject(options); + options = Schema.normalize(options); assert.equal(options["x-from-dependency"], "adder/Adder.sol"); - - options = Schema.generateObject(options, {"x-another-option": "exists"}); - assert.equal(options["x-another-option"], "exists"); }); }); diff --git a/test/solc.js b/test/solc.js index 228495a..71903bf 100644 --- a/test/solc.js +++ b/test/solc.js @@ -11,7 +11,7 @@ describe("solc", function() { var data = result.contracts[":A"]; - var A = Schema.generateObject(data); + var A = Schema.normalize(data); assert.deepEqual(A.abi, JSON.parse(data.interface)); assert.equal(A.bytecode, "0x" + data.bytecode); @@ -38,7 +38,7 @@ describe("solc", function() { // contracts now grouped by solidity source file var rawA = JSON.parse(solcOut).contracts["A.sol"].A; - var A = Schema.generateObject(rawA); + var A = Schema.normalize(rawA); var expected = { abi: rawA.abi, From 598172eac5cd1c54970bdfa29135ee5a2896e199 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 18:05:34 -0400 Subject: [PATCH 17/19] Delete generateObject --- index.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/index.js b/index.js index d0129bf..bc06b0e 100644 --- a/index.js +++ b/index.js @@ -208,39 +208,6 @@ var TruffleContractSchema = { normalized.schemaVersion = pkgVersion; return normalized; - }, - - // Generate a proper binary from normalized options, and optionally - // merge it with an existing binary. - generateObject: function(objDirty, existingObjDirty, options) { - objDirty = objDirty || {}; - existingObjDirty = existingObjDirty || {}; - - options = options || {}; - - obj = this.normalize(objDirty); - existingObj = this.normalize(existingObjDirty); - - Object.keys(existingObj).forEach(function(key) { - // networks will be skipped because normalize replaces undefined with {} - if (obj[key] === undefined) { - obj[key] = existingObj[key]; - } - }); - - Object.keys(existingObj.networks).forEach(function(network_id) { - obj.networks[network_id] = existingObj.networks[network_id]; - }); - - var updatedAt = new Date().toISOString(); - - if (options.dirty !== false) { - obj.updatedAt = updatedAt; - } else { - obj.updatedAt = obj.updatedAt || updatedAt; - } - - return obj; } }; From aeb6f8aa66a629ea3c0373f54fe38992c0f0f15a Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 18:09:22 -0400 Subject: [PATCH 18/19] Make abi as required --- spec/contract.spec.json | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/contract.spec.json b/spec/contract.spec.json index c39bac2..0f60d1e 100644 --- a/spec/contract.spec.json +++ b/spec/contract.spec.json @@ -47,6 +47,7 @@ }, "additionalProperties": false, "required": [ + "abi" ], "definitions": { "ContractName": { From 32cb65f4811fdf8d9801e2e1be9190614712252e Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 19 May 2017 19:09:33 -0400 Subject: [PATCH 19/19] Make validate synchronous --- index.js | 16 +++++++--------- test/schema.js | 21 +++++++++------------ test/solc.js | 10 ++++------ 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index bc06b0e..5ff6ec3 100644 --- a/index.js +++ b/index.js @@ -146,15 +146,13 @@ var TruffleContractSchema = { // - Resolves as validated `contractObj` // - Rejects with list of errors from schema validator validate: function(contractObj) { - return new Promise(function (resolve, reject) { - var ajv = new Ajv(); - var validate = ajv.compile(contractSchema); - if (validate(contractObj)) { - resolve(contractObj); - } else { - reject(validate.errors); - } - }); + var ajv = new Ajv(); + var validate = ajv.compile(contractSchema); + if (validate(contractObj)) { + return contractObj; + } else { + throw validate.errors; + } }, // accepts as argument anything that can be turned into a contract object diff --git a/test/schema.js b/test/schema.js index a5cf513..cafc00c 100644 --- a/test/schema.js +++ b/test/schema.js @@ -28,26 +28,23 @@ describe("Schema", function() { assert.ifError(validator.errors); }); - it("returns a validation promise with successful `then` behavior", function(done) { + it("validates correct input", function() { Schema.validate(MetaCoin) - .then(function() { - done(); - }); }); - it("returns a validation promise with successful `catch` behavior", function(done) { + it("throws exception on invalid input", function() { var invalid = { "abi": -1 }; - Schema.validate(invalid) - .catch(function(errors) { - var abiErrors = errors.filter(function(error) { - return error.dataPath === ".abi" - }); - assert(abiErrors); - done(); + 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 index 71903bf..c3b8061 100644 --- a/test/solc.js +++ b/test/solc.js @@ -22,7 +22,7 @@ describe("solc", function() { done(); }); - it("processes solc compileStandard output correctly", function() { + it("processes solc compileStandard output correctly", function(done) { this.timeout(5000); var solcIn = JSON.stringify({ @@ -60,10 +60,8 @@ describe("solc", function() { ); }); - return Schema - .validate(A) - .catch(function (errors) { - assert.ifError(errors); - }); + // throws error if invalid + Schema.validate(A); + done(); }); });