From eea16803d2025a26ad39988522206ef9488b7ccb Mon Sep 17 00:00:00 2001 From: "Alejandro J. Santander" Date: Tue, 5 Nov 2019 14:18:41 -0300 Subject: [PATCH] Extracted logic from apm extract-functions command --- packages/aragon-cli/npm-shrinkwrap.json | 355 +++++++++++++++++- packages/aragon-cli/package.json | 7 +- .../commands/apm_cmds/extract-functions.js | 45 +-- .../apm_cmds/util/generate-artifact.js | 2 +- .../src/helpers/solidity-extractor.js | 79 +++- .../src/lib/apm/extractContractInfoToFile.js | 10 + .../aragon-cli/test/contracts/ParseMe.sol | 27 ++ .../lib/apm/extractContractInfoToFile.test.js | 45 +++ 8 files changed, 515 insertions(+), 55 deletions(-) create mode 100644 packages/aragon-cli/src/lib/apm/extractContractInfoToFile.js create mode 100644 packages/aragon-cli/test/contracts/ParseMe.sol create mode 100644 packages/aragon-cli/test/lib/apm/extractContractInfoToFile.test.js diff --git a/packages/aragon-cli/npm-shrinkwrap.json b/packages/aragon-cli/npm-shrinkwrap.json index 7a2564fd7..6f496edaf 100644 --- a/packages/aragon-cli/npm-shrinkwrap.json +++ b/packages/aragon-cli/npm-shrinkwrap.json @@ -460,6 +460,84 @@ "listr-verbose-renderer": "^0.5.0" }, "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "requires": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "figures": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", @@ -467,6 +545,254 @@ "requires": { "escape-string-regexp": "^1.0.5" } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=" + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + } + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "requires": { + "chalk": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } } } }, @@ -8087,6 +8413,16 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -25159,6 +25495,16 @@ "chardet": "^0.4.0", "iconv-lite": "^0.4.17", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "figures": { @@ -30471,11 +30817,12 @@ } }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "rimraf": "^2.6.3" } }, "tmp-promise": { diff --git a/packages/aragon-cli/package.json b/packages/aragon-cli/package.json index 78b5d3925..fdfbac6d2 100644 --- a/packages/aragon-cli/package.json +++ b/packages/aragon-cli/package.json @@ -89,11 +89,11 @@ "devDependencies": { "@aragon/apps-agent": "^2.0.0", "@aragon/apps-finance": "^3.0.0", + "@aragon/apps-payroll": "^1.0.0", + "@aragon/apps-survey": "^1.0.0", "@aragon/apps-token-manager": "^2.0.0", "@aragon/apps-vault": "^4.0.0", "@aragon/apps-voting": "^2.0.0", - "@aragon/apps-payroll": "^1.0.0", - "@aragon/apps-survey": "^1.0.0", "@babel/cli": "^7.1.2", "@babel/core": "^7.1.2", "@babel/node": "^7.0.0", @@ -119,7 +119,8 @@ "prettier": "^1.15.3", "proxyquire": "^2.1.0", "regenerator-runtime": "^0.13.3", - "sinon": "^7.3.1" + "sinon": "^7.3.1", + "tmp": "^0.1.0" }, "engines": { "node": ">=8.0.0" diff --git a/packages/aragon-cli/src/commands/apm_cmds/extract-functions.js b/packages/aragon-cli/src/commands/apm_cmds/extract-functions.js index 316b38bf6..2aad993d7 100644 --- a/packages/aragon-cli/src/commands/apm_cmds/extract-functions.js +++ b/packages/aragon-cli/src/commands/apm_cmds/extract-functions.js @@ -1,8 +1,6 @@ const path = require('path') -const { keccak256 } = require('web3').utils -const { writeJson } = require('fs-extra') -const extract = require('../../helpers/solidity-extractor') const chalk = require('chalk') +const extractContractInfoToFile = require('../../lib/apm/extractContractInfoToFile') exports.command = 'extract-functions [contract]' @@ -22,37 +20,18 @@ exports.builder = function(yargs) { }) } -exports.handler = async function({ - cwd, - reporter, - - contract, - output, -}) { - // Analyse contract functions and returns an array - // > [{ sig: 'transfer(address)', role: 'X_ROLE', notice: 'Transfers..'}] - const functions = await extract(path.resolve(cwd, contract)) - - let roleSet = new Set() - functions.forEach(({ roles }) => roles.forEach(role => roleSet.add(role))) - const roleIds = [...roleSet] - - const roles = roleIds.map(id => ({ - id, - bytes: keccak256(id), - name: '', // Name and params can't be extracted from solidity file, must be filled in manually - params: [], - })) - - const content = { - roles, - functions, - } - - const filename = path.basename(contract).replace('.sol', '.json') +exports.handler = async function({ cwd, reporter, contract, output }) { + const contractPath = path.resolve(cwd, contract) + const filename = path.basename(contractPath).replace('.sol', '.json') const outputPath = path.resolve(output, filename) - await writeJson(outputPath, content, { spaces: '\t' }) + const progressHandler = (step) => { + switch (step) { + case 1: + reporter.success(`Saved to ${chalk.blue(outputPath)}`) + break + } + } - reporter.success(`Saved to ${chalk.blue(outputPath)}`) + await extractContractInfoToFile(contractPath, outputPath, progressHandler) } diff --git a/packages/aragon-cli/src/commands/apm_cmds/util/generate-artifact.js b/packages/aragon-cli/src/commands/apm_cmds/util/generate-artifact.js index 6d0c13dc5..da0a1cb4b 100644 --- a/packages/aragon-cli/src/commands/apm_cmds/util/generate-artifact.js +++ b/packages/aragon-cli/src/commands/apm_cmds/util/generate-artifact.js @@ -124,7 +124,7 @@ async function generateApplicationArtifact( // Analyse contract functions and returns an array // > [{ sig: 'transfer(address)', role: 'X_ROLE', notice: 'Transfers..'}] - artifact.functions = await extract(path.resolve(cwd, artifact.path)) + artifact.functions = (await extract(path.resolve(cwd, artifact.path))).functions // extract abi for each function // > [{ sig: , role: , notice: , abi: }] decorateFunctionsWithAbi(artifact.functions, artifact.abi, web3) diff --git a/packages/aragon-cli/src/helpers/solidity-extractor.js b/packages/aragon-cli/src/helpers/solidity-extractor.js index e5542e704..a0982fed0 100644 --- a/packages/aragon-cli/src/helpers/solidity-extractor.js +++ b/packages/aragon-cli/src/helpers/solidity-extractor.js @@ -1,5 +1,6 @@ const fs = require('fs') const { promisify } = require('util') +const { keccak256 } = require('web3').utils const readFile = promisify(fs.readFile) // See https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#types @@ -84,21 +85,71 @@ const getRoles = declaration => { ) } -// Takes the path to a solidity file and extracts public function signatures, -// its auth role if any and its notice statement -module.exports = async sourceCodePath => { - const sourceCode = await readFile(sourceCodePath, 'utf8') - - // everything between every 'function' and '{' and its @notice - const funcDecs = sourceCode.match(/(@notice|^\s*function)(?:[^]*?){/gm) +const extractFunctions = async (sourceCode) => { + // Everything between every 'function' and '{' and its @notice. + const functionDeclarations = sourceCode.match(/(@notice|^\s*function)(?:[^]*?){/gm) - if (!funcDecs) return [] + if (!functionDeclarations) { + return [] + } - return funcDecs - .filter(dec => modifiesStateAndIsPublic(dec)) - .map(dec => ({ - sig: getSignature(dec), - roles: getRoles(dec), - notice: getNotice(dec), + const stateModifyingFunctionDeclarations = functionDeclarations + .filter(functionDeclaration => modifiesStateAndIsPublic(functionDeclaration)) + .map(functionDeclaration => ({ + sig: getSignature(functionDeclaration), + roles: getRoles(functionDeclaration), + notice: getNotice(functionDeclaration), })) + + return stateModifyingFunctionDeclarations } + +const extractRoles = async (functionDescriptors) => { + // Extract all role ids from the function descriptors. + let roleSet = new Set() + functionDescriptors.forEach(({ roles }) => roles.forEach(role => roleSet.add(role))) + const roleIds = [...roleSet] + + // Parse role ids into objects. + // TODO: Name and parameters are currently not being extracted, and it's probably better to get it from an AST instead of the Solidity code. For now, the properties are merely place holders. + return roleIds.map(id => ({ + id, + bytes: '0x' + keccak256(id), + name: '', + params: [], + })) +} + +// Given the path to a Solidity file, parses it and returns an object with the form: +/* + roles: [ + { + id: "MINT_ROLE", + bytes: "0x0xbf05b9322505d747ab5880dfb677dc4864381e9fc3a25ccfa184a3a53d02f4b2", + name: "", + params: [] + }, + ... + ], + functions: [ + { + sig: "baz(uint32,bool)", + roles: [ "MINT_ROLE", "BURN_ROLE" ], + notice: "Sample radspec documentation..." + }, + ... + ] +*/ +const extractContractInfo = async (sourceCodePath) => { + const sourceCode = await readFile(sourceCodePath, 'utf8') + + const functionDescriptors = await extractFunctions(sourceCode) + const roleDescriptors = await extractRoles(functionDescriptors) + + return { + roles: roleDescriptors, + functions: functionDescriptors + } +} + +module.exports = extractContractInfo diff --git a/packages/aragon-cli/src/lib/apm/extractContractInfoToFile.js b/packages/aragon-cli/src/lib/apm/extractContractInfoToFile.js new file mode 100644 index 000000000..5820b6ebb --- /dev/null +++ b/packages/aragon-cli/src/lib/apm/extractContractInfoToFile.js @@ -0,0 +1,10 @@ +const extract = require('../../helpers/solidity-extractor') +const { writeJson } = require('fs-extra') + +module.exports = async (contractPath, outputPath, progressHandler) => { + const contractInfo = await extract(contractPath) + + await writeJson(outputPath, contractInfo, { spaces: '\t' }) + + progressHandler(1) +} diff --git a/packages/aragon-cli/test/contracts/ParseMe.sol b/packages/aragon-cli/test/contracts/ParseMe.sol new file mode 100644 index 000000000..0f9c64cdf --- /dev/null +++ b/packages/aragon-cli/test/contracts/ParseMe.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.5.0; + +/* + NOTE: This contract is not intended to compile and is not necessarily correct. +*/ +contract ParseMe { + + uint256 public value; + + bytes32 public constant SAMPLE_ROLE_1 = keccak256("SAMPLE_ROLE_1"); + bytes32 public constant SAMPLE_ROLE_2 = keccak256("SAMPLE_ROLE_2"); + + function multiplyByTwo(uint256 _value) public pure returns (uint256) { + return _value * 2; + } + + /** + * @notice Update value. + */ + function updateValue(uint256 _newValue) public auth(SAMPLE_ROLE_1) { + value = _newValue; + } + + function updateValueAlt(uint256 _newValue) public auth(SAMPLE_ROLE_2) { + value = multiplyByTwo(_newValue); + } +} diff --git a/packages/aragon-cli/test/lib/apm/extractContractInfoToFile.test.js b/packages/aragon-cli/test/lib/apm/extractContractInfoToFile.test.js new file mode 100644 index 000000000..be12e2467 --- /dev/null +++ b/packages/aragon-cli/test/lib/apm/extractContractInfoToFile.test.js @@ -0,0 +1,45 @@ +import test from 'ava' +const path = require('path') +const fs = require('fs') +const tmp = require('tmp') +const extractContractInfoToFile = require('../../../src/lib/apm/extractContractInfoToFile') + +let tempDir, contractPath, outputPath, recordedStep + +const readOutput = () => JSON.parse(fs.readFileSync(outputPath, 'utf8')) + +test.before('create a temp directory and resolve paths', t => { + contractPath = path.resolve('test/contracts/ParseMe.sol') + + tempDir = tmp.dirSync() + const filename = path.basename(contractPath).replace('.sol', '.json') + outputPath = path.resolve(tempDir.name, filename) +}) + +test.before('call extractContractInfoToFile function', async t => { + await extractContractInfoToFile(contractPath, outputPath, (step) => { + recordedStep = step + }) +}) + +test('generates output', async t => { + t.true(fs.existsSync(outputPath)) +}) + +test('output file contains 2 roles', async t => { + const output = readOutput() + t.is(output.roles.length, 2) +}) + +test('output file contains 2 functions', async t => { + const output = readOutput() + t.is(output.functions.length, 2) +}) + +test('progress handler was called', async t => { + t.is(recordedStep, 1) +}) + +test.after('remove temp directory', t => { + tempDir.removeCallback() +})