truffle-plugin-stdjsonin 0.5.14
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +37 -0
- package/stdjsonin.js +149 -0
- package/truffle-plugin.json +5 -0
- package/util.js +79 -0
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Mehrdad Salehi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# truffle-plugin-stdjsonin
|
2
|
+
|
3
|
+
A [Truffle](https://trufflesuite.com/index.html) plugin for generating a flat Solidity Json Input file.
|
4
|
+
|
5
|
+
The [Solidity Json Input](https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html#compiler-input-and-output-json-description) format is preferred over [flattening](https://www.npmjs.com/package/truffle-flattener) your files during verification on Etherscan as it :
|
6
|
+
- preserves code formatting
|
7
|
+
- maintains multipart files
|
8
|
+
- embeds compiler settings, including optimization and bytecodehash
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
1. Install the plugin using npm
|
12
|
+
```
|
13
|
+
npm install -D https://github.com/nhancv/truffle-plugin-stdjsonin/
|
14
|
+
```
|
15
|
+
2. Add the plugin to your `truffle-config.js` file
|
16
|
+
```javascript
|
17
|
+
module.exports = {
|
18
|
+
/* ... rest of truffle-config */
|
19
|
+
|
20
|
+
plugins: [
|
21
|
+
'truffle-plugin-stdjsonin'
|
22
|
+
]
|
23
|
+
}
|
24
|
+
```
|
25
|
+
## Usage
|
26
|
+
1. Run the plugin on your specified contract name
|
27
|
+
```
|
28
|
+
truffle run stdjsonin ContractName
|
29
|
+
```
|
30
|
+
A `ContractName-Input.json` file is generated in your project directory.
|
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "truffle-plugin-stdjsonin",
|
3
|
+
"version": "0.5.14",
|
4
|
+
"description": "generate Standrad JSON Input from the Truffle CLI",
|
5
|
+
"repository": "https://github.com/nhancv/truffle-plugin-stdjsonin",
|
6
|
+
"main": "stdjsonin.js",
|
7
|
+
"files": [
|
8
|
+
"util.js",
|
9
|
+
"stdjsonin.js",
|
10
|
+
"truffle-plugin.json"
|
11
|
+
],
|
12
|
+
"scripts": {
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
14
|
+
"lint": "eslint ."
|
15
|
+
},
|
16
|
+
"keywords": [
|
17
|
+
"Standard JSON Input",
|
18
|
+
"truffle",
|
19
|
+
"plugin",
|
20
|
+
"ethereum",
|
21
|
+
"etherscan",
|
22
|
+
"verify"
|
23
|
+
],
|
24
|
+
"author": "Mehrdad Salehi",
|
25
|
+
"license": "MIT",
|
26
|
+
"dependencies": {
|
27
|
+
"cli-logger": "^0.5.40"
|
28
|
+
},
|
29
|
+
"devDependencies": {
|
30
|
+
"eslint": "^7.32.0",
|
31
|
+
"eslint-config-standard": "^16.0.3",
|
32
|
+
"eslint-plugin-import": "^2.24.2",
|
33
|
+
"eslint-plugin-node": "^11.1.0",
|
34
|
+
"eslint-plugin-promise": "^5.1.0",
|
35
|
+
"eslint-plugin-standard": "^5.0.0"
|
36
|
+
}
|
37
|
+
}
|
package/stdjsonin.js
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
const cliLogger = require("cli-logger");
|
2
|
+
const fs = require("fs");
|
3
|
+
const path = require("path");
|
4
|
+
const { enforce, enforceOrThrow, normaliseContractPath } = require("./util");
|
5
|
+
const { version } = require("./package.json");
|
6
|
+
|
7
|
+
const logger = cliLogger({ level: "info" });
|
8
|
+
|
9
|
+
module.exports = async (config) => {
|
10
|
+
// Set debug logging
|
11
|
+
if (config.debug) logger.level("debug");
|
12
|
+
logger.debug("DEBUG logging is turned ON");
|
13
|
+
logger.debug(`Running truffle-plugin-stdjsonin v${version}`);
|
14
|
+
|
15
|
+
const options = parseConfig(config);
|
16
|
+
const contractNameAddressPairs = config._.slice(1);
|
17
|
+
|
18
|
+
for (const contractNameAddressPair of contractNameAddressPairs) {
|
19
|
+
logger.info(`Generating Standard JSON Input ${contractNameAddressPair}`);
|
20
|
+
try {
|
21
|
+
const contractName = contractNameAddressPair.split("@")[0];
|
22
|
+
const artifact = getArtifact(contractName, options);
|
23
|
+
const inputJSON = getInputJSON(artifact, options);
|
24
|
+
fs.writeFileSync(`${contractName}-input.json`, JSON.stringify(inputJSON));
|
25
|
+
console.log(
|
26
|
+
`Standard JSON Input for ${contractName} is saved in ${contractName}-input.json`
|
27
|
+
);
|
28
|
+
console.log(
|
29
|
+
"[Optional step] trying to compile and check with generated Standard JSON Input..."
|
30
|
+
);
|
31
|
+
tryCompileAndMatch(artifact, inputJSON, contractName);
|
32
|
+
} catch (error) {
|
33
|
+
logger.error(error.message);
|
34
|
+
}
|
35
|
+
logger.info();
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
const parseConfig = (config) => {
|
40
|
+
enforce(config._.length > 1, "No contract name(s) specified", logger);
|
41
|
+
|
42
|
+
const workingDir = config.working_directory;
|
43
|
+
const contractsBuildDir = config.contracts_build_directory;
|
44
|
+
const contractsDir = config.contracts_directory;
|
45
|
+
|
46
|
+
let forceConstructorArgsType, forceConstructorArgs;
|
47
|
+
if (config.forceConstructorArgs) {
|
48
|
+
[forceConstructorArgsType, forceConstructorArgs] =
|
49
|
+
config.forceConstructorArgs.split(":");
|
50
|
+
enforce(
|
51
|
+
forceConstructorArgsType === "string",
|
52
|
+
"Force constructor args must be string type",
|
53
|
+
logger
|
54
|
+
);
|
55
|
+
logger.debug(`Force custructor args provided: 0x${forceConstructorArgs}`);
|
56
|
+
}
|
57
|
+
|
58
|
+
return {
|
59
|
+
workingDir,
|
60
|
+
contractsBuildDir,
|
61
|
+
contractsDir,
|
62
|
+
forceConstructorArgs,
|
63
|
+
};
|
64
|
+
};
|
65
|
+
|
66
|
+
const getArtifact = (contractName, options) => {
|
67
|
+
const artifactPath = path.resolve(
|
68
|
+
options.contractsBuildDir,
|
69
|
+
`${contractName}.json`
|
70
|
+
);
|
71
|
+
logger.debug(`Reading artifact file at ${artifactPath}`);
|
72
|
+
enforceOrThrow(
|
73
|
+
fs.existsSync(artifactPath),
|
74
|
+
`Could not find ${contractName} artifact at ${artifactPath}`
|
75
|
+
);
|
76
|
+
// Stringify + parse to make a deep copy (to avoid bugs with PR #19)
|
77
|
+
return JSON.parse(JSON.stringify(require(artifactPath)));
|
78
|
+
};
|
79
|
+
|
80
|
+
const getInputJSON = (artifact, options) => {
|
81
|
+
const metadata = JSON.parse(artifact.metadata);
|
82
|
+
// const libraries = getLibraries(artifact, options)
|
83
|
+
|
84
|
+
const sources = {};
|
85
|
+
for (const contractPath in metadata.sources) {
|
86
|
+
// If we're on Windows we need to de-Unixify the path so that Windows can read the file
|
87
|
+
// We also need to replace the 'project:' prefix so that the file can be read
|
88
|
+
const normalisedContractPath = normaliseContractPath(
|
89
|
+
contractPath,
|
90
|
+
options.contractsDir
|
91
|
+
);
|
92
|
+
const absolutePath = require.resolve(normalisedContractPath);
|
93
|
+
const content = fs.readFileSync(absolutePath, "utf8");
|
94
|
+
|
95
|
+
// Remove the 'project:' prefix that was added in Truffle v5.3.14
|
96
|
+
const relativeContractPath = contractPath;
|
97
|
+
|
98
|
+
sources[relativeContractPath] = { content };
|
99
|
+
}
|
100
|
+
|
101
|
+
const inputJSON = {
|
102
|
+
language: metadata.language,
|
103
|
+
sources,
|
104
|
+
settings: {
|
105
|
+
remappings: metadata.settings.remappings,
|
106
|
+
optimizer: metadata.settings.optimizer,
|
107
|
+
evmVersion: metadata.settings.evmVersion,
|
108
|
+
},
|
109
|
+
};
|
110
|
+
|
111
|
+
return inputJSON;
|
112
|
+
};
|
113
|
+
|
114
|
+
const tryCompileAndMatch = (artifact, jsonInput, contractName) => {
|
115
|
+
const solc = require("solc");
|
116
|
+
const i = JSON.stringify(jsonInput);
|
117
|
+
const iObj = JSON.parse(i);
|
118
|
+
iObj.settings.outputSelection = { "*": { "*": ["*", "*"] } };
|
119
|
+
const o = solc.compile(JSON.stringify(iObj));
|
120
|
+
const oObj = JSON.parse(o);
|
121
|
+
if (oObj.errors?.length > 0) {
|
122
|
+
console.warn("tryCompileAndMatch ERROR:", oObj.errors[0].message);
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
const metadataOld = artifact.metadata;
|
126
|
+
const metadataOldObj = JSON.parse(metadataOld);
|
127
|
+
|
128
|
+
const path = Object.keys(metadataOldObj.settings.compilationTarget)[0];
|
129
|
+
const metadataNew =
|
130
|
+
oObj.contracts[path][metadataOldObj.settings.compilationTarget[path]]
|
131
|
+
.metadata;
|
132
|
+
|
133
|
+
const bytecodeNew = oObj.contracts[path][
|
134
|
+
metadataOldObj.settings.compilationTarget[path]
|
135
|
+
].evm.bytecode.object.replace("0x", "");
|
136
|
+
const bytecodeOld = artifact.bytecode.replace("0x", "");
|
137
|
+
|
138
|
+
if (metadataOld === metadataNew) {
|
139
|
+
console.log("\u2713 metadata matches EXACTLY!");
|
140
|
+
} else {
|
141
|
+
console.log("ERROR: metadata does not match");
|
142
|
+
}
|
143
|
+
|
144
|
+
if (bytecodeOld === bytecodeNew) {
|
145
|
+
console.log("\u2713 bytecode matches EXACTLY!");
|
146
|
+
} else {
|
147
|
+
console.log("ERROR: bytecode does not match");
|
148
|
+
}
|
149
|
+
};
|
package/util.js
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
const path = require('path')
|
2
|
+
|
3
|
+
const abort = (message, logger = console, code = 1) => {
|
4
|
+
logger.error(message)
|
5
|
+
process.exit(code)
|
6
|
+
}
|
7
|
+
|
8
|
+
const enforce = (condition, message, logger, code) => {
|
9
|
+
if (!condition) abort(message, logger, code)
|
10
|
+
}
|
11
|
+
|
12
|
+
const enforceOrThrow = (condition, message) => {
|
13
|
+
if (!condition) throw new Error(message)
|
14
|
+
}
|
15
|
+
|
16
|
+
/**
|
17
|
+
* The metadata in the Truffle artifact file changes source paths on Windows. Instead of
|
18
|
+
* D:\Hello\World.sol, it looks like /D/Hello/World.sol. When trying to read this path,
|
19
|
+
* Windows cannot find it, since it is not a valid path. This function changes
|
20
|
+
* /D/Hello/World.sol to D:\Hello\World.sol. This way, Windows is able to read these source
|
21
|
+
* files. It does not change regular Unix paths, only Unixified Windows paths. It also does
|
22
|
+
* not make any changes on platforms that aren't Windows.
|
23
|
+
*
|
24
|
+
* @param {string} contractPath path to a contract file in any format.
|
25
|
+
* @param {any} options Options object containing the parsed truffle-plugin-verify options
|
26
|
+
* @returns {string} path to the contract in Windows format when on Windows, or Unix format otherwise.
|
27
|
+
*/
|
28
|
+
const normaliseContractPath = (contractPath, options) => {
|
29
|
+
// Replace the 'project:' prefix that was added in Truffle v5.3.14 with the actual project path
|
30
|
+
const absolutePath = getAbsolutePath(contractPath, options)
|
31
|
+
|
32
|
+
// If the current platform is not Windows, the path does not need to be changed
|
33
|
+
if (process.platform !== 'win32') return absolutePath
|
34
|
+
|
35
|
+
// If the contract path doesn't start with '/[A-Z]/' it is not a Unixified Windows path
|
36
|
+
if (!absolutePath.match(/^\/[A-Z]\//i)) return absolutePath
|
37
|
+
|
38
|
+
const driveLetter = absolutePath.substring(1, 2)
|
39
|
+
const normalisedContractPath = path.resolve(`${driveLetter}:/${absolutePath.substring(3)}`)
|
40
|
+
|
41
|
+
return normalisedContractPath
|
42
|
+
}
|
43
|
+
|
44
|
+
/**
|
45
|
+
* @param {string} contractPath path to a contract file in any format.
|
46
|
+
* @param {string} contractsDir path to the directory containing contract source files.
|
47
|
+
* @returns {string} absolute path to the contract source file
|
48
|
+
*/
|
49
|
+
const getAbsolutePath = (contractPath, contractsDir) => {
|
50
|
+
// Older versions of truffle already used the absolute path
|
51
|
+
if (!contractPath.startsWith('project:/')) return contractPath
|
52
|
+
|
53
|
+
// Figure out the project path and use it to construct the absolute path
|
54
|
+
const relativeContractPath = contractPath.replace('project:/', '')
|
55
|
+
const projectPath = findProjectPath(relativeContractPath, contractsDir)
|
56
|
+
const absolutePath = path.join(projectPath, relativeContractPath)
|
57
|
+
|
58
|
+
return absolutePath
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* @param {string} relativeContractPath path to a contract file in any format.
|
63
|
+
* @param {string} contractsPath path to the directory containing contract source files.
|
64
|
+
* @returns {string} project path
|
65
|
+
*/
|
66
|
+
const findProjectPath = (relativeContractPath, contractsDir) => {
|
67
|
+
for (let currentPath = relativeContractPath; currentPath.length > 0; currentPath = currentPath.slice(0, -1)) {
|
68
|
+
if (contractsDir.endsWith(currentPath)) {
|
69
|
+
return contractsDir.slice(0, -1 * currentPath.length)
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
module.exports = {
|
75
|
+
abort,
|
76
|
+
enforce,
|
77
|
+
enforceOrThrow,
|
78
|
+
normaliseContractPath
|
79
|
+
}
|