Skip to content
This repository has been archived by the owner on Jun 14, 2018. It is now read-only.

Formalize schema, standardize on normalization process, and improve solc support #3

Merged
merged 20 commits into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost wonder if this is too explicit (and I would counter my own argument by asking if it matters one way or another). Still, I like validate() over validateContractObject(), especially in the case where there's only one type of thing that ever gets validated. If there are two types of things, I would definitely call for the explicit function name. What's your take on simply naming it validate()?

Additionally, why is this wrapped inside a promise? It looks as though Ajv's validate function is synchronous, meaning you can have it throw instead of promisified. I recommend only wrapping it in a promise if it's doing asynchronous work or if there's a high likelihood we'll add asynchronous code to the function in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still, I like validate() over validateContractObject()

Agreed, I'll switch it.

Additionally, why is this wrapped inside a promise? It looks as though Ajv's validate function is synchronous, meaning you can have it throw instead of promisified. I recommend only wrapping it in a promise if it's doing asynchronous work or if there's a high likelihood we'll add asynchronous code to the function in the future.

I figured that I didn't want to make any assumptions about the underlying schema validation library. If Ajv turns out to not be the best choice and we have to change it to something that uses Promises, we'd already be covered.

But it might be heavy handed, I'm kind of just playing around to see what looks good / makes sense. Your call!


// Normalize options passed in to be the exact options required
// for truffle-contract.
//
Expand Down Expand Up @@ -38,7 +55,8 @@ var TruffleSchema = {
"binary": "bytecode",
"srcmap": "sourceMap",
"srcmapRuntime": "deployedSourceMap",
"interface": "abi"
"interface": "abi",
"runtimeBytecode": "deployedBytecode"
};

// Merge options/contract object first, then extra_options
Expand Down Expand Up @@ -108,11 +126,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();

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"homepage": "https://github.com/trufflesuite/truffle-schema#readme",
"dependencies": {
"ajv": "^5.1.1",
"crypto-js": "^3.1.9-1"
},
"devDependencies": {
Expand Down
105 changes: 105 additions & 0 deletions spec/contract.spec.json
Original file line number Diff line number Diff line change
@@ -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_]*$"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if contract names should be so restricted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This restriction seems fine. Might be worth seeing what Solidity restricts them to.

},
"Address": {
"type": "string",
"pattern": "^0x[a-fA-F0-9]{40}$"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's bound to be lots of these specific-number-of-bytes-in-hex regexes... not sure if there's any way around that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there needs to be. This looks good to me.

},
"ABI": {
"type": "array"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the ABI part of the spec? ethpm/ethpm-spec#20 indicates that the recommendation is "opaque JSON"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"doing what ethpm does" is probably a good strategy. Our validator within the truffle-contract-schema can include the abi schema and validate that as well.

},
"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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the AST representation in scope for something to be part of this spec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not in scope - at least, don't write your own validator for it. I'd be surprised if solidity doesn't already have one. If they do, treat it like the abi above; if they don't, leave it alone for now.

},
"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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each event is a subset of the ABI; we probably want to include that in the spec here? So would we want to include the ABI in the spec, if so?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't, see above. Although you could copy the event portion of the abi json schema since we're using that within our own. I think either way (either copying the abi's schema for events, or doing nothing) is good for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait I'm confused - I don't believe there is a json-schema for the ABI, so there's no event portion. I will double-check in EthPM's Gitter to see what Piper concluded on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I guess @tcoulter the question I would have is "how much does truffle treat the ABI/events as a black box, how much does truffle need to know the internals?"

I would guess "very needs to know the internals", and so I would think it's probably worth specing those out fully, even if there's some risk they'll change upstream.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like Piper linked to a python version of the json schema. That's what I was referring to. Honestly I didn't click in. If there's no javascript json schema, then lets ignore it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess "very needs to know the internals", and so I would think it's probably worth specing those out fully, even if there's some risk they'll change upstream.

If you think you can spec those out without taking too much time, go for it. If you think it'll take awhile, leave it as a black box.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think you can spec those out without taking too much time, go for it. If you think it'll take awhile, leave it as a black box.

I'll look into that after doing code stuff. I don't think it will take a lot of time, but I'm treating this pass at the schema as a first version anyway, so certainly can leave things out of scope.

},
"SchemaVersion": {
"type": "string",
"pattern": "[0-9]+\\.[0-9]+\\.[0-9]+"
}
}
}
Loading