├── .circleci └── config.yml ├── .eslintrc.js ├── .github └── workflows │ └── scorecards.yml ├── .gitignore ├── .nycrc ├── LICENSE ├── README.md ├── abi.ts ├── bindings ├── compile.ts ├── core.ts ├── helpers.ts └── index.ts ├── build ├── clean.js ├── pack-publish-block.js └── postbuild.js ├── common ├── helpers.ts └── types.ts ├── downloadCurrentVersion.ts ├── formatters.ts ├── funding.json ├── index.ts ├── linker.ts ├── package.json ├── smtchecker.ts ├── smtsolver.ts ├── solc.ts ├── test ├── abi.ts ├── cli.ts ├── compiler.ts ├── index.ts ├── linker.ts ├── resources │ ├── fixtureAsmJson.json │ ├── fixtureAsmJson.output │ ├── fixtureAsmJson.sol │ ├── fixtureIncorrectSource.sol │ ├── fixtureSmoke.sol │ ├── importA.sol │ ├── importB.sol │ ├── importCallback │ │ ├── base │ │ │ ├── contractA.sol │ │ │ └── contractB.sol │ │ ├── contractC.sol │ │ ├── includeA │ │ │ ├── libX.sol │ │ │ ├── libY.sol │ │ │ └── utils.sol │ │ └── includeB │ │ │ └── libZ.sol │ └── smtChecker │ │ ├── loop.sol │ │ ├── smoke.sol │ │ ├── smoke_with_engine.sol │ │ └── smoke_with_multi_engine.sol ├── smtcallback.ts ├── smtchecker.ts └── translate.ts ├── translate.ts ├── tsconfig.json ├── verifyVersion.ts └── wrapper.ts /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: 2 | - requires_package: &requires_package 3 | requires: 4 | - build-package 5 | 6 | workflows: 7 | version: 2.1 8 | pr-checks: 9 | jobs: 10 | - check-coding-style 11 | - node-v12 12 | - node-v14 13 | - node-v16 14 | - node-v18 15 | - node-v20 16 | - node-v22 17 | - node-current: 18 | run_coveralls: true 19 | - build-package 20 | - hardhat-sample-project: *requires_package 21 | - cli-smoke-test: *requires_package 22 | - solidity-solcjs-ext-test 23 | 24 | nightly: 25 | triggers: 26 | - schedule: 27 | cron: "0 0 * * *" 28 | filters: 29 | branches: 30 | only: 31 | - master 32 | 33 | jobs: 34 | - node-current 35 | - build-package 36 | - hardhat-sample-project: *requires_package 37 | - cli-smoke-test: *requires_package 38 | - solidity-solcjs-ext-test 39 | 40 | version: 2.1 41 | 42 | orbs: 43 | shellcheck: circleci/shellcheck@volatile 44 | 45 | commands: 46 | show-npm-version: 47 | steps: 48 | - run: 49 | name: Versions 50 | command: | 51 | npm version 52 | yarn --version 53 | pnpm version || >&2 echo "pnpm not installed" 54 | 55 | install-dependencies: 56 | parameters: 57 | cache-id: 58 | type: string 59 | path: 60 | type: string 61 | default: . 62 | package-manager: 63 | type: string 64 | default: npm 65 | dependency-file: 66 | type: string 67 | default: package.json 68 | steps: 69 | - restore_cache: 70 | name: "Restoring <> cache" 71 | key: <>-dependency-cache-v5-{{ .Environment.CIRCLE_JOB }}-{{ checksum "<>/<>" }} 72 | - run: 73 | name: "<> install in <>" 74 | command: | 75 | cd "<>" 76 | if [[ ! -e node_modules/ ]]; then 77 | if [[ "<>" == "pnpm" ]]; then 78 | # Create a flat node_modules without symlinks. Same as the node_modules created by npm or Yarn. 79 | <> install --no-frozen-lockfile --node-linker=hoisted 80 | else 81 | <> install 82 | fi 83 | fi 84 | - save_cache: 85 | name: "Saving <> cache" 86 | key: <>-dependency-cache-v5-{{ .Environment.CIRCLE_JOB }}-{{ checksum "<>/<>" }} 87 | paths: 88 | - "<>/node_modules/" 89 | 90 | inject-solc-js-tarball: 91 | description: "Recursively finds and replaces all instances of solc-js module installed in node_modules/ with the one from a tarball." 92 | parameters: 93 | path: 94 | type: string 95 | default: . 96 | tarball-path: 97 | type: string 98 | default: workspace/solc-js.tgz 99 | package-manager: 100 | type: enum 101 | enum: ["npm", "yarn", "pnpm"] 102 | default: npm 103 | steps: 104 | - run: 105 | name: "Sanity check: tarball exists and the target dir contains a JS project" 106 | command: | 107 | [[ -f "<>" ]] 108 | [[ -f "<>/package.json" ]] 109 | - run: 110 | name: Inject solc-js from the tarball into dependencies at <> 111 | command: | 112 | absolute_tarball_path=$(realpath "<>") 113 | cd "<>" 114 | mv package.json original-package.json 115 | # NOTE: The 'overrides' feature requires npm >= 8.3. Yarn requires `resolutions` instead. 116 | jq ". + {overrides: {solc: \"${absolute_tarball_path}\"}} + {resolutions: {solc: \"${absolute_tarball_path}\"}}" original-package.json > package.json 117 | if [[ "<>" == "pnpm" ]]; then 118 | <> install --no-frozen-lockfile --node-linker=hoisted 119 | else 120 | <> install 121 | fi 122 | - run: 123 | name: "Sanity check: all transitive dependencies successfully replaced with the tarball" 124 | command: | 125 | solc_version=$( 126 | tar --extract --to-stdout --file "<>" package/package.json | 127 | jq --raw-output .version 128 | ) 129 | cd "<>" 130 | if [[ "<>" == "pnpm" ]]; then 131 | dependency_version=$(pnpm list --depth Infinity solc | grep "solc" | grep -v "solc ${solc_version}" || true) 132 | else 133 | dependency_version=$(<> list solc | grep "solc@" | grep -v "solc@${solc_version}" || true) 134 | fi 135 | if [[ -n "${dependency_version}" ]]; then 136 | echo "Another version of solc-js is still present in the dependency tree." 137 | exit 1 138 | fi 139 | 140 | fetch-latest-hardhat-release-tag: 141 | description: "Uses GitHub API to fetch the latest hardhat release version." 142 | steps: 143 | - run: 144 | name: Retrieve Hardhat latest release tag 145 | command: | 146 | # Make authenticated requests when the Github token is available 147 | if [[ -n "$GITHUB_ACCESS_TOKEN" ]]; then 148 | EXTRA_HEADERS=(--header "Authorization: Bearer ${GITHUB_ACCESS_TOKEN}") 149 | fi 150 | HARDHAT_LATEST_RELEASE_TAG=$( 151 | curl \ 152 | --silent \ 153 | --location \ 154 | --fail \ 155 | --show-error \ 156 | "${EXTRA_HEADERS[@]}" \ 157 | https://api.github.com/repos/nomiclabs/hardhat/releases \ 158 | | jq --raw-output 'map(select(.prerelease == false and (.tag_name | test("^hardhat@")))) | .[0].tag_name' \ 159 | ) 160 | echo "export HARDHAT_LATEST_RELEASE_TAG='${HARDHAT_LATEST_RELEASE_TAG}'" >> "$BASH_ENV" 161 | 162 | jobs: 163 | node-base: &node-base 164 | working_directory: ~/solc-js 165 | docker: 166 | - image: cimg/node:current 167 | parameters: 168 | run_coveralls: 169 | type: boolean 170 | default: false 171 | steps: 172 | # We want the default npm here. Older one might not work with older node.js 173 | - show-npm-version 174 | - checkout 175 | - install-dependencies: 176 | cache-id: solc-js 177 | - run: 178 | name: updateBinary 179 | command: npm run updateBinary 180 | - run: 181 | name: test 182 | command: npm run test 183 | - when: 184 | condition: <> 185 | steps: 186 | - run: 187 | name: coveralls 188 | command: npm run coveralls 189 | 190 | check-coding-style: 191 | docker: 192 | - image: cimg/node:current 193 | steps: 194 | - show-npm-version 195 | - checkout 196 | - shellcheck/install 197 | - install-dependencies: 198 | cache-id: solc-js 199 | - run: 200 | name: Check for javascript/typescript coding style 201 | command: npm run lint 202 | - shellcheck/check: 203 | ignore-dirs: | 204 | ./.git 205 | ./node_modules 206 | ./dist 207 | 208 | build-package: 209 | docker: 210 | - image: cimg/node:current 211 | steps: 212 | - show-npm-version 213 | - checkout: 214 | path: solc-js/ 215 | - install-dependencies: 216 | cache-id: solc-js 217 | path: solc-js 218 | - attach_workspace: 219 | at: workspace 220 | - run: 221 | name: Package solc-js 222 | command: | 223 | cd solc-js/ 224 | npm run build:tarball 225 | 226 | cp "$(npm run --silent tarballName)" ../workspace/solc-js.tgz 227 | 228 | # The artifact is meant to be used with `npm publish` and that fails unless the file name includes a version number. 229 | # Oddly, the name and version from the file name do not even need to be correct - after npm accepts the file, 230 | # it will use the ones from package.json anyway. 231 | mkdir ../artifacts 232 | mv "$(npm run --silent tarballName)" ../artifacts/ 233 | - persist_to_workspace: 234 | root: workspace 235 | paths: 236 | - solc-js.tgz 237 | - store_artifacts: 238 | path: artifacts/ 239 | 240 | hardhat-sample-project: 241 | docker: 242 | - image: cimg/node:current 243 | steps: 244 | - show-npm-version 245 | - attach_workspace: 246 | at: workspace 247 | - run: git clone --depth 1 "https://github.com/nomiclabs/hardhat-hackathon-boilerplate" boilerplate/ 248 | - run: 249 | # Leaving package-lock.json causes a weird error in arborist when npm is used again after 250 | # `npm install`: 'The "from" argument must be of type string. Received undefined' 251 | name: Neutralize package-lock.json 252 | command: rm boilerplate/package-lock.json 253 | - install-dependencies: 254 | cache-id: hardhat-hackathon-boilerplate 255 | path: boilerplate 256 | - fetch-latest-hardhat-release-tag 257 | - run: 258 | name: Update to the latest Hardhat release 259 | command: | 260 | # We can just use a release here because injection does not require rebuilding it. 261 | cd boilerplate/ 262 | # Install the latest release of Hardhat if the version matches the expected format. 263 | [[ "${HARDHAT_LATEST_RELEASE_TAG}" =~ ^hardhat@([0-9]+\.){2}[0-9]+$ ]] && npm install ${HARDHAT_LATEST_RELEASE_TAG} 264 | 265 | - inject-solc-js-tarball: 266 | path: boilerplate/ 267 | - run: 268 | name: Configure the boilerplate project to force Hardhat not to use a native binary 269 | command: | 270 | cd boilerplate/ 271 | 272 | solc_version=$(jq --raw-output .version node_modules/solc/package.json) 273 | 274 | sed -i 's|pragma solidity [^;]\+;|pragma solidity *;|g' contracts/Token.sol 275 | 276 | { 277 | echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');" 278 | echo "const assert = require('assert');" 279 | echo 280 | echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {" 281 | echo " assert(args.solcVersion == '${solc_version}', 'Unexpected solc version: ' + args.solcVersion);" 282 | echo " return {" 283 | echo " compilerPath: '$(realpath "node_modules/solc/soljson.js")'," 284 | echo " isSolcJs: true," 285 | echo " version: args.solcVersion," 286 | echo " longVersion: args.solcVersion" 287 | echo " };" 288 | echo "})" 289 | echo "module.exports = {solidity: '${solc_version}'};" 290 | } >> hardhat.config.js 291 | - run: 292 | name: Build and test the boilerplate project with local Hardhat 293 | command: | 294 | cd boilerplate/ 295 | npm run test 296 | 297 | cli-smoke-test: 298 | docker: 299 | - image: cimg/node:current 300 | steps: 301 | - show-npm-version 302 | - attach_workspace: 303 | at: workspace 304 | - run: 305 | name: "CLI smoke test (package)" 306 | command: | 307 | mkdir package/ 308 | cd package/ 309 | npm install ../workspace/solc-js.tgz 310 | 311 | npx solcjs --version 312 | 313 | echo "contract C {}" > C.sol 314 | npx solcjs C.sol --bin 315 | [[ -f C_sol_C.bin ]] 316 | - checkout: 317 | path: solc-js/ 318 | - install-dependencies: 319 | cache-id: solc-js 320 | path: solc-js 321 | - run: 322 | name: Build solc-js 323 | command: | 324 | cd solc-js/ 325 | npm run updateBinary 326 | npm run build 327 | - run: 328 | name: "CLI smoke test (repository)" 329 | command: | 330 | cd solc-js 331 | dist/solc.js --version 332 | 333 | echo "contract C {}" > C.sol 334 | dist/solc.js C.sol --bin 335 | [[ -f C_sol_C.bin ]] 336 | 337 | solidity-solcjs-ext-test: 338 | docker: 339 | - image: cimg/node:current 340 | steps: 341 | - show-npm-version 342 | - checkout: 343 | path: solc-js/ 344 | - run: git clone --depth 1 "https://github.com/ethereum/solidity" solidity/ 345 | - run: cd solidity/ && curl "https://binaries.soliditylang.org/bin/soljson-nightly.js" --location --output soljson.js 346 | - run: cd solidity/ && test/externalTests/solc-js/solc-js.sh "$(realpath soljson.js)" "$(scripts/get_version.sh)" "$(realpath ../solc-js/)" 347 | 348 | node-v12: 349 | <<: *node-base 350 | docker: 351 | - image: cimg/node:12.22 352 | node-v14: 353 | <<: *node-base 354 | docker: 355 | - image: cimg/node:14.21 356 | node-v16: 357 | <<: *node-base 358 | docker: 359 | - image: cimg/node:16.20 360 | node-v18: 361 | <<: *node-base 362 | docker: 363 | - image: cimg/node:18.18 364 | node-v20: 365 | <<: *node-base 366 | docker: 367 | - image: cimg/node:20.9 368 | node-v22: 369 | <<: *node-base 370 | docker: 371 | - image: cimg/node:22.14 372 | node-current: 373 | <<: *node-base 374 | docker: 375 | - image: cimg/node:current 376 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: [ 8 | 'standard' 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: [ 12 | '@typescript-eslint' 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 12, 16 | sourceType: 'module' 17 | }, 18 | rules: { 19 | semi: ['error', 'always'] 20 | }, 21 | ignorePatterns: ['dist', 'soljson.js'] 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | name: Scorecards supply-chain security 2 | on: 3 | # Only the default branch is supported. 4 | branch_protection_rule: 5 | schedule: 6 | - cron: '15 4 * * 4' 7 | push: 8 | branches: [ "master" ] 9 | 10 | # Declare default permissions as read only. 11 | permissions: read-all 12 | 13 | jobs: 14 | analysis: 15 | name: Scorecards analysis 16 | runs-on: ubuntu-latest 17 | permissions: 18 | # Needed to upload the results to code-scanning dashboard. 19 | security-events: write 20 | # Used to receive a badge. 21 | # id-token: write 22 | 23 | steps: 24 | - name: "Checkout code" 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 26 | with: 27 | persist-credentials: false 28 | 29 | - name: "Run analysis" 30 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # tag=v2.4.1 31 | with: 32 | results_file: results.sarif 33 | results_format: sarif 34 | # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: 35 | # - you want to enable the Branch-Protection check on a *public* repository, or 36 | # - you are installing Scorecards on a *private* repository 37 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 38 | # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} 39 | 40 | # Publish the results for public repositories to enable scorecard badges. For more details, see 41 | # https://github.com/ossf/scorecard-action#publishing-results. 42 | # For private repositories, `publish_results` will automatically be set to `false`, regardless 43 | # of the value entered here. 44 | # publish_results: true 45 | 46 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 47 | # format to the repository Actions tab. 48 | - name: "Upload artifact" 49 | uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # tag=v4.6.1 50 | with: 51 | name: SARIF file 52 | path: results.sarif 53 | retention-days: 5 54 | 55 | # Upload the results to GitHub's code scanning dashboard. 56 | - name: "Upload to code-scanning" 57 | uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # tag=v3.28.11 58 | with: 59 | sarif_file: results.sarif 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul/nyc 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # solc-js 30 | soljson.js 31 | test_DAO_Token_sol_Token.abi 32 | test_DAO_Token_sol_Token.bin 33 | test_DAO_Token_sol_TokenInterface.abi 34 | test_DAO_Token_sol_TokenInterface.bin 35 | test_DAO_Token_sol_tokenRecipient.abi 36 | test_DAO_Token_sol_tokenRecipient.bin 37 | 38 | bin 39 | out 40 | *.bin 41 | *.abi 42 | 43 | dist/** 44 | 45 | .nyc_output 46 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "coverage", 4 | "dist/soljson.js", 5 | "**/test/**" 6 | ], 7 | "extensions": [ 8 | ".js" 9 | ], 10 | "report-dir": "./coverage", 11 | "reporter": [ 12 | "lcov", 13 | "html", 14 | "text-summary" 15 | ], 16 | "temp-directory": "./coverage/.nyc_output" 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://img.shields.io/circleci/project/github/ethereum/solc-js/master.svg?style=flat-square)](https://circleci.com/gh/ethereum/solc-js/tree/master) 2 | [![Coverage Status](https://img.shields.io/coveralls/ethereum/solc-js.svg?style=flat-square)](https://coveralls.io/r/ethereum/solc-js) 3 | 4 | # solc-js 5 | 6 | JavaScript bindings for the [Solidity compiler](https://github.com/ethereum/solidity). 7 | 8 | Uses the Emscripten compiled Solidity found in the [solc-bin repository](https://github.com/ethereum/solc-bin). 9 | 10 | ## Node.js Usage 11 | 12 | To use the latest stable version of the Solidity compiler via Node.js you can install it via npm: 13 | 14 | ```bash 15 | npm install solc 16 | ``` 17 | 18 | ### Usage on the Command-Line 19 | 20 | If this package is installed globally (`npm install -g solc`), a command-line tool called `solcjs` will be available. 21 | 22 | To see all the supported features, execute: 23 | 24 | ```bash 25 | solcjs --help 26 | ``` 27 | 28 | To compile a contract that imports other contracts via relative paths: 29 | ```bash 30 | solcjs --bin --include-path node_modules/ --base-path . MainContract.sol 31 | ``` 32 | Use the ``--base-path`` and ``--include-path`` options to describe the layout of your project. 33 | ``--base-path`` represents the root of your own source tree while ``--include-path`` allows you to 34 | specify extra locations containing external code (e.g. libraries installed with a package manager). 35 | 36 | Note: ensure that all the files you specify on the command line are located inside the base path or 37 | one of the include paths. 38 | The compiler refers to files from outside of these directories using absolute paths. 39 | Having absolute paths in contract metadata will result in your bytecode being reproducible only 40 | when it's placed in these exact absolute locations. 41 | 42 | Note: this commandline interface is not compatible with `solc` provided by the Solidity compiler package and thus cannot be 43 | used in combination with an Ethereum client via the `eth.compile.solidity()` RPC method. Please refer to the 44 | [Solidity compiler documentation](https://solidity.readthedocs.io/) for instructions to install `solc`. 45 | Furthermore, the commandline interface to solc-js provides fewer features than the binary release. 46 | 47 | ### Usage in Projects 48 | 49 | There are two ways to use `solc`: 50 | 51 | 1. Through a high-level API giving a uniform interface to all compiler versions 52 | 2. Through a low-level API giving access to all the compiler interfaces, which depend on the version of the compiler 53 | 54 | #### High-level API 55 | 56 | The high-level API consists of a single method, `compile`, which expects the [Compiler Standard Input and Output JSON](https://solidity.readthedocs.io/en/v0.5.0/using-the-compiler.html#compiler-input-and-output-json-description). 57 | 58 | It also accepts an optional set of callback functions, which include the ``import`` and the ``smtSolver`` callbacks. 59 | Starting 0.6.0 it only accepts an object in place of the callback to supply the callbacks. 60 | 61 | The ``import`` callback function is used to resolve unmet dependencies. 62 | This callback receives a path and must synchronously return either an error or the content of the dependency 63 | as a string. It cannot be used together with callback-based, asynchronous, 64 | filesystem access. A workaround is to collect the names of dependencies, return 65 | an error, and keep re-running the compiler until all of them are resolved. 66 | 67 | #### Example usage without the import callback 68 | 69 | Example: 70 | 71 | ```javascript 72 | var solc = require('solc'); 73 | 74 | var input = { 75 | language: 'Solidity', 76 | sources: { 77 | 'test.sol': { 78 | content: 'contract C { function f() public { } }' 79 | } 80 | }, 81 | settings: { 82 | outputSelection: { 83 | '*': { 84 | '*': ['*'] 85 | } 86 | } 87 | } 88 | }; 89 | 90 | var output = JSON.parse(solc.compile(JSON.stringify(input))); 91 | 92 | // `output` here contains the JSON output as specified in the documentation 93 | for (var contractName in output.contracts['test.sol']) { 94 | console.log( 95 | contractName + 96 | ': ' + 97 | output.contracts['test.sol'][contractName].evm.bytecode.object 98 | ); 99 | } 100 | ``` 101 | 102 | #### Example usage with import callback 103 | 104 | ```javascript 105 | var solc = require('solc'); 106 | 107 | var input = { 108 | language: 'Solidity', 109 | sources: { 110 | 'test.sol': { 111 | content: 'import "lib.sol"; contract C { function f() public { L.f(); } }' 112 | } 113 | }, 114 | settings: { 115 | outputSelection: { 116 | '*': { 117 | '*': ['*'] 118 | } 119 | } 120 | } 121 | }; 122 | 123 | function findImports(path) { 124 | if (path === 'lib.sol') 125 | return { 126 | contents: 127 | 'library L { function f() internal returns (uint) { return 7; } }' 128 | }; 129 | else return { error: 'File not found' }; 130 | } 131 | 132 | // New syntax (supported from 0.5.12, mandatory from 0.6.0) 133 | var output = JSON.parse( 134 | solc.compile(JSON.stringify(input), { import: findImports }) 135 | ); 136 | 137 | // `output` here contains the JSON output as specified in the documentation 138 | for (var contractName in output.contracts['test.sol']) { 139 | console.log( 140 | contractName + 141 | ': ' + 142 | output.contracts['test.sol'][contractName].evm.bytecode.object 143 | ); 144 | } 145 | ``` 146 | 147 | Since version 0.5.1, the ``smtSolver`` callback function is used to solve SMT queries generated by 148 | Solidity's SMTChecker. If you have an SMT solver installed locally, it can 149 | be used to solve the given queries, where the callback must synchronously 150 | return either an error or the result from the solver. A default 151 | ``smtSolver`` callback is included in this package via the module 152 | ``smtchecker.js`` which exports the ``smtCallback`` function that takes 1) a 153 | function that takes queries and returns the solving result, and 2) a solver 154 | configuration object. The module ``smtsolver.js`` has a few predefined solver 155 | configurations, and relies on Z3, Eldarica or cvc5 being installed locally. It 156 | exports the list of locally found solvers and a function that invokes a given 157 | solver. 158 | 159 | The API of the SMT callback is **experimental** and can change at any time. 160 | The last change was in version 0.8.11. 161 | 162 | #### Example usage with smtSolver callback 163 | 164 | ```javascript 165 | var solc = require('solc'); 166 | const smtchecker = require('solc/smtchecker'); 167 | const smtsolver = require('solc/smtsolver'); 168 | // Note that this example only works via node and not in the browser. 169 | 170 | var input = { 171 | language: 'Solidity', 172 | sources: { 173 | 'test.sol': { 174 | content: 'contract C { function f(uint x) public { assert(x > 0); } }' 175 | } 176 | }, 177 | settings: { 178 | modelChecker: { 179 | engine: "chc", 180 | solvers: [ "smtlib2" ] 181 | } 182 | } 183 | }; 184 | 185 | var output = JSON.parse( 186 | solc.compile( 187 | JSON.stringify(input), 188 | { smtSolver: smtchecker.smtCallback(smtsolver.smtSolver, smtsolver.availableSolvers[0]) } 189 | ) 190 | ); 191 | 192 | ``` 193 | The assertion is clearly false, and an ``assertion failure`` warning 194 | should be returned, together with a counterexample. 195 | 196 | #### Low-level API 197 | 198 | The low-level API is as follows: 199 | 200 | - `solc.lowlevel.compileSingle`: the original entry point, supports only a single file 201 | - `solc.lowlevel.compileMulti`: this supports multiple files, introduced in 0.1.6 202 | - `solc.lowlevel.compileCallback`: this supports callbacks, introduced in 0.2.1 203 | - `solc.lowlevel.compileStandard`: this works just like `compile` above, but is only present in compilers after (and including) 0.4.11 204 | 205 | For examples how to use them, please refer to the README of the above mentioned solc-js releases. 206 | 207 | **Note**: These low-level functions remain available for compatibility reasons. 208 | However, they were superseded by the `compile()` function and are no longer required. 209 | Starting from version `0.5.0+commit.1d4f565a`, the functions `compileSingle`, `compileMulti`, and `compileCallback` are always `null` when using newer solc binary versions. 210 | It is recommended to use the latest release of solc-js, but it should also handle all the older solc binaries down to `0.1.x`. 211 | 212 | ### Using with Electron 213 | 214 | **Note:** 215 | If you are using Electron, `nodeIntegration` is on for `BrowserWindow` by default. If it is on, Electron will provide a `require` method which will not behave as expected and this may cause calls, such as `require('solc')`, to fail. 216 | 217 | To turn off `nodeIntegration`, use the following: 218 | 219 | ```javascript 220 | new BrowserWindow({ 221 | webPreferences: { 222 | nodeIntegration: false 223 | } 224 | }); 225 | ``` 226 | 227 | ### Using a Legacy Version 228 | 229 | In order to compile contracts using a specific version of Solidity, the `solc.loadRemoteVersion(version, callback)` method is available. This returns a new `solc` object that uses a version of the compiler specified. 230 | 231 | You can also load the "binary" manually and use `setupMethods` to create the familiar wrapper functions described above: 232 | `var solc = solc.setupMethods(require("/my/local/soljson.js"))`. 233 | 234 | ### Using the Latest Development Snapshot 235 | 236 | By default, the npm version is only created for releases. This prevents people from deploying contracts with non-release versions because they are less stable and harder to verify. If you would like to use the latest development snapshot (at your own risk!), you may use the following example code. 237 | 238 | ```javascript 239 | var solc = require('solc'); 240 | 241 | // getting the development snapshot 242 | solc.loadRemoteVersion('latest', function(err, solcSnapshot) { 243 | if (err) { 244 | // An error was encountered, display and quit 245 | } else { 246 | // NOTE: Use `solcSnapshot` here with the same interface `solc` has 247 | // For example: 248 | const output = solcSnapshot.compile(/* ... */) 249 | } 250 | }); 251 | ``` 252 | 253 | The version **must** be in the long format. 254 | Thus, if you would like to use version `v0.8.17` you need to include the commit hash of the release. 255 | You can extract the long version string for each version from the [publicly available release list](https://binaries.soliditylang.org/bin/list.json). 256 | 257 | ```javascript 258 | solc.loadRemoteVersion('v0.8.17+commit.8df45f5f', function(err, solcSnapshot) { /* ... */ }); 259 | ``` 260 | 261 | ### Linking Bytecode 262 | 263 | When using libraries, the resulting bytecode will contain placeholders for the real addresses of the referenced libraries. These have to be updated, via a process called linking, before deploying the contract. 264 | 265 | The `linker` module (`require('solc/linker')`) offers helpers to accomplish this. 266 | 267 | The `linkBytecode` method provides a simple helper for linking: 268 | 269 | ```javascript 270 | var linker = require('solc/linker'); 271 | 272 | bytecode = linker.linkBytecode(bytecode, { MyLibrary: '0x123456...' }); 273 | ``` 274 | 275 | As of Solidity 0.4.11 the compiler supports [standard JSON input and output](https://solidity.readthedocs.io/en/develop/using-the-compiler.html#compiler-input-and-output-json-description) which outputs a _link references_ map. This gives a map of library names to offsets in the bytecode to replace the addresses at. It also doesn't have the limitation on library file and contract name lengths. 276 | 277 | There is a method available in the `linker` module called `findLinkReferences` which can find such link references in bytecode produced by an older compiler: 278 | 279 | ```javascript 280 | var linker = require('solc/linker'); 281 | 282 | var linkReferences = linker.findLinkReferences(bytecode); 283 | ``` 284 | 285 | ### Updating the ABI 286 | 287 | The ABI generated by Solidity versions can differ slightly, due to new features introduced. There is a tool included which aims to translate the ABI generated by an older Solidity version to conform to the latest standard. 288 | 289 | It can be used as: 290 | 291 | ```javascript 292 | var abi = require('solc/abi'); 293 | 294 | var inputABI = [ 295 | { 296 | constant: false, 297 | inputs: [], 298 | name: 'hello', 299 | outputs: [{ name: '', type: 'string' }], 300 | payable: false, 301 | type: 'function' 302 | } 303 | ]; 304 | var outputABI = abi.update('0.3.6', inputABI); 305 | // Output contains: [{"constant":false,"inputs":[],"name":"hello","outputs":[{"name":"","type":"string"}],"payable":true,"type":"function"},{"type":"fallback","payable":true}] 306 | ``` 307 | 308 | ### Formatting old JSON assembly output 309 | 310 | There is a helper available to format old JSON assembly output into a text familiar to earlier users of Remix IDE. 311 | 312 | ``` 313 | var translate = require('solc/translate') 314 | 315 | // assemblyJSON refers to the JSON of the given assembly and sourceCode is the source of which the assembly was generated from 316 | var output = translate.prettyPrintLegacyAssemblyJSON(assemblyJSON, sourceCode) 317 | ``` 318 | 319 | ## Browser Usage 320 | 321 | Compilation is generally a long-running and resource intensive task that cannot reasonably be performed in the main thread of the browser. 322 | Some browsers even disallow synchronous compilation on the main thread if the module is larger than 4KB. 323 | Thus, the only supported way to use `solc` in a web browser is through a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). 324 | 325 | ### Loading solc with web workers 326 | 327 | Web Workers allow you to run javascript in the background in the browser, letting the browser's main thread free to do whatever it needs to do. 328 | Please, see the minimal example of how to use `solc` with web workers below or check out this [repository](https://github.com/r0qs/solcjs-webworker-example) for a full demo. 329 | 330 | * index.html 331 | ```html 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 348 | 349 | 350 | 351 | ``` 352 | 353 | * worker.js: 354 | ```javascript 355 | importScripts('https://binaries.soliditylang.org/bin/soljson-v0.8.19+commit.7dd6d404.js') 356 | import wrapper from 'solc/wrapper'; 357 | 358 | self.addEventListener('message', () => { 359 | const compiler = wrapper(self.Module) 360 | self.postMessage({ 361 | version: compiler.version() 362 | }) 363 | }, false) 364 | ``` 365 | -------------------------------------------------------------------------------- /abi.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver'; 2 | 3 | function update (compilerVersion, abi) { 4 | let hasConstructor = false; 5 | let hasFallback = false; 6 | 7 | for (let i = 0; i < abi.length; i++) { 8 | const item = abi[i]; 9 | 10 | if (item.type === 'constructor') { 11 | hasConstructor = true; 12 | 13 | // <0.4.5 assumed every constructor to be payable 14 | if (semver.lt(compilerVersion, '0.4.5')) { 15 | item.payable = true; 16 | } 17 | } else if (item.type === 'fallback') { 18 | hasFallback = true; 19 | } 20 | 21 | if (item.type !== 'event') { 22 | // add 'payable' to everything, except constant functions 23 | if (!item.constant && semver.lt(compilerVersion, '0.4.0')) { 24 | item.payable = true; 25 | } 26 | 27 | // add stateMutability field 28 | if (semver.lt(compilerVersion, '0.4.16')) { 29 | if (item.payable) { 30 | item.stateMutability = 'payable'; 31 | } else if (item.constant) { 32 | item.stateMutability = 'view'; 33 | } else { 34 | item.stateMutability = 'nonpayable'; 35 | } 36 | } 37 | } 38 | } 39 | 40 | // 0.1.2 from Aug 2015 had it. The code has it since May 2015 (e7931ade) 41 | if (!hasConstructor && semver.lt(compilerVersion, '0.1.2')) { 42 | abi.push({ 43 | type: 'constructor', 44 | payable: true, 45 | stateMutability: 'payable', 46 | inputs: [] 47 | }); 48 | } 49 | 50 | if (!hasFallback && semver.lt(compilerVersion, '0.4.0')) { 51 | abi.push({ 52 | type: 'fallback', 53 | payable: true, 54 | stateMutability: 'payable' 55 | }); 56 | } 57 | 58 | return abi; 59 | } 60 | 61 | export = { 62 | update 63 | }; 64 | -------------------------------------------------------------------------------- /bindings/compile.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { isNil } from '../common/helpers'; 4 | import { bindSolcMethod } from './helpers'; 5 | 6 | export function setupCompile (solJson, core) { 7 | return { 8 | compileJson: bindCompileJson(solJson), 9 | compileJsonCallback: bindCompileJsonCallback(solJson, core), 10 | compileJsonMulti: bindCompileJsonMulti(solJson), 11 | compileStandard: bindCompileStandard(solJson, core) 12 | }; 13 | } 14 | 15 | /********************** 16 | * COMPILE 17 | **********************/ 18 | 19 | /** 20 | * Returns a binding to the solidity compileJSON method. 21 | * input (text), optimize (bool) -> output (jsontext) 22 | * 23 | * @param solJson The Emscripten compiled Solidity object. 24 | */ 25 | function bindCompileJson (solJson) { 26 | return bindSolcMethod( 27 | solJson, 28 | 'compileJSON', 29 | 'string', 30 | ['string', 'number'], 31 | null 32 | ); 33 | } 34 | 35 | /** 36 | * Returns a binding to the solidity compileJSONMulti method. 37 | * input (jsontext), optimize (bool) -> output (jsontext) 38 | * 39 | * @param solJson The Emscripten compiled Solidity object. 40 | */ 41 | function bindCompileJsonMulti (solJson) { 42 | return bindSolcMethod( 43 | solJson, 44 | 'compileJSONMulti', 45 | 'string', 46 | ['string', 'number'], 47 | null 48 | ); 49 | } 50 | 51 | /** 52 | * Returns a binding to the solidity compileJSONCallback method. 53 | * input (jsontext), optimize (bool), callback (ptr) -> output (jsontext) 54 | * 55 | * @param solJson The Emscripten compiled Solidity object. 56 | * @param coreBindings The core bound Solidity methods. 57 | */ 58 | function bindCompileJsonCallback (solJson, coreBindings) { 59 | const compileInternal = bindSolcMethod( 60 | solJson, 61 | 'compileJSONCallback', 62 | 'string', 63 | ['string', 'number', 'number'], 64 | null 65 | ); 66 | 67 | if (isNil(compileInternal)) return null; 68 | 69 | return function (input, optimize, readCallback) { 70 | return runWithCallbacks(solJson, coreBindings, readCallback, compileInternal, [input, optimize]); 71 | }; 72 | } 73 | 74 | /** 75 | * Returns a binding to the solidity solidity_compile method with a fallback to 76 | * compileStandard. 77 | * input (jsontext), callback (optional >= v6 only - ptr) -> output (jsontext) 78 | * 79 | * @param solJson The Emscripten compiled Solidity object. 80 | * @param coreBindings The core bound Solidity methods. 81 | */ 82 | function bindCompileStandard (solJson, coreBindings) { 83 | let boundFunctionStandard: any = null; 84 | let boundFunctionSolidity: any = null; 85 | 86 | // input (jsontext), callback (ptr) -> output (jsontext) 87 | const compileInternal = bindSolcMethod( 88 | solJson, 89 | 'compileStandard', 90 | 'string', 91 | ['string', 'number'], 92 | null 93 | ); 94 | 95 | if (coreBindings.isVersion6OrNewer) { 96 | // input (jsontext), callback (ptr), callback_context (ptr) -> output (jsontext) 97 | boundFunctionSolidity = bindSolcMethod( 98 | solJson, 99 | 'solidity_compile', 100 | 'string', 101 | ['string', 'number', 'number'], 102 | null 103 | ); 104 | } else { 105 | // input (jsontext), callback (ptr) -> output (jsontext) 106 | boundFunctionSolidity = bindSolcMethod( 107 | solJson, 108 | 'solidity_compile', 109 | 'string', 110 | ['string', 'number'], 111 | null 112 | ); 113 | } 114 | 115 | if (!isNil(compileInternal)) { 116 | boundFunctionStandard = function (input, readCallback) { 117 | return runWithCallbacks(solJson, coreBindings, readCallback, compileInternal, [input]); 118 | }; 119 | } 120 | 121 | if (!isNil(boundFunctionSolidity)) { 122 | boundFunctionStandard = function (input, callbacks) { 123 | return runWithCallbacks(solJson, coreBindings, callbacks, boundFunctionSolidity, [input]); 124 | }; 125 | } 126 | 127 | return boundFunctionStandard; 128 | } 129 | 130 | /********************** 131 | * CALL BACKS 132 | **********************/ 133 | 134 | function wrapCallback (coreBindings, callback) { 135 | assert(typeof callback === 'function', 'Invalid callback specified.'); 136 | 137 | return function (data, contents, error) { 138 | const result = callback(coreBindings.copyFromCString(data)); 139 | if (typeof result.contents === 'string') { 140 | coreBindings.copyToCString(result.contents, contents); 141 | } 142 | if (typeof result.error === 'string') { 143 | coreBindings.copyToCString(result.error, error); 144 | } 145 | }; 146 | } 147 | 148 | function wrapCallbackWithKind (coreBindings, callback) { 149 | assert(typeof callback === 'function', 'Invalid callback specified.'); 150 | 151 | return function (context, kind, data, contents, error) { 152 | // Must be a null pointer. 153 | assert(context === 0, 'Callback context must be null.'); 154 | const result = callback(coreBindings.copyFromCString(kind), coreBindings.copyFromCString(data)); 155 | if (typeof result.contents === 'string') { 156 | coreBindings.copyToCString(result.contents, contents); 157 | } 158 | if (typeof result.error === 'string') { 159 | coreBindings.copyToCString(result.error, error); 160 | } 161 | }; 162 | } 163 | 164 | // calls compile() with args || cb 165 | function runWithCallbacks (solJson, coreBindings, callbacks, compile, args) { 166 | if (callbacks) { 167 | assert(typeof callbacks === 'object', 'Invalid callback object specified.'); 168 | } else { 169 | callbacks = {}; 170 | } 171 | 172 | let readCallback = callbacks.import; 173 | if (readCallback === undefined) { 174 | readCallback = function (data) { 175 | return { 176 | error: 'File import callback not supported' 177 | }; 178 | }; 179 | } 180 | 181 | let singleCallback; 182 | if (coreBindings.isVersion6OrNewer) { 183 | // After 0.6.x multiple kind of callbacks are supported. 184 | let smtSolverCallback = callbacks.smtSolver; 185 | if (smtSolverCallback === undefined) { 186 | smtSolverCallback = function (data) { 187 | return { 188 | error: 'SMT solver callback not supported' 189 | }; 190 | }; 191 | } 192 | 193 | singleCallback = function (kind, data) { 194 | if (kind === 'source') { 195 | return readCallback(data); 196 | } else if (kind === 'smt-query') { 197 | return smtSolverCallback(data); 198 | } else { 199 | assert(false, 'Invalid callback kind specified.'); 200 | } 201 | }; 202 | 203 | singleCallback = wrapCallbackWithKind(coreBindings, singleCallback); 204 | } else { 205 | // Old Solidity version only supported imports. 206 | singleCallback = wrapCallback(coreBindings, readCallback); 207 | } 208 | 209 | const cb = coreBindings.addFunction(singleCallback, 'viiiii'); 210 | let output; 211 | try { 212 | args.push(cb); 213 | if (coreBindings.isVersion6OrNewer) { 214 | // Callback context. 215 | args.push(null); 216 | } 217 | 218 | output = compile(...args); 219 | } finally { 220 | coreBindings.removeFunction(cb); 221 | } 222 | 223 | if (coreBindings.reset) { 224 | // Explicitly free memory. 225 | // 226 | // NOTE: cwrap() of "compile" will copy the returned pointer into a 227 | // Javascript string and it is not possible to call free() on it. 228 | // reset() however will clear up all allocations. 229 | coreBindings.reset(); 230 | } 231 | return output; 232 | } 233 | -------------------------------------------------------------------------------- /bindings/core.ts: -------------------------------------------------------------------------------- 1 | import { bindSolcMethod, bindSolcMethodWithFallbackFunc } from './helpers'; 2 | import translate from '../translate'; 3 | import * as semver from 'semver'; 4 | import { isNil } from '../common/helpers'; 5 | 6 | export function setupCore (solJson) { 7 | const core = { 8 | alloc: bindAlloc(solJson), 9 | license: bindLicense(solJson), 10 | version: bindVersion(solJson), 11 | reset: bindReset(solJson) 12 | }; 13 | 14 | const helpers = { 15 | addFunction: unboundAddFunction.bind(this, solJson), 16 | removeFunction: unboundRemoveFunction.bind(this, solJson), 17 | 18 | copyFromCString: unboundCopyFromCString.bind(this, solJson), 19 | copyToCString: unboundCopyToCString.bind(this, solJson, core.alloc), 20 | 21 | // @ts-ignore 22 | versionToSemver: versionToSemver(core.version()) 23 | }; 24 | 25 | return { 26 | ...core, 27 | ...helpers, 28 | 29 | isVersion6OrNewer: semver.gt(helpers.versionToSemver(), '0.5.99') 30 | }; 31 | } 32 | 33 | /********************** 34 | * Core Functions 35 | **********************/ 36 | 37 | /** 38 | * Returns a binding to the solidity_alloc function. 39 | * 40 | * @param solJson The Emscripten compiled Solidity object. 41 | */ 42 | function bindAlloc (solJson) { 43 | const allocBinding = bindSolcMethod( 44 | solJson, 45 | 'solidity_alloc', 46 | 'number', 47 | ['number'], 48 | null 49 | ); 50 | 51 | // the fallback malloc is not a cwrap function and should just be returned 52 | // directly in-case the alloc binding could not happen. 53 | if (isNil(allocBinding)) { 54 | return solJson._malloc; 55 | } 56 | 57 | return allocBinding; 58 | } 59 | 60 | /** 61 | * Returns a binding to the solidity_version method. 62 | * 63 | * @param solJson The Emscripten compiled Solidity object. 64 | */ 65 | function bindVersion (solJson) { 66 | return bindSolcMethodWithFallbackFunc( 67 | solJson, 68 | 'solidity_version', 69 | 'string', 70 | [], 71 | 'version' 72 | ); 73 | } 74 | 75 | function versionToSemver (version) { 76 | return translate.versionToSemver.bind(this, version); 77 | } 78 | 79 | /** 80 | * Returns a binding to the solidity_license method. 81 | * 82 | * If the current solJson version < 0.4.14 then this will bind an empty function. 83 | * 84 | * @param solJson The Emscripten compiled Solidity object. 85 | */ 86 | function bindLicense (solJson) { 87 | return bindSolcMethodWithFallbackFunc( 88 | solJson, 89 | 'solidity_license', 90 | 'string', 91 | [], 92 | 'license', 93 | () => { 94 | } 95 | ); 96 | } 97 | 98 | /** 99 | * Returns a binding to the solidity_reset method. 100 | * 101 | * @param solJson The Emscripten compiled Solidity object. 102 | */ 103 | function bindReset (solJson) { 104 | return bindSolcMethod( 105 | solJson, 106 | 'solidity_reset', 107 | null, 108 | [], 109 | null 110 | ); 111 | } 112 | 113 | /********************** 114 | * Helpers Functions 115 | **********************/ 116 | 117 | /** 118 | * Copy to a C string. 119 | * 120 | * Allocates memory using solc's allocator. 121 | * 122 | * Before 0.6.0: 123 | * Assuming copyToCString is only used in the context of wrapCallback, solc will free these pointers. 124 | * See https://github.com/ethereum/solidity/blob/v0.5.13/libsolc/libsolc.h#L37-L40 125 | * 126 | * After 0.6.0: 127 | * The duty is on solc-js to free these pointers. We accomplish that by calling `reset` at the end. 128 | * 129 | * @param solJson The Emscripten compiled Solidity object. 130 | * @param alloc The memory allocation function. 131 | * @param str The source string being copied to a C string. 132 | * @param ptr The pointer location where the C string will be set. 133 | */ 134 | function unboundCopyToCString (solJson, alloc, str, ptr) { 135 | const length = solJson.lengthBytesUTF8(str); 136 | 137 | const buffer = alloc(length + 1); 138 | 139 | solJson.stringToUTF8(str, buffer, length + 1); 140 | solJson.setValue(ptr, buffer, '*'); 141 | } 142 | 143 | /** 144 | * Wrapper over Emscripten's C String copying function (which can be different 145 | * on different versions). 146 | * 147 | * @param solJson The Emscripten compiled Solidity object. 148 | * @param ptr The pointer location where the C string will be referenced. 149 | */ 150 | function unboundCopyFromCString (solJson, ptr) { 151 | const copyFromCString = solJson.UTF8ToString || solJson.Pointer_stringify; 152 | return copyFromCString(ptr); 153 | } 154 | 155 | function unboundAddFunction (solJson, func, signature?) { 156 | return (solJson.addFunction || solJson.Runtime.addFunction)(func, signature); 157 | } 158 | 159 | function unboundRemoveFunction (solJson, ptr) { 160 | return (solJson.removeFunction || solJson.Runtime.removeFunction)(ptr); 161 | } 162 | -------------------------------------------------------------------------------- /bindings/helpers.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from '../common/helpers'; 2 | 3 | export function bindSolcMethod (solJson, method, returnType, args, defaultValue) { 4 | if (isNil(solJson[`_${method}`]) && defaultValue !== undefined) { 5 | return defaultValue; 6 | } 7 | 8 | return solJson.cwrap(method, returnType, args); 9 | } 10 | 11 | export function bindSolcMethodWithFallbackFunc (solJson, method, returnType, args, fallbackMethod, finalFallback = undefined) { 12 | const methodFunc = bindSolcMethod(solJson, method, returnType, args, null); 13 | 14 | if (!isNil(methodFunc)) { 15 | return methodFunc; 16 | } 17 | 18 | return bindSolcMethod(solJson, fallbackMethod, returnType, args, finalFallback); 19 | } 20 | 21 | export function getSupportedMethods (solJson) { 22 | return { 23 | licenseSupported: anyMethodExists(solJson, 'solidity_license'), 24 | versionSupported: anyMethodExists(solJson, 'solidity_version'), 25 | allocSupported: anyMethodExists(solJson, 'solidity_alloc'), 26 | resetSupported: anyMethodExists(solJson, 'solidity_reset'), 27 | compileJsonSupported: anyMethodExists(solJson, 'compileJSON'), 28 | compileJsonMultiSupported: anyMethodExists(solJson, 'compileJSONMulti'), 29 | compileJsonCallbackSuppported: anyMethodExists(solJson, 'compileJSONCallback'), 30 | compileJsonStandardSupported: anyMethodExists(solJson, 'compileStandard', 'solidity_compile') 31 | }; 32 | } 33 | 34 | function anyMethodExists (solJson, ...names) { 35 | return names.some(name => !isNil(solJson[`_${name}`])); 36 | } 37 | -------------------------------------------------------------------------------- /bindings/index.ts: -------------------------------------------------------------------------------- 1 | import { setupCore } from './core'; 2 | import { getSupportedMethods } from './helpers'; 3 | import { setupCompile } from './compile'; 4 | 5 | export default function setupBindings (solJson) { 6 | const coreBindings = setupCore(solJson); 7 | const compileBindings = setupCompile(solJson, coreBindings); 8 | const methodFlags = getSupportedMethods(solJson); 9 | 10 | return { 11 | methodFlags, 12 | coreBindings, 13 | compileBindings 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /build/clean.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const distFolder = path.join(__dirname, 'dist'); 5 | 6 | if (fs.existsSync(distFolder)) { 7 | fs.rmdirSync(distFolder); 8 | } 9 | -------------------------------------------------------------------------------- /build/pack-publish-block.js: -------------------------------------------------------------------------------- 1 | // This is meant to run in a hook before npm pack. 2 | // Reporting an error from the hook interrupts the command. 3 | if (process.env.BYPASS_SAFETY_CHECK === 'false' || process.env.BYPASS_SAFETY_CHECK === undefined) { 4 | console.error('Run `npm run build:tarball` or `npm run publish:tarball` to pack or publish the package'); 5 | process.exit(1); 6 | } else if (process.env.BYPASS_SAFETY_CHECK !== 'true') { 7 | console.error('Invalid value of the BYPASS_SAFETY_CHECK variable. Must be "true", "false" or unset.'); 8 | process.exit(1); 9 | } 10 | -------------------------------------------------------------------------------- /build/postbuild.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | fs.chmodSync(path.join(__dirname, '../dist', 'solc.js'), '755'); 5 | -------------------------------------------------------------------------------- /common/helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if and only if the value is null or undefined. 3 | * 4 | * @param value 5 | */ 6 | export function isNil (value: any): boolean { 7 | // Uses == over === which compares both null and undefined. 8 | return value == null; 9 | } 10 | 11 | /** 12 | * Returns true if and only if the value is an object and not an array. 13 | * 14 | * @param value 15 | */ 16 | export function isObject (value: any): boolean { 17 | // typeof [] will result in an 'object' so this additionally uses Array.isArray 18 | // to confirm it's just an object. 19 | return typeof value === 'object' && !Array.isArray(value); 20 | } 21 | -------------------------------------------------------------------------------- /common/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A mapping between libraries and the addresses to which they were deployed. 3 | * 4 | * Containing support for two level configuration, These two level 5 | * configurations can be seen below. 6 | * 7 | * { 8 | * "lib.sol:L1": "0x...", 9 | * "lib.sol:L2": "0x...", 10 | * "lib.sol": {"L3": "0x..."} 11 | * } 12 | */ 13 | export interface LibraryAddresses { 14 | [qualifiedNameOrSourceUnit: string]: string | { [unqualifiedLibraryName: string]: string }; 15 | } 16 | 17 | /** 18 | * A mapping between libraries and lists of placeholder instances present in their hex-encoded bytecode. 19 | * For each placeholder its length and the position of the first character is stored. 20 | * 21 | * Each start and length entry will always directly refer to the position in 22 | * binary and not hex-encoded bytecode. 23 | */ 24 | export interface LinkReferences { 25 | [libraryLabel: string]: Array<{ start: number, length: number }>; 26 | } 27 | -------------------------------------------------------------------------------- /downloadCurrentVersion.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // This is used to download the correct binary version 4 | // as part of the prepublish step. 5 | 6 | import * as fs from 'fs'; 7 | import { https } from 'follow-redirects'; 8 | import MemoryStream from 'memorystream'; 9 | import { keccak256 } from 'js-sha3'; 10 | const pkg = require('./package.json'); 11 | 12 | function getVersionList (cb) { 13 | console.log('Retrieving available version list...'); 14 | 15 | const mem = new MemoryStream(null, { readable: false }); 16 | https.get('https://binaries.soliditylang.org/bin/list.json', function (response) { 17 | if (response.statusCode !== 200) { 18 | console.log('Error downloading file: ' + response.statusCode); 19 | process.exit(1); 20 | } 21 | response.pipe(mem); 22 | response.on('end', function () { 23 | cb(mem.toString()); 24 | }); 25 | }); 26 | } 27 | 28 | function downloadBinary (outputName, version, expectedHash) { 29 | console.log('Downloading version', version); 30 | 31 | // Remove if existing 32 | if (fs.existsSync(outputName)) { 33 | fs.unlinkSync(outputName); 34 | } 35 | 36 | process.on('SIGINT', function () { 37 | console.log('Interrupted, removing file.'); 38 | fs.unlinkSync(outputName); 39 | process.exit(1); 40 | }); 41 | 42 | const file = fs.createWriteStream(outputName, { encoding: 'binary' }); 43 | https.get('https://binaries.soliditylang.org/bin/' + version, function (response) { 44 | if (response.statusCode !== 200) { 45 | console.log('Error downloading file: ' + response.statusCode); 46 | process.exit(1); 47 | } 48 | response.pipe(file); 49 | file.on('finish', function () { 50 | file.close(function () { 51 | const hash = '0x' + keccak256(fs.readFileSync(outputName, { encoding: 'binary' })); 52 | if (expectedHash !== hash) { 53 | console.log('Hash mismatch: ' + expectedHash + ' vs ' + hash); 54 | process.exit(1); 55 | } 56 | console.log('Done.'); 57 | }); 58 | }); 59 | }); 60 | } 61 | 62 | console.log('Downloading correct solidity binary...'); 63 | 64 | getVersionList(function (list) { 65 | list = JSON.parse(list); 66 | const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]; 67 | const releaseFileName = list.releases[wanted]; 68 | const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0]; 69 | if (!expectedFile) { 70 | console.log('Version list is invalid or corrupted?'); 71 | process.exit(1); 72 | } 73 | const expectedHash = expectedFile.keccak256; 74 | downloadBinary('soljson.js', releaseFileName, expectedHash); 75 | }); 76 | -------------------------------------------------------------------------------- /formatters.ts: -------------------------------------------------------------------------------- 1 | export function formatFatalError (message) { 2 | return JSON.stringify({ 3 | errors: [ 4 | { 5 | type: 'JSONError', 6 | component: 'solcjs', 7 | severity: 'error', 8 | message: message, 9 | formattedMessage: 'Error: ' + message 10 | } 11 | ] 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0xcc8d03e014e121d10602eeff729b755d5dc6a317df0d6302c8a9d3b5424aaba8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import wrapper from './wrapper'; 2 | 3 | const soljson = require('./soljson.js'); 4 | export = wrapper(soljson); 5 | -------------------------------------------------------------------------------- /linker.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { keccak256 } from 'js-sha3'; 3 | import { isNil, isObject } from './common/helpers'; 4 | import { LibraryAddresses, LinkReferences } from './common/types'; 5 | 6 | /** 7 | * Generates a new-style library placeholder from a fully-qualified library name. 8 | * 9 | * Newer versions of the compiler use hashed names instead of just truncating the name 10 | * before putting it in a placeholder. 11 | * 12 | * @param fullyQualifiedLibraryName Fully qualified library name. 13 | */ 14 | function libraryHashPlaceholder (fullyQualifiedLibraryName) { 15 | return `$${keccak256(fullyQualifiedLibraryName).slice(0, 34)}$`; 16 | } 17 | 18 | /** 19 | * Finds all placeholders corresponding to the specified library label and replaces them 20 | * with a concrete address. Works with both hex-encoded and binary bytecode as long as 21 | * the address is in the same format. 22 | * 23 | * @param bytecode Bytecode string. 24 | * 25 | * @param label Library label, either old- or new-style. Must exactly match the part between `__` markers in the 26 | * placeholders. Will be padded with `_` characters if too short or truncated if too long. 27 | * 28 | * @param address Address to replace placeholders with. Must be the right length. 29 | * It will **not** be padded with zeros if too short. 30 | */ 31 | function replacePlaceholder (bytecode, label, address) { 32 | // truncate to 36 characters 33 | const truncatedName = label.slice(0, 36); 34 | const libLabel = `__${truncatedName.padEnd(36, '_')}__`; 35 | 36 | while (bytecode.indexOf(libLabel) >= 0) { 37 | bytecode = bytecode.replace(libLabel, address); 38 | } 39 | 40 | return bytecode; 41 | } 42 | 43 | /** 44 | * Finds and all library placeholders in the provided bytecode and replaces them with actual addresses. 45 | * Supports both old- and new-style placeholders (even both in the same file). 46 | * See [Library Linking](https://docs.soliditylang.org/en/latest/using-the-compiler.html#library-linking) 47 | * for a full explanation of the linking process. 48 | * 49 | * Example of a legacy placeholder: `__lib.sol:L_____________________________` 50 | * Example of a new-style placeholder: `__$cb901161e812ceb78cfe30ca65050c4337$__` 51 | * 52 | * @param bytecode Hex-encoded bytecode string. All 40-byte substrings starting and ending with 53 | * `__` will be interpreted as placeholders. 54 | * 55 | * @param libraries Mapping between fully qualified library names and the hex-encoded 56 | * addresses they should be replaced with. Addresses shorter than 40 characters are automatically padded with zeros. 57 | * 58 | * @returns bytecode Hex-encoded bytecode string with placeholders replaced with addresses. 59 | * Note that some placeholders may remain in the bytecode if `libraries` does not provide addresses for all of them. 60 | */ 61 | function linkBytecode (bytecode: string, libraries: LibraryAddresses): string { 62 | assert(typeof bytecode === 'string'); 63 | assert(typeof libraries === 'object'); 64 | 65 | // NOTE: for backwards compatibility support old compiler which didn't use file names 66 | const librariesComplete: { [fullyQualifiedLibraryName: string]: string } = {}; 67 | 68 | for (const [fullyQualifiedLibraryName, libraryObjectOrAddress] of Object.entries(libraries)) { 69 | if (isNil(libraryObjectOrAddress)) { 70 | throw new Error(`No address provided for library ${fullyQualifiedLibraryName}`); 71 | } 72 | 73 | // API compatible with the standard JSON i/o 74 | // {"lib.sol": {"L": "0x..."}} 75 | if (isObject(libraryObjectOrAddress)) { 76 | for (const [unqualifiedLibraryName, address] of Object.entries(libraryObjectOrAddress)) { 77 | librariesComplete[unqualifiedLibraryName] = address; 78 | librariesComplete[`${fullyQualifiedLibraryName}:${unqualifiedLibraryName}`] = address; 79 | } 80 | 81 | continue; 82 | } 83 | 84 | // backwards compatible API for early solc-js versions 85 | const parsed = fullyQualifiedLibraryName.match(/^(?[^:]+):(?.+)$/); 86 | const libraryAddress = libraryObjectOrAddress as string; 87 | 88 | if (!isNil(parsed)) { 89 | const { unqualifiedLibraryName } = parsed.groups; 90 | librariesComplete[unqualifiedLibraryName] = libraryAddress; 91 | } 92 | 93 | librariesComplete[fullyQualifiedLibraryName] = libraryAddress; 94 | } 95 | 96 | for (const libraryName in librariesComplete) { 97 | let hexAddress = librariesComplete[libraryName]; 98 | 99 | if (!hexAddress.startsWith('0x') || hexAddress.length > 42) { 100 | throw new Error(`Invalid address specified for ${libraryName}`); 101 | } 102 | 103 | // remove 0x prefix 104 | hexAddress = hexAddress.slice(2).padStart(40, '0'); 105 | 106 | bytecode = replacePlaceholder(bytecode, libraryName, hexAddress); 107 | bytecode = replacePlaceholder(bytecode, libraryHashPlaceholder(libraryName), hexAddress); 108 | } 109 | 110 | return bytecode; 111 | } 112 | 113 | /** 114 | * Finds locations of all library address placeholders in the hex-encoded bytecode. 115 | * Returns information in a format matching `evm.bytecode.linkReferences` output 116 | * in Standard JSON. 117 | * 118 | * See [Library Linking](https://docs.soliditylang.org/en/latest/using-the-compiler.html#library-linking) 119 | * for a full explanation of library placeholders and linking process. 120 | * 121 | * WARNING: The output matches `evm.bytecode.linkReferences` exactly only in 122 | * case of old-style placeholders created from fully qualified library names 123 | * of no more than 36 characters, and even then only if the name does not start 124 | * or end with an underscore. This is different from 125 | * `evm.bytecode.linkReferences`, which uses fully qualified library names. 126 | * This is a limitation of the placeholder format - the fully qualified names 127 | * are not preserved in the compiled bytecode and cannot be reconstructed 128 | * without external information. 129 | * 130 | * @param bytecode Hex-encoded bytecode string. 131 | * 132 | * @returns linkReferences A mapping between library labels and their locations 133 | * in the bytecode. In case of old-style placeholders the label is a fully 134 | * qualified library name truncated to 36 characters. For new-style placeholders 135 | * it's the first 34 characters of the hex-encoded hash of the fully qualified 136 | * library name, with a leading and trailing $ character added. Note that the 137 | * offsets and lengths refer to the *binary* (not hex-encoded) bytecode, just 138 | * like in `evm.bytecode.linkReferences`. 139 | */ 140 | function findLinkReferences (bytecode: string): LinkReferences { 141 | assert(typeof bytecode === 'string'); 142 | 143 | // find 40 bytes in the pattern of __...<36 digits>...__ 144 | // e.g. __Lib.sol:L_____________________________ 145 | const linkReferences: LinkReferences = {}; 146 | 147 | let offset = 0; 148 | 149 | while (true) { 150 | const found = bytecode.match(/__(.{36})__/); 151 | if (!found) { 152 | break; 153 | } 154 | 155 | const start = found.index; 156 | 157 | // trim trailing underscores 158 | // NOTE: this has no way of knowing if the trailing underscore was part of the name 159 | const libraryName = found[1].replace(/_+$/gm, ''); 160 | 161 | if (!linkReferences[libraryName]) { 162 | linkReferences[libraryName] = []; 163 | } 164 | 165 | // offsets are in bytes in binary representation (and not hex) 166 | linkReferences[libraryName].push({ 167 | start: (offset + start) / 2, 168 | length: 20 169 | }); 170 | 171 | offset += start + 20; 172 | bytecode = bytecode.slice(start + 20); 173 | } 174 | 175 | return linkReferences; 176 | } 177 | 178 | export = { 179 | linkBytecode, 180 | findLinkReferences 181 | }; 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solc", 3 | "version": "0.8.30", 4 | "description": "Solidity compiler", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "bin": { 8 | "solcjs": "solc.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "postbuild": "node build/postbuild.js && cp README.md LICENSE dist/", 13 | "lint": "eslint --ext .js,.ts .", 14 | "lint:fix": "eslint --fix --ext .js,.ts .", 15 | "updateBinary": "node build/clean.js && ts-node ./downloadCurrentVersion.ts && ts-node ./verifyVersion.ts", 16 | "prepack": "node build/pack-publish-block.js", 17 | "build:tarball": "npm run updateBinary && npm run build && BYPASS_SAFETY_CHECK=true npm pack ./dist", 18 | "publish:tarball": "tarball=$(npm run --silent tarballName) && ls \"$tarball\" && BYPASS_SAFETY_CHECK=true npm publish \"$tarball\"", 19 | "tarballName": "jq --raw-output '.name + \"-\" + .version + \".tgz\"' package.json", 20 | "copyTestFiles": "cp -r ./test/resources ./dist/test/", 21 | "pretest": "npm run lint && npm run build && npm run copyTestFiles", 22 | "test": "cd dist && tape ./test/index.js", 23 | "coverage": "nyc npm run test", 24 | "coveralls": "npm run coverage && coveralls =12.0.0" 37 | }, 38 | "files": [ 39 | "common/*.js", 40 | "common/*.d.ts", 41 | "bindings/*.js", 42 | "bindings/*.d.ts", 43 | "*.js", 44 | "*.d.ts" 45 | ], 46 | "author": "chriseth", 47 | "license": "MIT", 48 | "bugs": { 49 | "url": "https://github.com/ethereum/solc-js/issues" 50 | }, 51 | "homepage": "https://github.com/ethereum/solc-js#readme", 52 | "dependencies": { 53 | "command-exists": "^1.2.8", 54 | "commander": "^8.1.0", 55 | "follow-redirects": "^1.12.1", 56 | "js-sha3": "0.8.0", 57 | "memorystream": "^0.3.1", 58 | "semver": "^5.5.0", 59 | "tmp": "0.0.33" 60 | }, 61 | "devDependencies": { 62 | "@types/node": "^16.11.7", 63 | "@types/semver": "^7.3.9", 64 | "@types/tape": "^4.13.2", 65 | "@types/tmp": "^0.2.3", 66 | "@typescript-eslint/eslint-plugin": "^5.8.0", 67 | "@typescript-eslint/parser": "^5.8.0", 68 | "coveralls": "^3.0.0", 69 | "eslint": "^7.32.0", 70 | "eslint-config-standard": "^16.0.3", 71 | "eslint-plugin-import": "^2.25.3", 72 | "eslint-plugin-node": "^11.1.0", 73 | "eslint-plugin-promise": "^5.1.1", 74 | "nyc": "^15.1.0", 75 | "tape": "^4.11.0", 76 | "tape-spawn": "^1.4.2", 77 | "ts-node": "^10.4.0", 78 | "typescript": "^4.5.4" 79 | }, 80 | "nyc": { 81 | "exclude": [ 82 | "soljson.js", 83 | "dist" 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /smtchecker.ts: -------------------------------------------------------------------------------- 1 | // This function checks the standard JSON output for auxiliaryInputRequested, 2 | // where smtlib2queries represent the queries created by the SMTChecker. 3 | // The function runs an SMT solver on each query and adjusts the input for 4 | // another run. 5 | // Returns null if no solving is requested. 6 | function handleSMTQueries (inputJSON: any, outputJSON: any, solverFunction: any, solver?: any) { 7 | const auxInputReq = outputJSON.auxiliaryInputRequested; 8 | if (!auxInputReq) { 9 | return null; 10 | } 11 | 12 | const queries = auxInputReq.smtlib2queries; 13 | if (!queries || Object.keys(queries).length === 0) { 14 | return null; 15 | } 16 | 17 | const responses = {}; 18 | for (const query in queries) { 19 | responses[query] = solverFunction(queries[query], solver); 20 | } 21 | 22 | // Note: all existing solved queries are replaced. 23 | // This assumes that all necessary queries are quested above. 24 | inputJSON.auxiliaryInput = { smtlib2responses: responses }; 25 | return inputJSON; 26 | } 27 | 28 | function smtCallback (solverFunction, solver?: any) { 29 | return function (query) { 30 | try { 31 | const result = solverFunction(query, solver); 32 | return { contents: result }; 33 | } catch (err) { 34 | return { error: err }; 35 | } 36 | }; 37 | } 38 | 39 | export = { 40 | handleSMTQueries, 41 | smtCallback 42 | }; 43 | -------------------------------------------------------------------------------- /smtsolver.ts: -------------------------------------------------------------------------------- 1 | import { sync as commandExistsSync } from 'command-exists'; 2 | import { execSync } from 'child_process'; 3 | import * as fs from 'fs'; 4 | import * as tmp from 'tmp'; 5 | 6 | // Timeout in ms. 7 | const timeout = 10000; 8 | 9 | const potentialSolvers = [ 10 | { 11 | name: 'z3', 12 | command: 'z3', 13 | params: '-smt2 rlimit=20000000 rewriter.pull_cheap_ite=true fp.spacer.q3.use_qgen=true fp.spacer.mbqi=false fp.spacer.ground_pobs=false' 14 | }, 15 | { 16 | name: 'Eldarica', 17 | command: 'eld', 18 | params: '-horn -t:' + (timeout / 1000) // Eldarica takes timeout in seconds. 19 | }, 20 | { 21 | name: 'cvc5', 22 | command: 'cvc5', 23 | params: '--lang=smt2 --tlimit=' + timeout 24 | } 25 | ]; 26 | 27 | const solvers = potentialSolvers.filter(solver => commandExistsSync(solver.command)); 28 | 29 | function solve (query, solver) { 30 | if (solver === undefined) { 31 | if (solvers.length === 0) { 32 | throw new Error('No SMT solver available. Assertion checking will not be performed.'); 33 | } else { 34 | solver = solvers[0]; 35 | } 36 | } 37 | 38 | const tmpFile = tmp.fileSync({ postfix: '.smt2' }); 39 | fs.writeFileSync(tmpFile.name, query); 40 | let solverOutput; 41 | try { 42 | solverOutput = execSync( 43 | solver.command + ' ' + solver.params + ' ' + tmpFile.name, { 44 | encoding: 'utf8', 45 | maxBuffer: 1024 * 1024 * 1024, 46 | stdio: 'pipe', 47 | timeout: timeout // Enforce timeout on the process, since solvers can sometimes go around it. 48 | } 49 | ).toString(); 50 | } catch (e) { 51 | // execSync throws if the process times out or returns != 0. 52 | // The latter might happen with z3 if the query asks for a model 53 | // for an UNSAT formula. We can still use stdout. 54 | solverOutput = e.stdout.toString(); 55 | if ( 56 | !solverOutput.startsWith('sat') && 57 | !solverOutput.startsWith('unsat') && 58 | !solverOutput.startsWith('unknown') && 59 | !solverOutput.startsWith('(error') && // Eldarica reports errors in an sexpr, for example: '(error "Failed to reconstruct array model")' 60 | !solverOutput.startsWith('error') 61 | ) { 62 | throw new Error('Failed to solve SMT query. ' + e.toString()); 63 | } 64 | } 65 | // Trigger early manual cleanup 66 | tmpFile.removeCallback(); 67 | return solverOutput; 68 | } 69 | 70 | export = { 71 | smtSolver: solve, 72 | availableSolvers: solvers 73 | }; 74 | -------------------------------------------------------------------------------- /solc.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as commander from 'commander'; 4 | import * as fs from 'fs'; 5 | import * as os from 'os'; 6 | import * as path from 'path'; 7 | import solc from './index'; 8 | import smtchecker from './smtchecker'; 9 | import smtsolver from './smtsolver'; 10 | 11 | // hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end 12 | const originalUncaughtExceptionListeners = process.listeners('uncaughtException'); 13 | // FIXME: remove annoying exception catcher of Emscripten 14 | // see https://github.com/chriseth/browser-solidity/issues/167 15 | process.removeAllListeners('uncaughtException'); 16 | 17 | const program: any = new commander.Command(); 18 | const commanderParseInt = function (value) { 19 | const parsedValue = parseInt(value, 10); 20 | if (isNaN(parsedValue)) { 21 | throw new commander.InvalidArgumentError('Not a valid integer.'); 22 | } 23 | return parsedValue; 24 | }; 25 | 26 | program.name('solcjs'); 27 | program.version(solc.version()); 28 | program 29 | .option('--version', 'Show version and exit.') 30 | .option('--optimize', 'Enable bytecode optimizer.', false) 31 | .option( 32 | '--optimize-runs ', 33 | 'The number of runs specifies roughly how often each opcode of the deployed code will be executed across the lifetime of the contract. ' + 34 | 'Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage.', 35 | commanderParseInt 36 | ) 37 | .option('--bin', 'Binary of the contracts in hex.') 38 | .option('--abi', 'ABI of the contracts.') 39 | .option('--standard-json', 'Turn on Standard JSON Input / Output mode.') 40 | .option('--base-path ', 'Root of the project source tree. ' + 41 | 'The import callback will attempt to interpret all import paths as relative to this directory.' 42 | ) 43 | .option('--include-path ', 'Extra source directories available to the import callback. ' + 44 | 'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' + 45 | 'Can be used multiple times to provide multiple locations.' 46 | ) 47 | .option('-o, --output-dir ', 'Output directory for the contracts.') 48 | .option('-p, --pretty-json', 'Pretty-print all JSON output.', false) 49 | .option('-v, --verbose', 'More detailed console output.', false); 50 | 51 | program.parse(process.argv); 52 | const options = program.opts(); 53 | 54 | const files = program.args; 55 | const destination = options.outputDir || '.'; 56 | 57 | function abort (msg) { 58 | console.error(msg || 'Error occurred'); 59 | process.exit(1); 60 | } 61 | 62 | function readFileCallback (sourcePath) { 63 | const prefixes = [options.basePath ? options.basePath : ''].concat( 64 | options.includePath ? options.includePath : [] 65 | ); 66 | for (const prefix of prefixes) { 67 | const prefixedSourcePath = (prefix ? prefix + '/' : '') + sourcePath; 68 | 69 | if (fs.existsSync(prefixedSourcePath)) { 70 | try { 71 | return { contents: fs.readFileSync(prefixedSourcePath).toString('utf8') }; 72 | } catch (e) { 73 | return { error: 'Error reading ' + prefixedSourcePath + ': ' + e }; 74 | } 75 | } 76 | } 77 | return { error: 'File not found inside the base path or any of the include paths.' }; 78 | } 79 | 80 | function withUnixPathSeparators (filePath) { 81 | // On UNIX-like systems forward slashes in paths are just a part of the file name. 82 | if (os.platform() !== 'win32') { 83 | return filePath; 84 | } 85 | 86 | return filePath.replace(/\\/g, '/'); 87 | } 88 | 89 | function makeSourcePathRelativeIfPossible (sourcePath) { 90 | const absoluteBasePath = (options.basePath ? path.resolve(options.basePath) : path.resolve('.')); 91 | const absoluteIncludePaths = ( 92 | options.includePath 93 | ? options.includePath.map((prefix) => { return path.resolve(prefix); }) 94 | : [] 95 | ); 96 | 97 | // Compared to base path stripping logic in solc this is much simpler because path.resolve() 98 | // handles symlinks correctly (does not resolve them except in work dir) and strips .. segments 99 | // from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it 100 | // ignores less important corner cases: drive letters are not stripped from absolute paths on 101 | // Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has 102 | // very little test coverage so there might be more differences that we are just not aware of. 103 | const absoluteSourcePath = path.resolve(sourcePath); 104 | 105 | for (const absolutePrefix of [absoluteBasePath].concat(absoluteIncludePaths)) { 106 | const relativeSourcePath = path.relative(absolutePrefix, absoluteSourcePath); 107 | 108 | if (!relativeSourcePath.startsWith('../')) { return withUnixPathSeparators(relativeSourcePath); } 109 | } 110 | 111 | // File is not located inside base path or include paths so use its absolute path. 112 | return withUnixPathSeparators(absoluteSourcePath); 113 | } 114 | 115 | function toFormattedJson (input) { 116 | return JSON.stringify(input, null, program.prettyJson ? 4 : 0); 117 | } 118 | 119 | function reformatJsonIfRequested (inputJson) { 120 | return (program.prettyJson ? toFormattedJson(JSON.parse(inputJson)) : inputJson); 121 | } 122 | 123 | let callbacks; 124 | if (options.basePath || !options.standardJson) { callbacks = { import: readFileCallback }; } 125 | 126 | if (options.standardJson) { 127 | const input = fs.readFileSync(process.stdin.fd).toString('utf8'); 128 | if (program.verbose) { console.log('>>> Compiling:\n' + reformatJsonIfRequested(input) + '\n'); } 129 | let output = reformatJsonIfRequested(solc.compile(input, callbacks)); 130 | 131 | try { 132 | if (smtsolver.availableSolvers.length === 0) { 133 | console.log('>>> Cannot retry compilation with SMT because there are no SMT solvers available.'); 134 | } else { 135 | const inputJSON = smtchecker.handleSMTQueries(JSON.parse(input), JSON.parse(output), smtsolver.smtSolver, smtsolver.availableSolvers[0]); 136 | if (inputJSON) { 137 | if (program.verbose) { console.log('>>> Retrying compilation with SMT:\n' + toFormattedJson(inputJSON) + '\n'); } 138 | output = reformatJsonIfRequested(solc.compile(JSON.stringify(inputJSON), callbacks)); 139 | } 140 | } 141 | } catch (e) { 142 | const addError = { 143 | component: 'general', 144 | formattedMessage: e.toString(), 145 | message: e.toString(), 146 | type: 'Warning' 147 | }; 148 | 149 | const outputJSON = JSON.parse(output); 150 | if (!outputJSON.errors) { 151 | outputJSON.errors = []; 152 | } 153 | outputJSON.errors.push(addError); 154 | output = toFormattedJson(outputJSON); 155 | } 156 | 157 | if (program.verbose) { console.log('>>> Compilation result:'); } 158 | console.log(output); 159 | process.exit(0); 160 | } else if (files.length === 0) { 161 | console.error('Must provide a file'); 162 | process.exit(1); 163 | } 164 | 165 | if (!(options.bin || options.abi)) { 166 | abort('Invalid option selected, must specify either --bin or --abi'); 167 | } 168 | 169 | if (!options.basePath && options.includePath && options.includePath.length > 0) { 170 | abort('--include-path option requires a non-empty base path.'); 171 | } 172 | 173 | if (options.includePath) { 174 | for (const includePath of options.includePath) { 175 | if (!includePath) { abort('Empty values are not allowed in --include-path.'); } 176 | } 177 | } 178 | 179 | const sources = {}; 180 | 181 | for (let i = 0; i < files.length; i++) { 182 | try { 183 | sources[makeSourcePathRelativeIfPossible(files[i])] = { 184 | content: fs.readFileSync(files[i]).toString() 185 | }; 186 | } catch (e) { 187 | abort('Error reading ' + files[i] + ': ' + e); 188 | } 189 | } 190 | 191 | const cliInput = { 192 | language: 'Solidity', 193 | settings: { 194 | optimizer: { 195 | enabled: options.optimize, 196 | runs: options.optimizeRuns 197 | }, 198 | outputSelection: { 199 | '*': { 200 | '*': ['abi', 'evm.bytecode'] 201 | } 202 | } 203 | }, 204 | sources: sources 205 | }; 206 | if (program.verbose) { console.log('>>> Compiling:\n' + toFormattedJson(cliInput) + '\n'); } 207 | const output = JSON.parse(solc.compile(JSON.stringify(cliInput), callbacks)); 208 | 209 | let hasError = false; 210 | 211 | if (!output) { 212 | abort('No output from compiler'); 213 | } else if (output.errors) { 214 | for (const error in output.errors) { 215 | const message = output.errors[error]; 216 | if (message.severity === 'warning') { 217 | console.log(message.formattedMessage); 218 | } else { 219 | console.error(message.formattedMessage); 220 | hasError = true; 221 | } 222 | } 223 | } 224 | 225 | fs.mkdirSync(destination, { recursive: true }); 226 | 227 | function writeFile (file, content) { 228 | file = path.join(destination, file); 229 | fs.writeFile(file, content, function (err) { 230 | if (err) { 231 | console.error('Failed to write ' + file + ': ' + err); 232 | } 233 | }); 234 | } 235 | 236 | for (const fileName in output.contracts) { 237 | for (const contractName in output.contracts[fileName]) { 238 | let contractFileName = fileName + ':' + contractName; 239 | contractFileName = contractFileName.replace(/[:./\\]/g, '_'); 240 | 241 | if (options.bin) { 242 | writeFile(contractFileName + '.bin', output.contracts[fileName][contractName].evm.bytecode.object); 243 | } 244 | 245 | if (options.abi) { 246 | writeFile(contractFileName + '.abi', toFormattedJson(output.contracts[fileName][contractName].abi)); 247 | } 248 | } 249 | } 250 | 251 | // Put back original exception handlers. 252 | originalUncaughtExceptionListeners.forEach(function (listener) { 253 | process.addListener('uncaughtException', listener); 254 | }); 255 | 256 | if (hasError) { 257 | process.exit(1); 258 | } 259 | -------------------------------------------------------------------------------- /test/abi.ts: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import abi from '../abi'; 3 | 4 | tape('ABI translator', function (t) { 5 | t.test('Empty ABI', function (st) { 6 | st.deepEqual(abi.update('0.4.0', []), []); 7 | st.end(); 8 | }); 9 | t.test('0.1.1 (no constructor)', function (st) { 10 | st.deepEqual(abi.update('0.1.1', []), [{ inputs: [], payable: true, stateMutability: 'payable', type: 'constructor' }, { payable: true, stateMutability: 'payable', type: 'fallback' }]); 11 | st.end(); 12 | }); 13 | t.test('0.3.6 (constructor)', function (st) { 14 | const input = [{ inputs: [], type: 'constructor' }]; 15 | st.deepEqual(abi.update('0.3.6', input), [{ inputs: [], payable: true, stateMutability: 'payable', type: 'constructor' }, { payable: true, stateMutability: 'payable', type: 'fallback' }]); 16 | st.end(); 17 | }); 18 | t.test('0.3.6 (non-constant function)', function (st) { 19 | const input = [{ inputs: [], type: 'function' }]; 20 | st.deepEqual(abi.update('0.3.6', input), [{ inputs: [], payable: true, stateMutability: 'payable', type: 'function' }, { payable: true, stateMutability: 'payable', type: 'fallback' }]); 21 | st.end(); 22 | }); 23 | t.test('0.3.6 (constant function)', function (st) { 24 | const input = [{ inputs: [], type: 'function', constant: true }]; 25 | st.deepEqual(abi.update('0.3.6', input), [{ inputs: [], constant: true, stateMutability: 'view', type: 'function' }, { payable: true, stateMutability: 'payable', type: 'fallback' }]); 26 | st.end(); 27 | }); 28 | t.test('0.3.6 (event)', function (st) { 29 | const input = [{ inputs: [], type: 'event' }]; 30 | st.deepEqual(abi.update('0.3.6', input), [{ inputs: [], type: 'event' }, { payable: true, stateMutability: 'payable', type: 'fallback' }]); 31 | st.end(); 32 | }); 33 | t.test('0.3.6 (has no fallback)', function (st) { 34 | const input = [{ inputs: [], type: 'constructor' }]; 35 | st.deepEqual(abi.update('0.3.6', input), [{ inputs: [], type: 'constructor', payable: true, stateMutability: 'payable' }, { type: 'fallback', payable: true, stateMutability: 'payable' }]); 36 | st.end(); 37 | }); 38 | t.test('0.4.0 (has fallback)', function (st) { 39 | const input = [{ inputs: [], type: 'constructor' }, { type: 'fallback' }]; 40 | st.deepEqual(abi.update('0.4.0', input), [{ inputs: [], type: 'constructor', payable: true, stateMutability: 'payable' }, { type: 'fallback', stateMutability: 'nonpayable' }]); 41 | st.end(); 42 | }); 43 | t.test('0.4.0 (non-constant function)', function (st) { 44 | const input = [{ inputs: [], type: 'function' }]; 45 | st.deepEqual(abi.update('0.4.0', input), [{ inputs: [], stateMutability: 'nonpayable', type: 'function' }]); 46 | st.end(); 47 | }); 48 | t.test('0.4.0 (constant function)', function (st) { 49 | const input = [{ inputs: [], type: 'function', constant: true }]; 50 | st.deepEqual(abi.update('0.4.0', input), [{ inputs: [], constant: true, stateMutability: 'view', type: 'function' }]); 51 | st.end(); 52 | }); 53 | t.test('0.4.0 (payable function)', function (st) { 54 | const input = [{ inputs: [], payable: true, type: 'function' }]; 55 | st.deepEqual(abi.update('0.4.0', input), [{ inputs: [], payable: true, stateMutability: 'payable', type: 'function' }]); 56 | st.end(); 57 | }); 58 | t.test('0.4.1 (constructor not payable)', function (st) { 59 | const input = [{ inputs: [], payable: false, type: 'constructor' }]; 60 | st.deepEqual(abi.update('0.4.1', input), [{ inputs: [], payable: true, stateMutability: 'payable', type: 'constructor' }]); 61 | st.end(); 62 | }); 63 | t.test('0.4.5 (constructor payable)', function (st) { 64 | const input = [{ inputs: [], payable: false, type: 'constructor' }]; 65 | st.deepEqual(abi.update('0.4.5', input), [{ inputs: [], payable: false, stateMutability: 'nonpayable', type: 'constructor' }]); 66 | st.end(); 67 | }); 68 | t.test('0.4.16 (statemutability)', function (st) { 69 | const input = [{ inputs: [], payable: false, stateMutability: 'pure', type: 'function' }]; 70 | st.deepEqual(abi.update('0.4.16', input), [{ inputs: [], payable: false, stateMutability: 'pure', type: 'function' }]); 71 | st.end(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/cli.ts: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import spawn from 'tape-spawn'; 3 | import * as path from 'path'; 4 | import solc from '../'; 5 | 6 | tape('CLI', function (t) { 7 | t.test('--version', function (st) { 8 | const spt = spawn(st, './solc.js --version'); 9 | spt.stdout.match(solc.version() + '\n'); 10 | spt.stdout.match(/^\s*[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?\+commit\.[0-9a-f]+([a-zA-Z0-9.-]+)?\s*$/); 11 | spt.stderr.empty(); 12 | spt.end(); 13 | }); 14 | 15 | t.test('no parameters', function (st) { 16 | const spt = spawn(st, './solc.js'); 17 | spt.stderr.match(/^Must provide a file/); 18 | spt.end(); 19 | }); 20 | 21 | t.test('no mode specified', function (st) { 22 | const spt = spawn(st, './solc.js test/resources/fixtureSmoke.sol'); 23 | spt.stderr.match(/^Invalid option selected/); 24 | spt.end(); 25 | }); 26 | 27 | t.test('--bin', function (st) { 28 | const spt = spawn(st, './solc.js --bin test/resources/fixtureSmoke.sol'); 29 | spt.stderr.empty(); 30 | spt.succeeds(); 31 | spt.end(); 32 | }); 33 | 34 | t.test('--bin --optimize', function (st) { 35 | const spt = spawn(st, './solc.js --bin --optimize test/resources/fixtureSmoke.sol'); 36 | spt.stderr.empty(); 37 | spt.succeeds(); 38 | spt.end(); 39 | }); 40 | 41 | t.test('--bin --optimize-runs 666', function (st) { 42 | const spt = spawn(st, './solc.js --bin --optimize-runs 666 test/resources/fixtureSmoke.sol'); 43 | spt.stderr.empty(); 44 | spt.succeeds(); 45 | spt.end(); 46 | }); 47 | 48 | t.test('--bin --optimize-runs not-a-number', function (st) { 49 | const spt = spawn(st, './solc.js --bin --optimize-runs not-a-number test/resources/fixtureSmoke.sol'); 50 | spt.stderr.match(/^error: option '--optimize-runs ' argument 'not-a-number' is invalid/); 51 | spt.end(); 52 | }); 53 | 54 | t.test('invalid file specified', function (st) { 55 | const spt = spawn(st, './solc.js --bin test/fileNotFound.sol'); 56 | spt.stderr.match(/^Error reading /); 57 | spt.end(); 58 | }); 59 | 60 | t.test('incorrect source source', function (st) { 61 | const spt = spawn(st, './solc.js --bin test/resources/fixtureIncorrectSource.sol'); 62 | spt.stderr.match(/SyntaxError: Invalid pragma "contract"/); 63 | spt.end(); 64 | }); 65 | 66 | t.test('--abi', function (st) { 67 | const spt = spawn(st, './solc.js --abi test/resources/fixtureSmoke.sol'); 68 | spt.stderr.empty(); 69 | spt.succeeds(); 70 | spt.end(); 71 | }); 72 | 73 | t.test('--bin --abi', function (st) { 74 | const spt = spawn(st, './solc.js --bin --abi test/resources/fixtureSmoke.sol'); 75 | spt.stderr.empty(); 76 | spt.succeeds(); 77 | spt.end(); 78 | }); 79 | 80 | t.test('no base path', function (st) { 81 | const spt = spawn( 82 | st, 83 | './solc.js --bin ' + 84 | 'test/resources/importA.sol ' + 85 | './test/resources//importA.sol ' + 86 | path.resolve('test/resources/importA.sol') 87 | ); 88 | spt.stderr.empty(); 89 | spt.succeeds(); 90 | spt.end(); 91 | }); 92 | 93 | t.test('relative base path', function (st) { 94 | // NOTE: This and other base path tests rely on the relative ./importB.sol import in importA.sol. 95 | // If base path is not stripped correctly from all source paths below, they will not be found 96 | // by the import callback when it appends the base path back. 97 | const spt = spawn( 98 | st, 99 | './solc.js --bin --base-path test/resources ' + 100 | 'test/resources/importA.sol ' + 101 | './test/resources//importA.sol ' + 102 | path.resolve('test/resources/importA.sol') 103 | ); 104 | spt.stderr.empty(); 105 | spt.succeeds(); 106 | spt.end(); 107 | }); 108 | 109 | t.test('relative non canonical base path', function (st) { 110 | const spt = spawn( 111 | st, 112 | './solc.js --bin --base-path ./test/resources ' + 113 | 'test/resources/importA.sol ' + 114 | './test/resources//importA.sol ' + 115 | path.resolve('test/resources/importA.sol') 116 | ); 117 | spt.stderr.empty(); 118 | spt.succeeds(); 119 | spt.end(); 120 | }); 121 | 122 | t.test('absolute base path', function (st) { 123 | const spt = spawn( 124 | st, 125 | './solc.js --bin --base-path ' + path.resolve('test/resources') + ' ' + 126 | 'test/resources/importA.sol ' + 127 | './test/resources//importA.sol ' + 128 | path.resolve('test/resources/importA.sol') 129 | ); 130 | spt.stderr.empty(); 131 | spt.succeeds(); 132 | spt.end(); 133 | }); 134 | 135 | t.test('include paths', function (st) { 136 | const spt = spawn( 137 | st, 138 | './solc.js --bin ' + 139 | 'test/resources/importCallback/base/contractB.sol ' + 140 | 'test/resources/importCallback/includeA/libY.sol ' + 141 | './test/resources/importCallback/includeA//libY.sol ' + 142 | path.resolve('test/resources/importCallback/includeA/libY.sol') + ' ' + 143 | '--base-path test/resources/importCallback/base ' + 144 | '--include-path test/resources/importCallback/includeA ' + 145 | '--include-path ' + path.resolve('test/resources/importCallback/includeB/') 146 | ); 147 | spt.stderr.empty(); 148 | spt.succeeds(); 149 | spt.end(); 150 | }); 151 | 152 | t.test('include paths without base path', function (st) { 153 | const spt = spawn( 154 | st, 155 | './solc.js --bin ' + 156 | 'test/resources/importCallback/contractC.sol ' + 157 | '--include-path test/resources/importCallback/includeA' 158 | ); 159 | spt.stderr.match(/--include-path option requires a non-empty base path\./); 160 | spt.fails(); 161 | spt.end(); 162 | }); 163 | 164 | t.test('empty include paths', function (st) { 165 | const spt = spawn( 166 | st, 167 | './solc.js --bin ' + 168 | 'test/resources/importCallback/contractC.sol ' + 169 | '--base-path test/resources/importCallback/base ' + 170 | '--include-path=' 171 | ); 172 | spt.stderr.match(/Empty values are not allowed in --include-path\./); 173 | spt.fails(); 174 | spt.end(); 175 | }); 176 | 177 | t.test('standard json', function (st) { 178 | const input = { 179 | language: 'Solidity', 180 | settings: { 181 | outputSelection: { 182 | '*': { 183 | '*': ['evm.bytecode', 'userdoc'] 184 | } 185 | } 186 | }, 187 | sources: { 188 | 'Contract.sol': { 189 | content: 'pragma solidity >=0.5.0; contract Contract { function f() pure public {} }' 190 | } 191 | } 192 | }; 193 | const spt = spawn(st, './solc.js --standard-json'); 194 | spt.stdin.setEncoding('utf-8'); 195 | spt.stdin.write(JSON.stringify(input)); 196 | spt.stdin.end(); 197 | spt.stdin.on('finish', function () { 198 | spt.stderr.empty(); 199 | spt.stdout.match(/Contract.sol/); 200 | spt.stdout.match(/userdoc/); 201 | spt.succeeds(); 202 | spt.end(); 203 | }); 204 | }); 205 | 206 | t.test('standard json base path', function (st) { 207 | const input = { 208 | language: 'Solidity', 209 | settings: { 210 | outputSelection: { 211 | '*': { 212 | '*': ['metadata'] 213 | } 214 | } 215 | }, 216 | sources: { 217 | 'importA.sol': { 218 | content: 'import "./importB.sol";' 219 | } 220 | } 221 | }; 222 | const spt = spawn(st, './solc.js --standard-json --base-path test/resources'); 223 | spt.stdin.setEncoding('utf-8'); 224 | spt.stdin.write(JSON.stringify(input)); 225 | spt.stdin.end(); 226 | spt.stdin.on('finish', function () { 227 | spt.stderr.empty(); 228 | spt.stdout.match(/{"contracts":{"importB.sol":{"D":{"metadata":/); 229 | spt.succeeds(); 230 | spt.end(); 231 | }); 232 | }); 233 | 234 | t.test('standard json include paths', function (st) { 235 | const input = { 236 | language: 'Solidity', 237 | sources: { 238 | 'contractB.sol': { 239 | content: 240 | '// SPDX-License-Identifier: GPL-3.0\n' + 241 | 'pragma solidity >=0.0;\n' + 242 | 'import "./contractA.sol";\n' 243 | } 244 | } 245 | }; 246 | const spt = spawn( 247 | st, 248 | './solc.js --standard-json ' + 249 | '--base-path test/resources/importCallback/base ' + 250 | '--include-path test/resources/importCallback/includeA ' + 251 | '--include-path ' + path.resolve('test/resources/importCallback/includeB/') 252 | ); 253 | spt.stdin.setEncoding('utf-8'); 254 | spt.stdin.write(JSON.stringify(input)); 255 | spt.stdin.end(); 256 | spt.stdin.on('finish', function () { 257 | spt.stderr.empty(); 258 | spt.stdout.match(/"sources":{"contractA.sol":{"id":0},"contractB.sol":{"id":1},"libX.sol":{"id":2},"libY.sol":{"id":3},"libZ.sol":{"id":4},"utils.sol":{"id":5}}}/); 259 | spt.succeeds(); 260 | spt.end(); 261 | }); 262 | }); 263 | }); 264 | -------------------------------------------------------------------------------- /test/compiler.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import tape from 'tape'; 3 | import * as semver from 'semver'; 4 | import * as tmp from 'tmp'; 5 | import solc from '../'; 6 | import linker from '../linker'; 7 | import { execSync } from 'child_process'; 8 | import wrapper from '../wrapper'; 9 | 10 | const noRemoteVersions = (process.argv.indexOf('--no-remote-versions') >= 0); 11 | 12 | function runTests (solc, versionText) { 13 | console.log(`Running tests with ${versionText} ${solc.version()}`); 14 | 15 | function resplitFileNameOnFirstColon (fileName, contractName) { 16 | assert(!contractName.includes(':')); 17 | 18 | const contractNameComponents = fileName.split(':'); 19 | const truncatedFileName = contractNameComponents.shift(); 20 | contractNameComponents.push(contractName); 21 | 22 | return [truncatedFileName, contractNameComponents.join(':')]; 23 | } 24 | 25 | function getBytecode (output, fileName, contractName) { 26 | try { 27 | let outputContract; 28 | if (semver.lt(solc.semver(), '0.4.9')) { 29 | outputContract = output.contracts[contractName]; 30 | } else { 31 | outputContract = output.contracts[fileName + ':' + contractName]; 32 | } 33 | return outputContract.bytecode; 34 | } catch (e) { 35 | return ''; 36 | } 37 | } 38 | 39 | function getBytecodeStandard (output, fileName, contractName) { 40 | try { 41 | let outputFile; 42 | if (semver.lt(solc.semver(), '0.4.9')) { 43 | outputFile = output.contracts['']; 44 | } else { 45 | if (semver.gt(solc.semver(), '0.4.10') && semver.lt(solc.semver(), '0.4.20')) { 46 | [fileName, contractName] = resplitFileNameOnFirstColon(fileName, contractName); 47 | } 48 | outputFile = output.contracts[fileName]; 49 | } 50 | return outputFile[contractName].evm.bytecode.object; 51 | } catch (e) { 52 | return ''; 53 | } 54 | } 55 | 56 | function getGasEstimate (output, fileName, contractName) { 57 | try { 58 | let outputFile; 59 | if (semver.lt(solc.semver(), '0.4.9')) { 60 | outputFile = output.contracts['']; 61 | } else { 62 | if (semver.gt(solc.semver(), '0.4.10') && semver.gt(solc.semver(), '0.4.20')) { 63 | [fileName, contractName] = resplitFileNameOnFirstColon(fileName, contractName); 64 | } 65 | outputFile = output.contracts[fileName]; 66 | } 67 | return outputFile[contractName].evm.gasEstimates; 68 | } catch (e) { 69 | return ''; 70 | } 71 | } 72 | 73 | function expectError (output: any, errorType: any, message: any) { 74 | if (output.errors) { 75 | for (const errorIndex in output.errors) { 76 | const error = output.errors[errorIndex]; 77 | if (error.type === errorType) { 78 | if (message) { 79 | if (error.message === message || error.message.match(message) !== null) { 80 | return true; 81 | } 82 | } else { 83 | return true; 84 | } 85 | } 86 | } 87 | } 88 | return false; 89 | } 90 | 91 | function expectNoError (output: any) { 92 | if (output.errors) { 93 | for (const errorIndex in output.errors) { 94 | const error = output.errors[errorIndex]; 95 | if (error.severity === 'error') { 96 | return false; 97 | } 98 | } 99 | } 100 | return true; 101 | } 102 | 103 | tape(versionText, function (t) { 104 | const tape = t.test; 105 | 106 | tape('Version and license', function (t) { 107 | t.test('check version', function (st) { 108 | st.equal(typeof solc.version(), 'string'); 109 | st.end(); 110 | }); 111 | t.test('check semver', function (st) { 112 | st.equal(typeof solc.semver(), 'string'); 113 | st.end(); 114 | }); 115 | t.test('check license', function (st) { 116 | st.ok(typeof solc.license() === 'undefined' || typeof solc.license() === 'string'); 117 | st.end(); 118 | }); 119 | }); 120 | 121 | tape('Compilation', function (t) { 122 | t.test('single files can be compiled (using lowlevel API)', function (st) { 123 | if (typeof solc.lowlevel.compileSingle !== 'function') { 124 | st.skip('Low-level compileSingle interface not implemented by this compiler version.'); 125 | st.end(); 126 | return; 127 | } 128 | 129 | const output = JSON.parse(solc.lowlevel.compileSingle('contract A { function g() public {} }')); 130 | st.ok('contracts' in output); 131 | const bytecode = getBytecode(output, '', 'A'); 132 | st.ok(typeof bytecode === 'string'); 133 | st.ok(bytecode.length > 0); 134 | st.end(); 135 | }); 136 | 137 | t.test('invalid source code fails properly (using lowlevel API)', function (st) { 138 | // TODO: try finding an example which doesn't crash it? 139 | if (semver.eq(solc.semver(), '0.4.11')) { 140 | st.skip('Skipping on broken compiler version'); 141 | st.end(); 142 | return; 143 | } 144 | 145 | if (typeof solc.lowlevel.compileSingle !== 'function') { 146 | st.skip('Low-level compileSingle interface not implemented by this compiler version.'); 147 | st.end(); 148 | return; 149 | } 150 | 151 | const output = JSON.parse(solc.lowlevel.compileSingle('contract x { this is an invalid contract }')); 152 | if (semver.lt(solc.semver(), '0.1.4')) { 153 | st.ok(output.error.indexOf('Parser error: Expected identifier') !== -1); 154 | st.end(); 155 | return; 156 | } 157 | st.plan(3); 158 | st.ok('errors' in output); 159 | // Check if the ParserError exists, but allow others too 160 | st.ok(output.errors.length >= 1); 161 | for (const error in output.errors) { 162 | // Error should be something like: 163 | // ParserError 164 | // Error: Expected identifier 165 | // Parser error: Expected identifier 166 | if ( 167 | output.errors[error].indexOf('ParserError') !== -1 || 168 | output.errors[error].indexOf('Error: Expected identifier') !== -1 || 169 | output.errors[error].indexOf('Parser error: Expected identifier') !== -1 || 170 | output.errors[error].indexOf(': Expected identifier') !== -1 // 0.4.12 171 | ) { 172 | st.ok(true); 173 | } 174 | } 175 | st.end(); 176 | }); 177 | 178 | t.test('multiple files can be compiled (using lowlevel API)', function (st) { 179 | // <0.1.6 doesn't have this 180 | if (typeof solc.lowlevel.compileMulti !== 'function') { 181 | st.skip('Low-level compileMulti interface not implemented by this compiler version.'); 182 | st.end(); 183 | return; 184 | } 185 | 186 | const input = { 187 | 'a.sol': 'contract A { function f() public returns (uint) { return 7; } }', 188 | 'b.sol': 'import "a.sol"; contract B is A { function g() public { f(); } }' 189 | }; 190 | const output = JSON.parse(solc.lowlevel.compileMulti(JSON.stringify({ sources: input }))); 191 | const B = getBytecode(output, 'b.sol', 'B'); 192 | st.ok(typeof B === 'string'); 193 | st.ok(B.length > 0); 194 | const A = getBytecode(output, 'a.sol', 'A'); 195 | st.ok(typeof A === 'string'); 196 | st.ok(A.length > 0); 197 | st.end(); 198 | }); 199 | 200 | t.test('lazy-loading callback works (using lowlevel API)', function (st) { 201 | // <0.2.1 doesn't have this 202 | if (typeof solc.lowlevel.compileCallback !== 'function') { 203 | st.skip('Low-level compileCallback interface not implemented by this compiler version.'); 204 | st.end(); 205 | return; 206 | } 207 | 208 | const input = { 209 | 'b.sol': 'import "a.sol"; contract B is A { function g() public { f(); } }' 210 | }; 211 | function findImports (path) { 212 | if (path === 'a.sol') { 213 | return { contents: 'contract A { function f() public returns (uint) { return 7; } }' }; 214 | } else { 215 | return { error: 'File not found' }; 216 | } 217 | } 218 | const output = JSON.parse(solc.lowlevel.compileCallback(JSON.stringify({ sources: input }), 0, { import: findImports })); 219 | const B = getBytecode(output, 'b.sol', 'B'); 220 | st.ok(typeof B === 'string'); 221 | st.ok(B.length > 0); 222 | const A = getBytecode(output, 'a.sol', 'A'); 223 | st.ok(typeof A === 'string'); 224 | st.ok(A.length > 0); 225 | st.end(); 226 | }); 227 | 228 | t.test('lazy-loading callback works (with file not found) (using lowlevel API)', function (st) { 229 | // <0.2.1 doesn't have this 230 | if (typeof solc.lowlevel.compileCallback !== 'function') { 231 | st.skip('Low-level compileCallback interface not implemented by this compiler version.'); 232 | st.end(); 233 | return; 234 | } 235 | 236 | const input = { 237 | 'b.sol': 'import "a.sol"; contract B { function g() public { f(); } }' 238 | }; 239 | function findImports (path) { 240 | return { error: 'File not found' }; 241 | } 242 | const output = JSON.parse(solc.lowlevel.compileCallback(JSON.stringify({ sources: input }), 0, { import: findImports })); 243 | st.plan(3); 244 | st.ok('errors' in output); 245 | // Check if the ParserError exists, but allow others too 246 | st.ok(output.errors.length >= 1); 247 | for (const error in output.errors) { 248 | // Error should be something like: 249 | // cont.sol:1:1: ParserError: Source "lib.sol" not found: File not found 250 | // cont.sol:1:1: Error: Source "lib.sol" not found: File not found 251 | if (output.errors[error].indexOf('Error') !== -1 && output.errors[error].indexOf('File not found') !== -1) { 252 | st.ok(true); 253 | } else if (output.errors[error].indexOf('not found: File not found') !== -1) { 254 | // 0.4.12 had its own weird way: 255 | // b.sol:1:1: : Source "a.sol" not found: File not found 256 | st.ok(true); 257 | } 258 | } 259 | st.end(); 260 | }); 261 | 262 | t.test('lazy-loading callback works (with exception) (using lowlevel API)', function (st) { 263 | // <0.2.1 doesn't have this 264 | if (typeof solc.lowlevel.compileCallback !== 'function') { 265 | st.skip('Low-level compileCallback interface not implemented by this compiler version.'); 266 | st.end(); 267 | return; 268 | } 269 | 270 | const input = { 271 | 'b.sol': 'import "a.sol"; contract B { function g() public { f(); } }' 272 | }; 273 | function findImports (path) { 274 | throw new Error('Could not implement this interface properly...'); 275 | } 276 | st.throws(function () { 277 | solc.lowlevel.compileCallback(JSON.stringify({ sources: input }), 0, { import: findImports }); 278 | }, /^Error: Could not implement this interface properly.../); 279 | st.end(); 280 | }); 281 | 282 | t.test('lazy-loading callback fails properly (with invalid callback) (using lowlevel API)', function (st) { 283 | // <0.2.1 doesn't have this 284 | if (typeof solc.lowlevel.compileCallback !== 'function') { 285 | st.skip('Low-level compileCallback interface not implemented by this compiler version.'); 286 | st.end(); 287 | return; 288 | } 289 | 290 | const input = { 291 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 292 | }; 293 | st.throws(function () { 294 | solc.lowlevel.compileCallback(JSON.stringify({ sources: input }), 0, 'this isn\'t a callback'); 295 | }, /Invalid callback object specified./); 296 | st.end(); 297 | }); 298 | 299 | t.test('file import without lazy-loading callback fails properly (using lowlevel API)', function (st) { 300 | // <0.2.1 doesn't have this 301 | if (typeof solc.lowlevel.compileCallback !== 'function') { 302 | st.skip('Low-level compileCallback interface not implemented by this compiler version.'); 303 | st.end(); 304 | return; 305 | } 306 | 307 | const input = { 308 | 'b.sol': 'import "a.sol"; contract B is A { function g() public { f(); } }' 309 | }; 310 | const output = JSON.parse(solc.lowlevel.compileCallback(JSON.stringify({ sources: input }))); 311 | st.plan(3); 312 | st.ok('errors' in output); 313 | // Check if the ParserError exists, but allow others too 314 | st.ok(output.errors.length >= 1); 315 | for (const error in output.errors) { 316 | // Error should be something like: 317 | // cont.sol:1:1: ParserError: Source "lib.sol" not found: File import callback not supported 318 | // cont.sol:1:1: Error: Source "lib.sol" not found: File import callback not supported 319 | if (output.errors[error].indexOf('Error') !== -1 && output.errors[error].indexOf('File import callback not supported') !== -1) { 320 | st.ok(true); 321 | } else if (output.errors[error].indexOf('not found: File import callback not supported') !== -1) { 322 | // 0.4.12 had its own weird way: 323 | // b.sol:1:1: : Source "a.sol" not found: File import callback not supported 324 | st.ok(true); 325 | } 326 | } 327 | st.end(); 328 | }); 329 | 330 | t.test('compiling standard JSON (using lowlevel API)', function (st) { 331 | if (typeof solc.lowlevel.compileStandard !== 'function') { 332 | st.skip('Low-level compileStandard interface not implemented by this compiler version.'); 333 | st.end(); 334 | return; 335 | } 336 | 337 | const input = { 338 | language: 'Solidity', 339 | settings: { 340 | outputSelection: { 341 | '*': { 342 | '*': ['evm.bytecode'] 343 | } 344 | } 345 | }, 346 | sources: { 347 | 'a.sol': { 348 | content: 'contract A { function f() public returns (uint) { return 7; } }' 349 | }, 350 | 'b.sol': { 351 | content: 'import "a.sol"; contract B is A { function g() public { f(); } }' 352 | } 353 | } 354 | }; 355 | 356 | function bytecodeExists (output, fileName, contractName) { 357 | try { 358 | return output.contracts[fileName][contractName].evm.bytecode.object.length > 0; 359 | } catch (e) { 360 | return false; 361 | } 362 | } 363 | 364 | const output = JSON.parse(solc.lowlevel.compileStandard(JSON.stringify(input))); 365 | st.ok(bytecodeExists(output, 'a.sol', 'A')); 366 | st.ok(bytecodeExists(output, 'b.sol', 'B')); 367 | st.end(); 368 | }); 369 | 370 | t.test('invalid source code fails properly with standard JSON (using lowlevel API)', function (st) { 371 | // TODO: try finding an example which doesn't crash it? 372 | if (semver.eq(solc.semver(), '0.4.11')) { 373 | st.skip('Skipping on broken compiler version'); 374 | st.end(); 375 | return; 376 | } 377 | 378 | if (typeof solc.lowlevel.compileStandard !== 'function') { 379 | st.skip('Low-level compileStandard interface not implemented by this compiler version.'); 380 | st.end(); 381 | return; 382 | } 383 | 384 | const input = { 385 | language: 'Solidity', 386 | settings: { 387 | outputSelection: { 388 | '*': { 389 | '*': ['evm.bytecode'] 390 | } 391 | } 392 | }, 393 | sources: { 394 | 'x.sol': { 395 | content: 'contract x { this is an invalid contract }' 396 | } 397 | } 398 | }; 399 | const output = JSON.parse(solc.lowlevel.compileStandard(JSON.stringify(input))); 400 | st.plan(3); 401 | st.ok('errors' in output); 402 | st.ok(output.errors.length >= 1); 403 | // Check if the ParserError exists, but allow others too 404 | for (const error in output.errors) { 405 | if (output.errors[error].type === 'ParserError') { 406 | st.ok(true); 407 | } 408 | } 409 | st.end(); 410 | }); 411 | 412 | t.test('compiling standard JSON (with callback) (using lowlevel API)', function (st) { 413 | if (typeof solc.lowlevel.compileStandard !== 'function') { 414 | st.skip('Low-level compileStandard interface not implemented by this compiler version.'); 415 | st.end(); 416 | return; 417 | } 418 | 419 | const input = { 420 | language: 'Solidity', 421 | settings: { 422 | outputSelection: { 423 | '*': { 424 | '*': ['evm.bytecode'] 425 | } 426 | } 427 | }, 428 | sources: { 429 | 'b.sol': { 430 | content: 'import "a.sol"; contract B is A { function g() public { f(); } }' 431 | } 432 | } 433 | }; 434 | 435 | function findImports (path) { 436 | if (path === 'a.sol') { 437 | return { contents: 'contract A { function f() public returns (uint) { return 7; } }' }; 438 | } else { 439 | return { error: 'File not found' }; 440 | } 441 | } 442 | 443 | function bytecodeExists (output, fileName, contractName) { 444 | try { 445 | return output.contracts[fileName][contractName].evm.bytecode.object.length > 0; 446 | } catch (e) { 447 | return false; 448 | } 449 | } 450 | 451 | const output = JSON.parse(solc.lowlevel.compileStandard(JSON.stringify(input), { import: findImports })); 452 | st.ok(bytecodeExists(output, 'a.sol', 'A')); 453 | st.ok(bytecodeExists(output, 'b.sol', 'B')); 454 | st.end(); 455 | }); 456 | 457 | t.test('compiling standard JSON (single file)', function (st) { 458 | const input = { 459 | language: 'Solidity', 460 | settings: { 461 | outputSelection: { 462 | '*': { 463 | '*': ['evm.bytecode', 'evm.gasEstimates'] 464 | } 465 | } 466 | }, 467 | sources: { 468 | 'c.sol': { 469 | content: 'contract C { function g() public { } function h() internal {} }' 470 | } 471 | } 472 | }; 473 | 474 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 475 | st.ok(expectNoError(output)); 476 | const C = getBytecodeStandard(output, 'c.sol', 'C'); 477 | st.ok(typeof C === 'string'); 478 | st.ok(C.length > 0); 479 | const CGas = getGasEstimate(output, 'c.sol', 'C'); 480 | st.ok(typeof CGas === 'object'); 481 | st.ok(typeof CGas.creation === 'object'); 482 | st.ok(typeof CGas.creation.codeDepositCost === 'string'); 483 | st.ok(typeof CGas.external === 'object'); 484 | st.ok(typeof CGas.external['g()'] === 'string'); 485 | st.ok(typeof CGas.internal === 'object'); 486 | st.ok(typeof CGas.internal['h()'] === 'string'); 487 | st.end(); 488 | }); 489 | 490 | t.test('compiling standard JSON (multiple files)', function (st) { 491 | // <0.1.6 doesn't have this 492 | if (!solc.features.multipleInputs) { 493 | st.skip('Not supported by solc'); 494 | st.end(); 495 | return; 496 | } 497 | 498 | const input = { 499 | language: 'Solidity', 500 | settings: { 501 | outputSelection: { 502 | '*': { 503 | '*': ['evm.bytecode', 'evm.gasEstimates'] 504 | } 505 | } 506 | }, 507 | sources: { 508 | 'a.sol': { 509 | content: 'contract A { function f() public returns (uint) { return 7; } }' 510 | }, 511 | 'b.sol': { 512 | content: 'import "a.sol"; contract B is A { function g() public { f(); } function h() internal {} }' 513 | } 514 | } 515 | }; 516 | 517 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 518 | st.ok(expectNoError(output)); 519 | const B = getBytecodeStandard(output, 'b.sol', 'B'); 520 | st.ok(typeof B === 'string'); 521 | st.ok(B.length > 0); 522 | st.ok(Object.keys(linker.findLinkReferences(B)).length === 0); 523 | const BGas = getGasEstimate(output, 'b.sol', 'B'); 524 | st.ok(typeof BGas === 'object'); 525 | st.ok(typeof BGas.creation === 'object'); 526 | st.ok(typeof BGas.creation.codeDepositCost === 'string'); 527 | st.ok(typeof BGas.external === 'object'); 528 | st.ok(typeof BGas.external['g()'] === 'string'); 529 | st.ok(typeof BGas.internal === 'object'); 530 | st.ok(typeof BGas.internal['h()'] === 'string'); 531 | const A = getBytecodeStandard(output, 'a.sol', 'A'); 532 | st.ok(typeof A === 'string'); 533 | st.ok(A.length > 0); 534 | st.end(); 535 | }); 536 | 537 | t.test('compiling standard JSON (abstract contract)', function (st) { 538 | // <0.1.6 doesn't have this 539 | if (!solc.features.multipleInputs) { 540 | st.skip('Not supported by solc'); 541 | st.end(); 542 | return; 543 | } 544 | 545 | const isVersion6 = semver.gt(solc.semver(), '0.5.99'); 546 | let source; 547 | if (isVersion6) { 548 | source = 'abstract contract C { function f() public virtual; }'; 549 | } else { 550 | source = 'contract C { function f() public; }'; 551 | } 552 | 553 | const input = { 554 | language: 'Solidity', 555 | settings: { 556 | outputSelection: { 557 | '*': { 558 | '*': ['evm.bytecode', 'evm.gasEstimates'] 559 | } 560 | } 561 | }, 562 | sources: { 563 | 'c.sol': { 564 | content: source 565 | } 566 | } 567 | }; 568 | 569 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 570 | st.ok(expectNoError(output)); 571 | const C = getBytecodeStandard(output, 'c.sol', 'C'); 572 | st.ok(typeof C === 'string'); 573 | st.ok(C.length === 0); 574 | st.end(); 575 | }); 576 | 577 | t.test('compiling standard JSON (with imports)', function (st) { 578 | // <0.2.1 doesn't have this 579 | if (!solc.features.importCallback) { 580 | st.skip('Not supported by solc'); 581 | st.end(); 582 | return; 583 | } 584 | 585 | const input = { 586 | language: 'Solidity', 587 | settings: { 588 | outputSelection: { 589 | '*': { 590 | '*': ['evm.bytecode'] 591 | } 592 | } 593 | }, 594 | sources: { 595 | 'b.sol': { 596 | content: 'import "a.sol"; contract B is A { function g() public { f(); } }' 597 | } 598 | } 599 | }; 600 | 601 | function findImports (path) { 602 | if (path === 'a.sol') { 603 | return { contents: 'contract A { function f() public returns (uint) { return 7; } }' }; 604 | } else { 605 | return { error: 'File not found' }; 606 | } 607 | } 608 | 609 | const output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports })); 610 | st.ok(expectNoError(output)); 611 | const A = getBytecodeStandard(output, 'a.sol', 'A'); 612 | st.ok(typeof A === 'string'); 613 | st.ok(A.length > 0); 614 | const B = getBytecodeStandard(output, 'b.sol', 'B'); 615 | st.ok(typeof B === 'string'); 616 | st.ok(B.length > 0); 617 | st.ok(Object.keys(linker.findLinkReferences(B)).length === 0); 618 | st.end(); 619 | }); 620 | 621 | t.test('compiling standard JSON (using libraries)', function (st) { 622 | // 0.4.0 has a bug with libraries 623 | if (semver.eq(solc.semver(), '0.4.0')) { 624 | st.skip('Skipping on broken compiler version'); 625 | st.end(); 626 | return; 627 | } 628 | 629 | // <0.1.6 doesn't have this 630 | if (!solc.features.multipleInputs) { 631 | st.skip('Not supported by solc'); 632 | st.end(); 633 | return; 634 | } 635 | 636 | const input = { 637 | language: 'Solidity', 638 | settings: { 639 | libraries: { 640 | 'lib.sol': { 641 | L: '0x4200000000000000000000000000000000000001' 642 | } 643 | }, 644 | outputSelection: { 645 | '*': { 646 | '*': ['evm.bytecode'] 647 | } 648 | } 649 | }, 650 | sources: { 651 | 'lib.sol': { 652 | content: 'library L { function f() public returns (uint) { return 7; } }' 653 | }, 654 | 'a.sol': { 655 | content: 'import "lib.sol"; contract A { function g() public { L.f(); } }' 656 | } 657 | } 658 | }; 659 | 660 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 661 | st.ok(expectNoError(output)); 662 | const A = getBytecodeStandard(output, 'a.sol', 'A'); 663 | st.ok(typeof A === 'string'); 664 | st.ok(A.length > 0); 665 | st.ok(Object.keys(linker.findLinkReferences(A)).length === 0); 666 | const L = getBytecodeStandard(output, 'lib.sol', 'L'); 667 | st.ok(typeof L === 'string'); 668 | st.ok(L.length > 0); 669 | st.end(); 670 | }); 671 | 672 | t.test('compiling standard JSON (with warning >=0.4.0)', function (st) { 673 | // In 0.4.0 "pragma solidity" was added. Not including it is a warning. 674 | if (semver.lt(solc.semver(), '0.4.0')) { 675 | st.skip('Not supported by solc'); 676 | st.end(); 677 | return; 678 | } 679 | 680 | const input = { 681 | language: 'Solidity', 682 | settings: { 683 | outputSelection: { 684 | '*': { 685 | '*': ['evm.bytecode'] 686 | } 687 | } 688 | }, 689 | sources: { 690 | 'c.sol': { 691 | content: 'contract C { function f() public { } }' 692 | } 693 | } 694 | }; 695 | 696 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 697 | st.ok(expectError(output, 'Warning', 'Source file does not specify required compiler version!')); 698 | st.end(); 699 | }); 700 | 701 | t.test('compiling standard JSON (using libraries) (using lowlevel API)', function (st) { 702 | // 0.4.0 has a bug with libraries 703 | if (semver.eq(solc.semver(), '0.4.0')) { 704 | st.skip('Skipping on broken compiler version'); 705 | st.end(); 706 | return; 707 | } 708 | 709 | if (typeof solc.lowlevel.compileStandard !== 'function') { 710 | st.skip('Low-level compileStandard interface not implemented by this compiler version.'); 711 | st.end(); 712 | return; 713 | } 714 | 715 | const input = { 716 | language: 'Solidity', 717 | settings: { 718 | libraries: { 719 | 'lib.sol': { 720 | L: '0x4200000000000000000000000000000000000001' 721 | } 722 | }, 723 | outputSelection: { 724 | '*': { 725 | '*': ['evm.bytecode'] 726 | } 727 | } 728 | }, 729 | sources: { 730 | 'lib.sol': { 731 | content: 'library L { function f() public returns (uint) { return 7; } }' 732 | }, 733 | 'a.sol': { 734 | content: 'import "lib.sol"; contract A { function g() public { L.f(); } }' 735 | } 736 | } 737 | }; 738 | 739 | const output = JSON.parse(solc.lowlevel.compileStandard(JSON.stringify(input))); 740 | st.ok(expectNoError(output)); 741 | const A = getBytecodeStandard(output, 'a.sol', 'A'); 742 | st.ok(typeof A === 'string'); 743 | st.ok(A.length > 0); 744 | st.ok(Object.keys(linker.findLinkReferences(A)).length === 0); 745 | const L = getBytecodeStandard(output, 'lib.sol', 'L'); 746 | st.ok(typeof L === 'string'); 747 | st.ok(L.length > 0); 748 | st.end(); 749 | }); 750 | 751 | t.test('compiling standard JSON (invalid JSON)', function (st) { 752 | const output = JSON.parse(solc.compile('{invalid')); 753 | // TODO: change wrapper to output matching error 754 | st.ok( 755 | expectError(output, 'JSONError', 'Line 1, Column 2\n Missing \'}\' or object member name') || 756 | expectError(output, 'JSONError', 'Invalid JSON supplied:') || 757 | expectError(output, 'JSONError', 'parse error at line 1, column 2: syntax error while parsing object key - invalid literal; last read: \'{i\'; expected string literal') 758 | ); 759 | st.end(); 760 | }); 761 | 762 | t.test('compiling standard JSON (invalid language)', function (st) { 763 | const output = JSON.parse(solc.compile('{"language":"InvalidSolidity","sources":{"cont.sol":{"content":""}}}')); 764 | st.ok(expectError(output, 'JSONError', 'supported as a language.') && expectError(output, 'JSONError', '"Solidity"')); 765 | st.end(); 766 | }); 767 | 768 | t.test('compiling standard JSON (no sources)', function (st) { 769 | const output = JSON.parse(solc.compile('{"language":"Solidity"}')); 770 | st.ok(expectError(output, 'JSONError', 'No input sources specified.')); 771 | st.end(); 772 | }); 773 | 774 | t.test('compiling standard JSON (multiple sources on old compiler)', function (st) { 775 | const output = JSON.parse(solc.compile('{"language":"Solidity","sources":{"cont.sol":{"content":"import \\"lib.sol\\";"},"lib.sol":{"content":""}}}')); 776 | if (solc.features.multipleInputs) { 777 | st.ok(expectNoError(output)); 778 | } else { 779 | st.ok(expectError(output, 'JSONError', 'Multiple sources provided, but compiler only supports single input.') || expectError(output, 'Parser error', 'Parser error: Source not found.')); 780 | } 781 | st.end(); 782 | }); 783 | 784 | t.test('compiling standard JSON (file names containing symbols)', function (st) { 785 | const input = { 786 | language: 'Solidity', 787 | settings: { 788 | outputSelection: { 789 | '*': { 790 | '*': ['evm.bytecode'] 791 | } 792 | } 793 | }, 794 | sources: { 795 | '!@#$%^&*()_+-=[]{}\\|"\';:~`<>,.?/': { 796 | content: 'contract C {}' 797 | } 798 | } 799 | }; 800 | 801 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 802 | st.ok(expectNoError(output)); 803 | const C = getBytecodeStandard(output, '!@#$%^&*()_+-=[]{}\\|"\';:~`<>,.?/', 'C'); 804 | st.ok(typeof C === 'string'); 805 | st.ok(C.length > 0); 806 | st.end(); 807 | }); 808 | 809 | t.test('compiling standard JSON (file names containing multiple semicolons)', function (st) { 810 | const input = { 811 | language: 'Solidity', 812 | settings: { 813 | outputSelection: { 814 | '*': { 815 | '*': ['evm.bytecode'] 816 | } 817 | } 818 | }, 819 | sources: { 820 | 'a:b:c:d:e:f:G.sol': { 821 | content: 'contract G {}' 822 | } 823 | } 824 | }; 825 | 826 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 827 | st.ok(expectNoError(output)); 828 | const G = getBytecodeStandard(output, 'a:b:c:d:e:f:G.sol', 'G'); 829 | st.ok(typeof G === 'string'); 830 | st.ok(G.length > 0); 831 | st.end(); 832 | }); 833 | }); 834 | }); 835 | 836 | // Only run on the latest version. 837 | if (versionText === 'latest' && !noRemoteVersions) { 838 | tape('Loading Legacy Versions', function (t) { 839 | t.test('loading remote version - development snapshot', function (st) { 840 | // getting the development snapshot 841 | st.plan(2); 842 | solc.loadRemoteVersion('latest', function (err, solcSnapshot) { 843 | if (err) { 844 | st.plan(1); 845 | st.skip('Network error - skipping remote loading test'); 846 | st.end(); 847 | return; 848 | } 849 | const input = { 850 | language: 'Solidity', 851 | settings: { 852 | outputSelection: { 853 | '*': { 854 | '*': ['evm.bytecode'] 855 | } 856 | } 857 | }, 858 | sources: { 859 | 'cont.sol': { 860 | content: 'contract x { function g() public {} }' 861 | } 862 | } 863 | }; 864 | const output = JSON.parse(solcSnapshot.compile(JSON.stringify(input))); 865 | const x = getBytecodeStandard(output, 'cont.sol', 'x'); 866 | st.ok(typeof x === 'string'); 867 | st.ok(x.length > 0); 868 | }); 869 | }); 870 | }); 871 | } 872 | } 873 | 874 | runTests(solc, 'latest'); 875 | 876 | if (!noRemoteVersions) { 877 | // New compiler interface features 0.1.6, 0.2.1, 0.4.11, 0.5.0, etc. 878 | // 0.4.0 added pragmas (used in tests above) 879 | const versions = [ 880 | 'v0.1.1+commit.6ff4cd6', 881 | 'v0.1.6+commit.d41f8b7', 882 | 'v0.2.0+commit.4dc2445', 883 | 'v0.2.1+commit.91a6b35', 884 | 'v0.3.6+commit.3fc68da', 885 | 'v0.4.0+commit.acd334c9', 886 | 'v0.4.9+commit.364da425', 887 | 'v0.4.10+commit.f0d539ae', 888 | 'v0.4.11+commit.68ef5810', 889 | 'v0.4.12+commit.194ff033', 890 | 'v0.4.19+commit.c4cbbb05', 891 | 'v0.4.20+commit.3155dd80', 892 | 'v0.4.26+commit.4563c3fc' 893 | ]; 894 | for (let version in versions) { 895 | version = versions[version]; 896 | // NOTE: The temporary directory will be removed on process exit. 897 | const tempDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solc-js-compiler-test-' }).name; 898 | execSync(`curl -L -o ${tempDir}/${version}.js https://binaries.soliditylang.org/bin/soljson-${version}.js`); 899 | const newSolc = wrapper(require(`${tempDir}/${version}.js`)); 900 | runTests(newSolc, version); 901 | } 902 | } 903 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver'; 2 | 3 | import('./linker'); 4 | import('./translate'); 5 | import('./compiler'); 6 | import('./smtcallback'); 7 | import('./smtchecker'); 8 | import('./abi'); 9 | 10 | // The CLI doesn't support Node 4 11 | if (semver.gte(process.version, '5.0.0')) { 12 | import('./cli'); 13 | } 14 | -------------------------------------------------------------------------------- /test/linker.ts: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import linker from '../linker'; 3 | 4 | tape('Link references', function (t) { 5 | t.test('Empty bytecode', function (st) { 6 | st.deepEqual(linker.findLinkReferences(''), {}); 7 | st.end(); 8 | }); 9 | 10 | t.test('No references', function (st) { 11 | st.deepEqual(linker.findLinkReferences('6060604052341561000f57600080fd'), {}); 12 | st.end(); 13 | }); 14 | 15 | t.test('One reference', function (st) { 16 | const bytecode = '6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029'; 17 | st.deepEqual(linker.findLinkReferences(bytecode), { 'lib2.sol:L': [{ start: 116, length: 20 }] }); 18 | st.end(); 19 | }); 20 | 21 | t.test('Two references', function (st) { 22 | const bytecode = '6060604052341561000f57600080fd5b61011a8061001e6000396000f30060606040526004361060255763ffffffff60e060020a60003504166326121ff08114602a575b600080fd5b3415603457600080fd5b603a603c565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff1660e060020a02815260040160006040518083038186803b1515608157600080fd5b6102c65a03f41515609157600080fd5b50505073__linkref.sol:Lx________________________6326121ff06040518163ffffffff1660e060020a02815260040160006040518083038186803b151560d957600080fd5b6102c65a03f4151560e957600080fd5b5050505600a165627a7a72305820fdfb8eab411d7bc86d7dfbb0c985c30bebf1cc105dc5b807291551b3d5aa29d90029'; 23 | st.deepEqual( 24 | linker.findLinkReferences(bytecode), 25 | { 'lib2.sol:L': [{ start: 92, length: 20 }], 'linkref.sol:Lx': [{ start: 180, length: 20 }] } 26 | ); 27 | st.end(); 28 | }); 29 | 30 | t.test('Library name with leading underscore', function (st) { 31 | const bytecode = '6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:_L___________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a7230582089689827bbf0b7dc385ffcb4b1deb9f9e61741f61f89b4af65f806ff2b0d73470029'; 32 | st.deepEqual( 33 | linker.findLinkReferences(bytecode), 34 | { 'lib2.sol:_L': [{ start: 116, length: 20 }] } 35 | ); 36 | st.end(); 37 | }); 38 | 39 | t.test('Library name with leading underscore (without fqdn)', function (st) { 40 | const bytecode = '6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73___L____________________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a7230582089689827bbf0b7dc385ffcb4b1deb9f9e61741f61f89b4af65f806ff2b0d73470029'; 41 | st.deepEqual( 42 | linker.findLinkReferences(bytecode), 43 | { _L: [{ start: 116, length: 20 }] } 44 | ); 45 | st.end(); 46 | }); 47 | 48 | t.test('Library name with underscore in the name', function (st) { 49 | const bytecode = '6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L_L__________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058205cb324a27452cc7f8894a57cb0e3ddce2dce0c423e4fc993a3dd51287abd49110029'; 50 | st.deepEqual( 51 | linker.findLinkReferences(bytecode), 52 | { 'lib2.sol:L_L': [{ start: 116, length: 20 }] } 53 | ); 54 | st.end(); 55 | }); 56 | 57 | // Note: this is a case the reference finder cannot properly handle as there's no way to tell 58 | t.test('Library name with trailing underscore', function (st) { 59 | const bytecode = '6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a7230582058e61511a603707222cfa83fd3ae4269f94eb86513cb9042cf0b44877403d85c0029'; 60 | st.deepEqual( 61 | linker.findLinkReferences(bytecode), 62 | { 'lib2.sol:L': [{ start: 116, length: 20 }] } 63 | ); 64 | st.end(); 65 | }); 66 | 67 | t.test('Invalid input (too short)', function (st) { 68 | const bytecode = '6060604052341561000____66606060606060'; 69 | st.deepEqual( 70 | linker.findLinkReferences(bytecode), 71 | {} 72 | ); 73 | st.end(); 74 | }); 75 | 76 | t.test('Invalid input (1 byte short)', function (st) { 77 | const bytecode = '6060604052341561000__lib2.sol:L___________________________66606060606060'; 78 | st.deepEqual( 79 | linker.findLinkReferences(bytecode), 80 | {} 81 | ); 82 | st.end(); 83 | }); 84 | 85 | t.test('Two references with same library name', function (st) { 86 | const bytecode = '6060604052341561000f57600080fd5b61011a8061001e6000396000f30060606040526004361060255763ffffffff60e060020a60003504166326121ff08114602a575b600080fd5b3415603457600080fd5b603a603c565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff1660e060020a02815260040160006040518083038186803b1515608157600080fd5b6102c65a03f41515609157600080fd5b50505073__lib2.sol:L____________________________6326121ff06040518163ffffffff1660e060020a02815260040160006040518083038186803b151560d957600080fd5b6102c65a03f4151560e957600080fd5b5050505600a165627a7a72305820fdfb8eab411d7bc86d7dfbb0c985c30bebf1cc105dc5b807291551b3d5aa29d90029'; 87 | st.deepEqual( 88 | linker.findLinkReferences(bytecode), 89 | { 'lib2.sol:L': [{ start: 92, length: 20 }, { start: 180, length: 20 }] } 90 | ); 91 | st.end(); 92 | }); 93 | }); 94 | 95 | tape('Linking', function (t) { 96 | t.test('link properly', function (st) { 97 | /* 98 | 'lib.sol': 'library L { function f() public returns (uint) { return 7; } }', 99 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 100 | */ 101 | let bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__lib.sol:L_____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a72305820ea2f6353179c181d7162544d637b7fe2d9e8da9803a0e2d9eafc2188d1d59ee30029'; 102 | bytecode = linker.linkBytecode(bytecode, { 'lib.sol:L': '0x123456' }); 103 | st.ok(bytecode.indexOf('_') < 0); 104 | st.end(); 105 | }); 106 | 107 | t.test('link properly with two-level configuration (from standard JSON)', function (st) { 108 | /* 109 | 'lib.sol': 'library L { function f() public returns (uint) { return 7; } }', 110 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 111 | */ 112 | let bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__lib.sol:L_____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a72305820ea2f6353179c181d7162544d637b7fe2d9e8da9803a0e2d9eafc2188d1d59ee30029'; 113 | bytecode = linker.linkBytecode(bytecode, { 'lib.sol': { L: '0x123456' } }); 114 | st.ok(bytecode.indexOf('_') < 0); 115 | st.end(); 116 | }); 117 | 118 | t.test('linker to fail with missing library', function (st) { 119 | /* 120 | 'lib.sol': 'library L { function f() public returns (uint) { return 7; } }', 121 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 122 | */ 123 | let bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__lib.sol:L_____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a72305820ea2f6353179c181d7162544d637b7fe2d9e8da9803a0e2d9eafc2188d1d59ee30029'; 124 | bytecode = linker.linkBytecode(bytecode, { }); 125 | st.ok(bytecode.indexOf('_') >= 0); 126 | st.end(); 127 | }); 128 | 129 | t.test('linker to fail with invalid address', function (st) { 130 | /* 131 | 'lib.sol': 'library L { function f() public returns (uint) { return 7; } }', 132 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 133 | */ 134 | const bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__lib.sol:L_____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a72305820ea2f6353179c181d7162544d637b7fe2d9e8da9803a0e2d9eafc2188d1d59ee30029'; 135 | st.throws(function () { 136 | linker.linkBytecode(bytecode, { 'lib.sol:L': '' }); 137 | }); 138 | st.end(); 139 | }); 140 | 141 | t.test('linker properly with truncated library name', function (st) { 142 | /* 143 | 'lib.sol': 'library L1234567890123456789012345678901234567890 { function f() public returns (uint) { return 7; } }', 144 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L1234567890123456789012345678901234567890.f(); } }' 145 | */ 146 | let bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__lib.sol:L123456789012345678901234567__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a723058209f88ff686bd8ceb0fc08853dc1332d5ff81dbcf5af3a1e9aa366828091761f8c0029'; 147 | bytecode = linker.linkBytecode(bytecode, { 'lib.sol:L1234567890123456789012345678901234567890': '0x123456' }); 148 | st.ok(bytecode.indexOf('_') < 0); 149 | st.end(); 150 | }); 151 | 152 | t.test('hashed placeholder', function (st) { 153 | let bytecode = '6060604052341561000__$cb901161e812ceb78cfe30ca65050c4337$__66606060606060'; 154 | bytecode = linker.linkBytecode(bytecode, { 'lib2.sol:L': '0x123456' }); 155 | st.equal(bytecode, '6060604052341561000000000000000000000000000000000000012345666606060606060'); 156 | st.end(); 157 | }); 158 | 159 | t.test('link properly when library doesn\'t have colon in name', function (st) { 160 | /* 161 | 'lib.sol': 'library L { function f() public returns (uint) { return 7; } }', 162 | 'cont.sol': 'import "lib.sol"; contract x { function g() public { L.f(); } }' 163 | */ 164 | let bytecode = '608060405234801561001057600080fd5b5061011f806100206000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063e2179b8e146044575b600080fd5b348015604f57600080fd5b5060566058565b005b73__libName_______________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b8101908080519060200190929190505050505600a165627a7a72305820ea2f6353179c181d7162544d637b7fe2d9e8da9803a0e2d9eafc2188d1d59ee30029'; 165 | bytecode = linker.linkBytecode(bytecode, { libName: '0x123456' }); 166 | st.ok(bytecode.indexOf('_') < 0); 167 | st.end(); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /test/resources/fixtureAsmJson.json: -------------------------------------------------------------------------------- 1 | { 2 | ".code" : 3 | [ 4 | { 5 | "begin" : 0, 6 | "end" : 135, 7 | "name" : "PUSH", 8 | "value" : "80" 9 | }, 10 | { 11 | "begin" : 0, 12 | "end" : 135, 13 | "name" : "PUSH", 14 | "value" : "40" 15 | }, 16 | { 17 | "begin" : 0, 18 | "end" : 135, 19 | "name" : "MSTORE" 20 | }, 21 | { 22 | "begin" : 0, 23 | "end" : 135, 24 | "name" : "CALLVALUE" 25 | }, 26 | { 27 | "begin" : 8, 28 | "end" : 17, 29 | "name" : "DUP1" 30 | }, 31 | { 32 | "begin" : 5, 33 | "end" : 7, 34 | "name" : "ISZERO" 35 | }, 36 | { 37 | "begin" : 5, 38 | "end" : 7, 39 | "name" : "PUSH [tag]", 40 | "value" : "1" 41 | }, 42 | { 43 | "begin" : 5, 44 | "end" : 7, 45 | "name" : "JUMPI" 46 | }, 47 | { 48 | "begin" : 30, 49 | "end" : 31, 50 | "name" : "PUSH", 51 | "value" : "0" 52 | }, 53 | { 54 | "begin" : 27, 55 | "end" : 28, 56 | "name" : "DUP1" 57 | }, 58 | { 59 | "begin" : 20, 60 | "end" : 32, 61 | "name" : "REVERT" 62 | }, 63 | { 64 | "begin" : 5, 65 | "end" : 7, 66 | "name" : "tag", 67 | "value" : "1" 68 | }, 69 | { 70 | "begin" : 5, 71 | "end" : 7, 72 | "name" : "JUMPDEST" 73 | }, 74 | { 75 | "begin" : 0, 76 | "end" : 135, 77 | "name" : "POP" 78 | }, 79 | { 80 | "begin" : 0, 81 | "end" : 135, 82 | "name" : "PUSH #[$]", 83 | "value" : "0000000000000000000000000000000000000000000000000000000000000000" 84 | }, 85 | { 86 | "begin" : 0, 87 | "end" : 135, 88 | "name" : "DUP1" 89 | }, 90 | { 91 | "begin" : 0, 92 | "end" : 135, 93 | "name" : "PUSH [$]", 94 | "value" : "0000000000000000000000000000000000000000000000000000000000000000" 95 | }, 96 | { 97 | "begin" : 0, 98 | "end" : 135, 99 | "name" : "PUSH", 100 | "value" : "0" 101 | }, 102 | { 103 | "begin" : 0, 104 | "end" : 135, 105 | "name" : "CODECOPY" 106 | }, 107 | { 108 | "begin" : 0, 109 | "end" : 135, 110 | "name" : "PUSH", 111 | "value" : "0" 112 | }, 113 | { 114 | "begin" : 0, 115 | "end" : 135, 116 | "name" : "RETURN" 117 | } 118 | ], 119 | ".data" : 120 | { 121 | "0" : 122 | { 123 | ".auxdata" : "a165627a7a72305820d28aa368c3d228b3e3e38715067dcb2be2d066ecc02e7135b3bdd76e7603692e0029", 124 | ".code" : 125 | [ 126 | { 127 | "begin" : 0, 128 | "end" : 135, 129 | "name" : "PUSH", 130 | "value" : "80" 131 | }, 132 | { 133 | "begin" : 0, 134 | "end" : 135, 135 | "name" : "PUSH", 136 | "value" : "40" 137 | }, 138 | { 139 | "begin" : 0, 140 | "end" : 135, 141 | "name" : "MSTORE" 142 | }, 143 | { 144 | "begin" : 0, 145 | "end" : 135, 146 | "name" : "CALLVALUE" 147 | }, 148 | { 149 | "begin" : 8, 150 | "end" : 17, 151 | "name" : "DUP1" 152 | }, 153 | { 154 | "begin" : 5, 155 | "end" : 7, 156 | "name" : "ISZERO" 157 | }, 158 | { 159 | "begin" : 5, 160 | "end" : 7, 161 | "name" : "PUSH [tag]", 162 | "value" : "1" 163 | }, 164 | { 165 | "begin" : 5, 166 | "end" : 7, 167 | "name" : "JUMPI" 168 | }, 169 | { 170 | "begin" : 30, 171 | "end" : 31, 172 | "name" : "PUSH", 173 | "value" : "0" 174 | }, 175 | { 176 | "begin" : 27, 177 | "end" : 28, 178 | "name" : "DUP1" 179 | }, 180 | { 181 | "begin" : 20, 182 | "end" : 32, 183 | "name" : "REVERT" 184 | }, 185 | { 186 | "begin" : 5, 187 | "end" : 7, 188 | "name" : "tag", 189 | "value" : "1" 190 | }, 191 | { 192 | "begin" : 5, 193 | "end" : 7, 194 | "name" : "JUMPDEST" 195 | }, 196 | { 197 | "begin" : 0, 198 | "end" : 135, 199 | "name" : "POP" 200 | }, 201 | { 202 | "begin" : 0, 203 | "end" : 135, 204 | "name" : "PUSH", 205 | "value" : "4" 206 | }, 207 | { 208 | "begin" : 0, 209 | "end" : 135, 210 | "name" : "CALLDATASIZE" 211 | }, 212 | { 213 | "begin" : 0, 214 | "end" : 135, 215 | "name" : "LT" 216 | }, 217 | { 218 | "begin" : 0, 219 | "end" : 135, 220 | "name" : "PUSH [tag]", 221 | "value" : "2" 222 | }, 223 | { 224 | "begin" : 0, 225 | "end" : 135, 226 | "name" : "JUMPI" 227 | }, 228 | { 229 | "begin" : 0, 230 | "end" : 135, 231 | "name" : "PUSH", 232 | "value" : "0" 233 | }, 234 | { 235 | "begin" : 0, 236 | "end" : 135, 237 | "name" : "CALLDATALOAD" 238 | }, 239 | { 240 | "begin" : 0, 241 | "end" : 135, 242 | "name" : "PUSH", 243 | "value" : "100000000000000000000000000000000000000000000000000000000" 244 | }, 245 | { 246 | "begin" : 0, 247 | "end" : 135, 248 | "name" : "SWAP1" 249 | }, 250 | { 251 | "begin" : 0, 252 | "end" : 135, 253 | "name" : "DIV" 254 | }, 255 | { 256 | "begin" : 0, 257 | "end" : 135, 258 | "name" : "DUP1" 259 | }, 260 | { 261 | "begin" : 0, 262 | "end" : 135, 263 | "name" : "PUSH", 264 | "value" : "26121FF0" 265 | }, 266 | { 267 | "begin" : 0, 268 | "end" : 135, 269 | "name" : "EQ" 270 | }, 271 | { 272 | "begin" : 0, 273 | "end" : 135, 274 | "name" : "PUSH [tag]", 275 | "value" : "3" 276 | }, 277 | { 278 | "begin" : 0, 279 | "end" : 135, 280 | "name" : "JUMPI" 281 | }, 282 | { 283 | "begin" : 0, 284 | "end" : 135, 285 | "name" : "tag", 286 | "value" : "2" 287 | }, 288 | { 289 | "begin" : 0, 290 | "end" : 135, 291 | "name" : "JUMPDEST" 292 | }, 293 | { 294 | "begin" : 0, 295 | "end" : 135, 296 | "name" : "PUSH", 297 | "value" : "0" 298 | }, 299 | { 300 | "begin" : 0, 301 | "end" : 135, 302 | "name" : "DUP1" 303 | }, 304 | { 305 | "begin" : 0, 306 | "end" : 135, 307 | "name" : "REVERT" 308 | }, 309 | { 310 | "begin" : 63, 311 | "end" : 133, 312 | "name" : "tag", 313 | "value" : "3" 314 | }, 315 | { 316 | "begin" : 63, 317 | "end" : 133, 318 | "name" : "JUMPDEST" 319 | }, 320 | { 321 | "begin" : 63, 322 | "end" : 133, 323 | "name" : "PUSH [tag]", 324 | "value" : "4" 325 | }, 326 | { 327 | "begin" : 63, 328 | "end" : 133, 329 | "name" : "PUSH [tag]", 330 | "value" : "5" 331 | }, 332 | { 333 | "begin" : 63, 334 | "end" : 133, 335 | "name" : "JUMP", 336 | "value" : "[in]" 337 | }, 338 | { 339 | "begin" : 63, 340 | "end" : 133, 341 | "name" : "tag", 342 | "value" : "4" 343 | }, 344 | { 345 | "begin" : 63, 346 | "end" : 133, 347 | "name" : "JUMPDEST" 348 | }, 349 | { 350 | "begin" : 63, 351 | "end" : 133, 352 | "name" : "PUSH", 353 | "value" : "40" 354 | }, 355 | { 356 | "begin" : 63, 357 | "end" : 133, 358 | "name" : "MLOAD" 359 | }, 360 | { 361 | "begin" : 63, 362 | "end" : 133, 363 | "name" : "DUP1" 364 | }, 365 | { 366 | "begin" : 63, 367 | "end" : 133, 368 | "name" : "DUP3" 369 | }, 370 | { 371 | "begin" : 63, 372 | "end" : 133, 373 | "name" : "PUSH", 374 | "value" : "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" 375 | }, 376 | { 377 | "begin" : 63, 378 | "end" : 133, 379 | "name" : "NOT" 380 | }, 381 | { 382 | "begin" : 63, 383 | "end" : 133, 384 | "name" : "AND" 385 | }, 386 | { 387 | "begin" : 63, 388 | "end" : 133, 389 | "name" : "PUSH", 390 | "value" : "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" 391 | }, 392 | { 393 | "begin" : 63, 394 | "end" : 133, 395 | "name" : "NOT" 396 | }, 397 | { 398 | "begin" : 63, 399 | "end" : 133, 400 | "name" : "AND" 401 | }, 402 | { 403 | "begin" : 63, 404 | "end" : 133, 405 | "name" : "DUP2" 406 | }, 407 | { 408 | "begin" : 63, 409 | "end" : 133, 410 | "name" : "MSTORE" 411 | }, 412 | { 413 | "begin" : 63, 414 | "end" : 133, 415 | "name" : "PUSH", 416 | "value" : "20" 417 | }, 418 | { 419 | "begin" : 63, 420 | "end" : 133, 421 | "name" : "ADD" 422 | }, 423 | { 424 | "begin" : 63, 425 | "end" : 133, 426 | "name" : "SWAP2" 427 | }, 428 | { 429 | "begin" : 63, 430 | "end" : 133, 431 | "name" : "POP" 432 | }, 433 | { 434 | "begin" : 63, 435 | "end" : 133, 436 | "name" : "POP" 437 | }, 438 | { 439 | "begin" : 63, 440 | "end" : 133, 441 | "name" : "PUSH", 442 | "value" : "40" 443 | }, 444 | { 445 | "begin" : 63, 446 | "end" : 133, 447 | "name" : "MLOAD" 448 | }, 449 | { 450 | "begin" : 63, 451 | "end" : 133, 452 | "name" : "DUP1" 453 | }, 454 | { 455 | "begin" : 63, 456 | "end" : 133, 457 | "name" : "SWAP2" 458 | }, 459 | { 460 | "begin" : 63, 461 | "end" : 133, 462 | "name" : "SUB" 463 | }, 464 | { 465 | "begin" : 63, 466 | "end" : 133, 467 | "name" : "SWAP1" 468 | }, 469 | { 470 | "begin" : 63, 471 | "end" : 133, 472 | "name" : "RETURN" 473 | }, 474 | { 475 | "begin" : 63, 476 | "end" : 133, 477 | "name" : "tag", 478 | "value" : "5" 479 | }, 480 | { 481 | "begin" : 63, 482 | "end" : 133, 483 | "name" : "JUMPDEST" 484 | }, 485 | { 486 | "begin" : 92, 487 | "end" : 98, 488 | "name" : "PUSH", 489 | "value" : "0" 490 | }, 491 | { 492 | "begin" : 113, 493 | "end" : 128, 494 | "name" : "PUSH", 495 | "value" : "26121FF0" 496 | }, 497 | { 498 | "begin" : 113, 499 | "end" : 128, 500 | "name" : "PUSH", 501 | "value" : "100000000000000000000000000000000000000000000000000000000" 502 | }, 503 | { 504 | "begin" : 113, 505 | "end" : 128, 506 | "name" : "MUL" 507 | }, 508 | { 509 | "begin" : 106, 510 | "end" : 128, 511 | "name" : "SWAP1" 512 | }, 513 | { 514 | "begin" : 106, 515 | "end" : 128, 516 | "name" : "POP" 517 | }, 518 | { 519 | "begin" : 63, 520 | "end" : 133, 521 | "name" : "SWAP1" 522 | }, 523 | { 524 | "begin" : 63, 525 | "end" : 133, 526 | "name" : "JUMP", 527 | "value" : "[out]" 528 | } 529 | ] 530 | } 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /test/resources/fixtureAsmJson.output: -------------------------------------------------------------------------------- 1 | .code 2 | PUSH 80 contract C {\n // Leaving an ... 3 | PUSH 40 contract C {\n // Leaving an ... 4 | MSTORE contract C {\n // Leaving an ... 5 | CALLVALUE contract C {\n // Leaving an ... 6 | DUP1 C {\n // 7 | ISZERO ac 8 | PUSH [tag] 1 ac 9 | JUMPI ac 10 | PUSH 0 m 11 | DUP1 n 12 | REVERT aving an emp 13 | tag 1 ac 14 | JUMPDEST ac 15 | POP contract C {\n // Leaving an ... 16 | PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000 contract C {\n // Leaving an ... 17 | DUP1 contract C {\n // Leaving an ... 18 | PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000 contract C {\n // Leaving an ... 19 | PUSH 0 contract C {\n // Leaving an ... 20 | CODECOPY contract C {\n // Leaving an ... 21 | PUSH 0 contract C {\n // Leaving an ... 22 | RETURN contract C {\n // Leaving an ... 23 | .data 24 | 0: 25 | .code 26 | PUSH 80 contract C {\n // Leaving an ... 27 | PUSH 40 contract C {\n // Leaving an ... 28 | MSTORE contract C {\n // Leaving an ... 29 | CALLVALUE contract C {\n // Leaving an ... 30 | DUP1 C {\n // 31 | ISZERO ac 32 | PUSH [tag] 1 ac 33 | JUMPI ac 34 | PUSH 0 m 35 | DUP1 n 36 | REVERT aving an emp 37 | tag 1 ac 38 | JUMPDEST ac 39 | POP contract C {\n // Leaving an ... 40 | PUSH 4 contract C {\n // Leaving an ... 41 | CALLDATASIZE contract C {\n // Leaving an ... 42 | LT contract C {\n // Leaving an ... 43 | PUSH [tag] 2 contract C {\n // Leaving an ... 44 | JUMPI contract C {\n // Leaving an ... 45 | PUSH 0 contract C {\n // Leaving an ... 46 | CALLDATALOAD contract C {\n // Leaving an ... 47 | PUSH 100000000000000000000000000000000000000000000000000000000 contract C {\n // Leaving an ... 48 | SWAP1 contract C {\n // Leaving an ... 49 | DIV contract C {\n // Leaving an ... 50 | DUP1 contract C {\n // Leaving an ... 51 | PUSH 26121FF0 contract C {\n // Leaving an ... 52 | EQ contract C {\n // Leaving an ... 53 | PUSH [tag] 3 contract C {\n // Leaving an ... 54 | JUMPI contract C {\n // Leaving an ... 55 | tag 2 contract C {\n // Leaving an ... 56 | JUMPDEST contract C {\n // Leaving an ... 57 | PUSH 0 contract C {\n // Leaving an ... 58 | DUP1 contract C {\n // Leaving an ... 59 | REVERT contract C {\n // Leaving an ... 60 | tag 3 function f() public returns (b... 61 | JUMPDEST function f() public returns (b... 62 | PUSH [tag] 4 function f() public returns (b... 63 | PUSH [tag] 5 function f() public returns (b... 64 | JUMP [in] function f() public returns (b... 65 | tag 4 function f() public returns (b... 66 | JUMPDEST function f() public returns (b... 67 | PUSH 40 function f() public returns (b... 68 | MLOAD function f() public returns (b... 69 | DUP1 function f() public returns (b... 70 | DUP3 function f() public returns (b... 71 | PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF function f() public returns (b... 72 | NOT function f() public returns (b... 73 | AND function f() public returns (b... 74 | PUSH FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF function f() public returns (b... 75 | NOT function f() public returns (b... 76 | AND function f() public returns (b... 77 | DUP2 function f() public returns (b... 78 | MSTORE function f() public returns (b... 79 | PUSH 20 function f() public returns (b... 80 | ADD function f() public returns (b... 81 | SWAP2 function f() public returns (b... 82 | POP function f() public returns (b... 83 | POP function f() public returns (b... 84 | PUSH 40 function f() public returns (b... 85 | MLOAD function f() public returns (b... 86 | DUP1 function f() public returns (b... 87 | SWAP2 function f() public returns (b... 88 | SUB function f() public returns (b... 89 | SWAP1 function f() public returns (b... 90 | RETURN function f() public returns (b... 91 | tag 5 function f() public returns (b... 92 | JUMPDEST function f() public returns (b... 93 | PUSH 0 bytes4 94 | PUSH 26121FF0 this.f.selector 95 | PUSH 100000000000000000000000000000000000000000000000000000000 this.f.selector 96 | MUL this.f.selector 97 | SWAP1 return this.f.selector 98 | POP return this.f.selector 99 | SWAP1 function f() public returns (b... 100 | JUMP [out] function f() public returns (b... 101 | .data 102 | -------------------------------------------------------------------------------- /test/resources/fixtureAsmJson.sol: -------------------------------------------------------------------------------- 1 | contract C { 2 | // Leaving an empty line here intentionally 3 | 4 | 5 | function f() public returns (bytes4) { 6 | return this.f.selector; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/resources/fixtureIncorrectSource.sol: -------------------------------------------------------------------------------- 1 | pragma contract; 2 | -------------------------------------------------------------------------------- /test/resources/fixtureSmoke.sol: -------------------------------------------------------------------------------- 1 | contract C { 2 | function f() public returns (uint) { 3 | return 0; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/resources/importA.sol: -------------------------------------------------------------------------------- 1 | import "./importB.sol"; 2 | 3 | contract C { 4 | function f() public returns (uint) { 5 | return 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/resources/importB.sol: -------------------------------------------------------------------------------- 1 | contract D { 2 | function f() public returns (uint) { 3 | return 0; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/resources/importCallback/base/contractA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | import "libX.sol"; 5 | import "libY.sol"; 6 | import "libZ.sol"; 7 | 8 | contract A {} 9 | -------------------------------------------------------------------------------- /test/resources/importCallback/base/contractB.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | import "./contractA.sol"; 5 | 6 | contract B {} 7 | -------------------------------------------------------------------------------- /test/resources/importCallback/contractC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | contract C {} 5 | -------------------------------------------------------------------------------- /test/resources/importCallback/includeA/libX.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | library X {} 5 | -------------------------------------------------------------------------------- /test/resources/importCallback/includeA/libY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | import "./utils.sol"; 5 | 6 | library Y {} 7 | -------------------------------------------------------------------------------- /test/resources/importCallback/includeA/utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | library Utils {} 5 | -------------------------------------------------------------------------------- /test/resources/importCallback/includeB/libZ.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.0; 3 | 4 | library Z {} 5 | -------------------------------------------------------------------------------- /test/resources/smtChecker/loop.sol: -------------------------------------------------------------------------------- 1 | contract C { 2 | function f(uint x) public pure { 3 | uint i = 0; 4 | while (i < x) 5 | ++i; 6 | assert(i == x); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/resources/smtChecker/smoke.sol: -------------------------------------------------------------------------------- 1 | pragma experimental SMTChecker; 2 | contract C { 3 | function f() public pure { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/resources/smtChecker/smoke_with_engine.sol: -------------------------------------------------------------------------------- 1 | contract C { 2 | function f() public pure { 3 | } 4 | } 5 | // ==== 6 | // SMTEngine: all 7 | -------------------------------------------------------------------------------- /test/resources/smtChecker/smoke_with_multi_engine.sol: -------------------------------------------------------------------------------- 1 | pragma experimental SMTChecker; 2 | contract C { 3 | function f() public pure { 4 | } 5 | } 6 | // ==== 7 | // SMTEngine: all 8 | // SMTEngine: chc 9 | -------------------------------------------------------------------------------- /test/smtcallback.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import tape from 'tape'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import * as semver from 'semver'; 6 | import solc from '../'; 7 | import smtchecker from '../smtchecker'; 8 | import smtsolver from '../smtsolver'; 9 | 10 | const preamble = 'pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n'; 11 | 12 | function collectErrors (solOutput) { 13 | if (solOutput === undefined) { 14 | return []; 15 | } 16 | 17 | const errors = []; 18 | for (const i in solOutput.errors) { 19 | const error = solOutput.errors[i]; 20 | if (error.message.includes('This is a pre-release compiler version')) { 21 | continue; 22 | } 23 | errors.push(error.message); 24 | } 25 | return errors; 26 | } 27 | 28 | function expectErrors (expectations, errors, ignoreCex) { 29 | if (errors.length !== expectations.length) { 30 | return false; 31 | } 32 | 33 | for (const i in errors) { 34 | if (errors[i].includes('Error trying to invoke SMT solver') || expectations[i].includes('Error trying to invoke SMT solver')) { 35 | continue; 36 | } 37 | // Expectations containing counterexamples might have many '\n' in a single line. 38 | // These are stored escaped in the test format (as '\\n'), whereas the actual error from the compiler has '\n'. 39 | // Therefore we need to replace '\\n' by '\n' in the expectations. 40 | // Function `replace` only replaces the first occurrence, and `replaceAll` is not standard yet. 41 | // Replace all '\\n' by '\n' via split & join. 42 | expectations[i] = expectations[i].split('\\n').join('\n'); 43 | if (ignoreCex) { 44 | expectations[i] = expectations[i].split('\nCounterexample')[0]; 45 | errors[i] = errors[i].split('\nCounterexample')[0]; 46 | } 47 | // `expectations` have "// Warning ... " before the actual message, 48 | // whereas `errors` have only the message. 49 | if (!expectations[i].includes(errors[i])) { 50 | return false; 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | tape('SMTCheckerCallback', function (t) { 58 | t.test('Interface via callback', function (st) { 59 | if (!semver.gt(solc.semver(), '0.5.99')) { 60 | st.skip('SMT callback not implemented by this compiler version.'); 61 | st.end(); 62 | return; 63 | } 64 | 65 | const satCallback = function (query) { 66 | return { contents: 'sat\n' }; 67 | }; 68 | const unsatCallback = function (query) { 69 | return { contents: 'unsat\n' }; 70 | }; 71 | const errorCallback = function (query) { 72 | return { error: 'Fake SMT solver error.' }; 73 | }; 74 | 75 | let pragmaSMT = ''; 76 | let settings = {}; 77 | // `pragma experimental SMTChecker;` was deprecated in 0.8.4 78 | if (!semver.gt(solc.semver(), '0.8.3')) { 79 | pragmaSMT = 'pragma experimental SMTChecker;\n'; 80 | } else { 81 | settings = { modelChecker: { engine: 'all' } }; 82 | } 83 | 84 | const input = { a: { content: preamble + pragmaSMT + 'contract C { function f(uint x) public pure { assert(x > 0); } }' } }; 85 | const inputJSON = JSON.stringify({ 86 | language: 'Solidity', 87 | sources: input, 88 | settings: settings 89 | }); 90 | 91 | let tests; 92 | if (!semver.gt(solc.semver(), '0.6.8')) { 93 | // Up to version 0.6.8 there were no embedded solvers. 94 | tests = [ 95 | { cb: satCallback, expectations: ['Assertion violation happens here'] }, 96 | { cb: unsatCallback, expectations: [] }, 97 | { cb: errorCallback, expectations: ['BMC analysis was not possible'] } 98 | ]; 99 | } else if (!semver.gt(solc.semver(), '0.6.12')) { 100 | // Solidity 0.6.9 comes with z3. 101 | tests = [ 102 | { cb: satCallback, expectations: ['Assertion violation happens here'] }, 103 | { cb: unsatCallback, expectations: ['At least two SMT solvers provided conflicting answers. Results might not be sound.'] }, 104 | { cb: errorCallback, expectations: ['Assertion violation happens here'] } 105 | ]; 106 | } else { 107 | // Solidity 0.7.0 reports assertion violations via CHC. 108 | tests = [ 109 | { cb: satCallback, expectations: ['Assertion violation happens here'] }, 110 | { cb: unsatCallback, expectations: ['Assertion violation happens here'] }, 111 | { cb: errorCallback, expectations: ['Assertion violation happens here'] } 112 | ]; 113 | } 114 | 115 | for (const i in tests) { 116 | const test = tests[i]; 117 | const output = JSON.parse(solc.compile( 118 | inputJSON, 119 | { smtSolver: test.cb } 120 | )); 121 | const errors = collectErrors(output); 122 | st.ok(expectErrors(errors, test.expectations, false)); 123 | } 124 | st.end(); 125 | }); 126 | 127 | t.test('Solidity smtCheckerTests', function (st) { 128 | const testdir = path.resolve(__dirname, 'resources/smtChecker/'); 129 | if (!fs.existsSync(testdir)) { 130 | st.skip('SMT checker tests not present.'); 131 | st.end(); 132 | return; 133 | } 134 | 135 | // For these tests we actually need z3/Spacer. 136 | const z3HornSolvers = smtsolver.availableSolvers.filter(solver => solver.command === 'z3'); 137 | if (z3HornSolvers.length === 0) { 138 | st.skip('z3/Spacer not available.'); 139 | st.end(); 140 | return; 141 | } 142 | 143 | const sources = []; 144 | 145 | // BFS to get all test files 146 | const dirs = [testdir]; 147 | let i; 148 | while (dirs.length > 0) { 149 | const dir = dirs.shift(); 150 | const files = fs.readdirSync(dir); 151 | for (i in files) { 152 | const file = path.join(dir, files[i]); 153 | if (fs.statSync(file).isDirectory()) { 154 | dirs.push(file); 155 | } else { 156 | sources.push(file); 157 | } 158 | } 159 | } 160 | 161 | // Read tests and collect expectations 162 | const tests = []; 163 | for (i in sources) { 164 | st.comment('Collecting ' + sources[i] + '...'); 165 | const source = fs.readFileSync(sources[i], 'utf8'); 166 | 167 | let engine; 168 | const option = '// SMTEngine: '; 169 | if (source.includes(option)) { 170 | const idx = source.indexOf(option); 171 | if (source.indexOf(option, idx + 1) !== -1) { 172 | st.comment('SMTEngine option given multiple times.'); 173 | continue; 174 | } 175 | const re = new RegExp(option + '(\\w+)'); 176 | const m = source.match(re); 177 | assert(m !== undefined); 178 | assert(m.length >= 2); 179 | engine = m[1]; 180 | } 181 | 182 | let expected = []; 183 | const delimiter = '// ----'; 184 | if (source.includes(delimiter)) { 185 | expected = source.substring(source.indexOf('// ----') + 8, source.length).split('\n'); 186 | // Sometimes the last expectation line ends with a '\n' 187 | if (expected.length > 0 && expected[expected.length - 1] === '') { 188 | expected.pop(); 189 | } 190 | } 191 | tests[sources[i]] = { 192 | expectations: expected, 193 | solidity: { test: { content: preamble + source } }, 194 | ignoreCex: source.includes('// SMTIgnoreCex: yes'), 195 | engine: engine 196 | }; 197 | } 198 | 199 | // Run all tests 200 | for (i in tests) { 201 | const test = tests[i]; 202 | 203 | // Z3's nondeterminism sometimes causes a test to timeout in one context but not in the other, 204 | // so if we see timeout we skip a potentially misleading run. 205 | const findError = (errorMsg) => { return errorMsg.includes('Error trying to invoke SMT solver'); }; 206 | if (test.expectations.find(findError) !== undefined) { 207 | st.skip('Test contains timeout which may have been caused by nondeterminism.'); 208 | continue; 209 | } 210 | 211 | let settings = {}; 212 | // `pragma experimental SMTChecker;` was deprecated in 0.8.4 213 | if (semver.gt(solc.semver(), '0.8.3')) { 214 | const engine = test.engine !== undefined ? test.engine : 'all'; 215 | settings = { 216 | modelChecker: { 217 | engine: engine, 218 | solvers: [ 219 | 'smtlib2' 220 | ] 221 | } 222 | }; 223 | } 224 | const output = JSON.parse(solc.compile( 225 | JSON.stringify({ 226 | language: 'Solidity', 227 | sources: test.solidity, 228 | settings: settings 229 | }), 230 | // This test needs z3 specifically. 231 | { smtSolver: smtchecker.smtCallback(smtsolver.smtSolver, z3HornSolvers[0]) } 232 | )); 233 | st.ok(output); 234 | 235 | // Collect obtained error messages 236 | test.errors = collectErrors(output); 237 | 238 | // These are errors in the SMTLib2Interface encoding. 239 | if (test.errors.length > 0 && test.errors[test.errors.length - 1].includes('BMC analysis was not possible')) { 240 | continue; 241 | } 242 | 243 | // These are due to CHC not being supported via SMTLib2Interface yet. 244 | if (test.expectations.length !== test.errors.length) { 245 | continue; 246 | } 247 | 248 | if (test.errors.find(findError) !== undefined) { 249 | st.skip('Test contains timeout which may have been caused by nondeterminism.'); 250 | continue; 251 | } 252 | 253 | // Compare expected vs obtained errors 254 | st.ok(expectErrors(test.expectations, test.errors, test.ignoreCex)); 255 | } 256 | 257 | st.end(); 258 | }); 259 | }); 260 | -------------------------------------------------------------------------------- /test/smtchecker.ts: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import * as semver from 'semver'; 3 | import solc from '../'; 4 | import smtchecker from '../smtchecker'; 5 | import smtsolver from '../smtsolver'; 6 | 7 | const preamble = 'pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n'; 8 | // 9 | tape('SMTChecker', function (t) { 10 | // We use null for `solverFunction` and `solver` when calling `handleSMTQueries` 11 | // because these tests do not call a solver. 12 | 13 | t.test('smoke test with no axuiliaryInputRequested', function (st) { 14 | const input = {}; 15 | const output = {}; 16 | st.equal(smtchecker.handleSMTQueries(input, output, null, null), null); 17 | st.end(); 18 | }); 19 | 20 | t.test('smoke test with no smtlib2queries', function (st) { 21 | const input = {}; 22 | const output = { auxiliaryInputRequested: {} }; 23 | st.equal(smtchecker.handleSMTQueries(input, output, null, null), null); 24 | st.end(); 25 | }); 26 | 27 | t.test('smoke test with empty smtlib2queries', function (st) { 28 | const input = {}; 29 | const output = { auxiliaryInputRequested: { smtlib2queries: { } } }; 30 | st.equal(smtchecker.handleSMTQueries(input, output, null, null), null); 31 | st.end(); 32 | }); 33 | 34 | t.test('smtCallback should return type function', (st) => { 35 | const response = smtchecker.smtCallback(() => {}); 36 | st.equal(typeof response, 'function'); 37 | st.end(); 38 | }); 39 | 40 | t.test('smtCallback should error when passed parser fails', (st) => { 41 | const cbFun = smtchecker.smtCallback((content) => { throw new Error(content); }); 42 | const response = cbFun('expected-error-message'); 43 | 44 | st.deepEqual(response, { error: new Error('expected-error-message') }); 45 | st.end(); 46 | }); 47 | 48 | t.test('smtCallback should return content when passed parser does not fail', (st) => { 49 | const cbFun = smtchecker.smtCallback((content) => { return content; }); 50 | const response = cbFun('expected-content-message'); 51 | 52 | st.deepEqual(response, { contents: 'expected-content-message' }); 53 | st.end(); 54 | }); 55 | }); 56 | 57 | tape('SMTCheckerWithSolver', function (t) { 58 | // In these tests we require z3 to actually run the solver. 59 | // This uses the SMT double run mechanism instead of the callback. 60 | 61 | t.test('Simple test with axuiliaryInputRequested', function (st) { 62 | const z3 = smtsolver.availableSolvers.filter(solver => solver.command === 'z3'); 63 | if (z3.length === 0) { 64 | st.skip('Test requires z3.'); 65 | st.end(); 66 | return; 67 | } 68 | 69 | if (semver.lt(solc.semver(), '0.8.7')) { 70 | st.skip('This test requires Solidity 0.8.7 to enable all SMTChecker options.'); 71 | st.end(); 72 | return; 73 | } 74 | 75 | const settings = { 76 | modelChecker: { 77 | engine: 'chc', 78 | solvers: ['smtlib2'] 79 | } 80 | }; 81 | 82 | const source = { a: { content: preamble + '\ncontract C { function f(uint x) public pure { assert(x > 0); } }' } }; 83 | 84 | const input = { 85 | language: 'Solidity', 86 | sources: source, 87 | settings: settings 88 | }; 89 | 90 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 91 | st.ok(output); 92 | 93 | const newInput = smtchecker.handleSMTQueries(input, output, smtsolver.smtSolver, z3[0]); 94 | st.notEqual(newInput, null); 95 | 96 | const newOutput = JSON.parse(solc.compile(JSON.stringify(newInput))); 97 | st.ok(newOutput); 98 | 99 | const smtErrors = newOutput.errors.filter(e => e.errorCode === '6328'); 100 | st.equal(smtErrors.length, 1); 101 | 102 | st.end(); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/translate.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import tape from 'tape'; 4 | import translate from '../translate'; 5 | 6 | const versionToSemver = translate.versionToSemver; 7 | 8 | tape('Version string to Semver translator', function (t) { 9 | t.test('Only numbers', function (st) { 10 | st.equal(versionToSemver('0.1.0'), '0.1.0'); 11 | st.end(); 12 | }); 13 | t.test('New style release (semver)', function (st) { 14 | st.equal(versionToSemver('0.4.5+commit.b318366e.Emscripten.clang'), '0.4.5+commit.b318366e.Emscripten.clang'); 15 | st.end(); 16 | }); 17 | t.test('New style nightly (semver)', function (st) { 18 | st.equal(versionToSemver('0.4.20-nightly.2018.2.13+commit.27ef9794.Emscripten.clang'), '0.4.20-nightly.2018.2.13+commit.27ef9794.Emscripten.clang'); 19 | st.end(); 20 | }); 21 | t.test('Old style 0.1.1', function (st) { 22 | st.equal(versionToSemver('0.1.1-6ff4cd6b/RelWithDebInfo-Emscripten/clang/int'), '0.1.1+commit.6ff4cd6b'); 23 | st.end(); 24 | }); 25 | t.test('Old style 0.1.2', function (st) { 26 | st.equal(versionToSemver('0.1.2-5c3bfd4b*/.-/clang/int'), '0.1.2+commit.5c3bfd4b'); 27 | st.end(); 28 | }); 29 | t.test('Broken 0.1.3', function (st) { 30 | st.equal(versionToSemver('0.1.3-0/.-/clang/int linked to libethereum-0.9.92-0/.-/clang/int'), '0.1.3'); 31 | st.end(); 32 | }); 33 | t.test('Old style 0.2.0', function (st) { 34 | st.equal( 35 | versionToSemver('0.2.0-e7098958/.-Emscripten/clang/int linked to libethereum-1.1.1-bbb80ab0/.-Emscripten/clang/int'), 36 | '0.2.0+commit.e7098958' 37 | ); 38 | st.end(); 39 | }); 40 | t.test('Old style 0.3.5', function (st) { 41 | // The one in the solc-bin list 42 | st.equal(versionToSemver('0.3.5-371690f0/Release-Emscripten/clang/Interpreter'), '0.3.5+commit.371690f0'); 43 | // The actual one reported by the compiler 44 | st.equal(versionToSemver('0.3.5-0/Release-Emscripten/clang/Interpreter'), '0.3.5'); 45 | st.end(); 46 | }); 47 | t.test('Old style 0.3.6', function (st) { 48 | st.equal(versionToSemver('0.3.6-3fc68da5/Release-Emscripten/clang'), '0.3.6+commit.3fc68da5'); 49 | st.end(); 50 | }); 51 | }); 52 | 53 | tape('prettyPrintLegacyAssemblyJSON', function (t) { 54 | t.test('Works properly', function (st) { 55 | const fixtureAsmJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'resources/fixtureAsmJson.json')).toString()); 56 | const fixtureAsmJsonSource = fs.readFileSync(path.resolve(__dirname, 'resources/fixtureAsmJson.sol')).toString(); 57 | const fixtureAsmJsonOutput = fs.readFileSync(path.resolve(__dirname, 'resources/fixtureAsmJson.output')).toString(); 58 | st.equal(translate.prettyPrintLegacyAssemblyJSON(fixtureAsmJson, fixtureAsmJsonSource), fixtureAsmJsonOutput); 59 | st.end(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /translate.ts: -------------------------------------------------------------------------------- 1 | import linker from './linker'; 2 | 3 | /// Translate old style version numbers to semver. 4 | /// Old style: 0.3.6-3fc68da5/Release-Emscripten/clang 5 | /// 0.3.5-371690f0/Release-Emscripten/clang/Interpreter 6 | /// 0.3.5-0/Release-Emscripten/clang/Interpreter 7 | /// 0.2.0-e7098958/.-Emscripten/clang/int linked to libethereum-1.1.1-bbb80ab0/.-Emscripten/clang/int 8 | /// 0.1.3-0/.-/clang/int linked to libethereum-0.9.92-0/.-/clang/int 9 | /// 0.1.2-5c3bfd4b*/.-/clang/int 10 | /// 0.1.1-6ff4cd6b/RelWithDebInfo-Emscripten/clang/int 11 | /// New style: 0.4.5+commit.b318366e.Emscripten.clang 12 | function versionToSemver (version) { 13 | // FIXME: parse more detail, but this is a good start 14 | const parsed = version.match(/^([0-9]+\.[0-9]+\.[0-9]+)-([0-9a-f]{8})[/*].*$/); 15 | if (parsed) { 16 | return parsed[1] + '+commit.' + parsed[2]; 17 | } 18 | if (version.indexOf('0.1.3-0') !== -1) { 19 | return '0.1.3'; 20 | } 21 | if (version.indexOf('0.3.5-0') !== -1) { 22 | return '0.3.5'; 23 | } 24 | // assume it is already semver compatible 25 | return version; 26 | } 27 | 28 | function translateErrors (ret, errors) { 29 | for (const error in errors) { 30 | let type = 'error'; 31 | let extractType: any = /^(.*):(\d+):(\d+):(.*):/; 32 | extractType = extractType.exec(errors[error]); 33 | if (extractType) { 34 | type = extractType[4].trim(); 35 | } else if (errors[error].indexOf(': Warning:')) { 36 | type = 'Warning'; 37 | } else if (errors[error].indexOf(': Error:')) { 38 | type = 'Error'; 39 | } 40 | ret.push({ 41 | type: type, 42 | component: 'general', 43 | severity: (type === 'Warning') ? 'warning' : 'error', 44 | message: errors[error], 45 | formattedMessage: errors[error] 46 | }); 47 | } 48 | } 49 | 50 | function translateGasEstimates (gasEstimates) { 51 | if (gasEstimates === null) { 52 | return 'infinite'; 53 | } 54 | 55 | if (typeof gasEstimates === 'number') { 56 | return gasEstimates.toString(); 57 | } 58 | 59 | const gasEstimatesTranslated = {}; 60 | for (const func in gasEstimates) { 61 | gasEstimatesTranslated[func] = translateGasEstimates(gasEstimates[func]); 62 | } 63 | return gasEstimatesTranslated; 64 | } 65 | 66 | function translateJsonCompilerOutput (output, libraries) { 67 | const ret: any = {}; 68 | 69 | ret.errors = []; 70 | let errors; 71 | if (output.error) { 72 | errors = [output.error]; 73 | } else { 74 | errors = output.errors; 75 | } 76 | translateErrors(ret.errors, errors); 77 | 78 | ret.contracts = {}; 79 | for (const contract in output.contracts) { 80 | // Split name first, can be `contract`, `:contract` or `filename:contract` 81 | const tmp = contract.match(/^((.*):)?([^:]+)$/); 82 | if (tmp.length !== 4) { 83 | // Force abort 84 | return null; 85 | } 86 | let fileName = tmp[2]; 87 | if (fileName === undefined) { 88 | // this is the case of `contract` 89 | fileName = ''; 90 | } 91 | const contractName = tmp[3]; 92 | 93 | const contractInput = output.contracts[contract]; 94 | 95 | const gasEstimates = contractInput.gasEstimates; 96 | const translatedGasEstimates: any = {}; 97 | 98 | if (gasEstimates.creation) { 99 | translatedGasEstimates.creation = { 100 | codeDepositCost: translateGasEstimates(gasEstimates.creation[1]), 101 | executionCost: translateGasEstimates(gasEstimates.creation[0]) 102 | }; 103 | } 104 | if (gasEstimates.internal) { 105 | translatedGasEstimates.internal = translateGasEstimates(gasEstimates.internal); 106 | } 107 | if (gasEstimates.external) { 108 | translatedGasEstimates.external = translateGasEstimates(gasEstimates.external); 109 | } 110 | 111 | const contractOutput = { 112 | abi: JSON.parse(contractInput.interface), 113 | metadata: contractInput.metadata, 114 | evm: { 115 | legacyAssembly: contractInput.assembly, 116 | bytecode: { 117 | object: contractInput.bytecode && linker.linkBytecode(contractInput.bytecode, libraries || {}), 118 | opcodes: contractInput.opcodes, 119 | sourceMap: contractInput.srcmap, 120 | linkReferences: contractInput.bytecode && linker.findLinkReferences(contractInput.bytecode) 121 | }, 122 | deployedBytecode: { 123 | object: contractInput.runtimeBytecode && linker.linkBytecode(contractInput.runtimeBytecode, libraries || {}), 124 | sourceMap: contractInput.srcmapRuntime, 125 | linkReferences: contractInput.runtimeBytecode && linker.findLinkReferences(contractInput.runtimeBytecode) 126 | }, 127 | methodIdentifiers: contractInput.functionHashes, 128 | gasEstimates: translatedGasEstimates 129 | } 130 | }; 131 | 132 | if (!ret.contracts[fileName]) { 133 | ret.contracts[fileName] = {}; 134 | } 135 | 136 | ret.contracts[fileName][contractName] = contractOutput; 137 | } 138 | 139 | const sourceMap = {}; 140 | for (const sourceId in output.sourceList) { 141 | sourceMap[output.sourceList[sourceId]] = sourceId; 142 | } 143 | 144 | ret.sources = {}; 145 | for (const source in output.sources) { 146 | ret.sources[source] = { 147 | id: sourceMap[source], 148 | legacyAST: output.sources[source].AST 149 | }; 150 | } 151 | 152 | return ret; 153 | } 154 | 155 | function escapeString (text) { 156 | return text 157 | .replace(/\n/g, '\\n') 158 | .replace(/\r/g, '\\r') 159 | .replace(/\t/g, '\\t'); 160 | } 161 | 162 | // 'asm' can be an object or a string 163 | function formatAssemblyText (asm, prefix, source) { 164 | if (typeof asm === 'string' || asm === null || asm === undefined) { 165 | return prefix + (asm || '') + '\n'; 166 | } 167 | let text = prefix + '.code\n'; 168 | asm['.code'].forEach(function (item, i) { 169 | const v = item.value === undefined ? '' : item.value; 170 | let src = ''; 171 | if (source !== undefined && item.begin !== undefined && item.end !== undefined) { 172 | src = escapeString(source.slice(item.begin, item.end)); 173 | } 174 | if (src.length > 30) { 175 | src = src.slice(0, 30) + '...'; 176 | } 177 | if (item.name !== 'tag') { 178 | text += ' '; 179 | } 180 | text += prefix + item.name + ' ' + v + '\t\t\t' + src + '\n'; 181 | }); 182 | text += prefix + '.data\n'; 183 | const asmData = asm['.data'] || []; 184 | for (const i in asmData) { 185 | const item = asmData[i]; 186 | text += ' ' + prefix + '' + i + ':\n'; 187 | text += formatAssemblyText(item, prefix + ' ', source); 188 | } 189 | return text; 190 | } 191 | 192 | function prettyPrintLegacyAssemblyJSON (assembly, source) { 193 | return formatAssemblyText(assembly, '', source); 194 | } 195 | 196 | export = { 197 | versionToSemver, 198 | translateJsonCompilerOutput, 199 | prettyPrintLegacyAssemblyJSON 200 | }; 201 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Configuration reference: https://www.typescriptlang.org/tsconfig 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "resolveJsonModule": true, 7 | // This is needed for backwards-compatibility, to keep imports of the form `wrapper = require('solc/wrapper)` 8 | // working like they did before the TypeScript migration. 9 | // TODO: Drop it in the next breaking release. 10 | "esModuleInterop": true, 11 | "outDir": "./dist", 12 | "declaration": true, 13 | "forceConsistentCasingInFileNames": true, 14 | // Allow JS must be included to ensure that the built binary is included 15 | // in the output. This could be copied directly in the future if required. 16 | "allowJs": true, 17 | // TODO: 18 | // In order to gracefully move our project to TypeScript without having 19 | // TS immediately yell at you, we'll disable strict mode for now. 20 | "strict": false, 21 | "noImplicitAny": false 22 | }, 23 | "include": [ 24 | "**/*.js", 25 | "**/*.ts", 26 | "**/*.json" 27 | ], 28 | "exclude": [ 29 | "coverage", 30 | "dist" 31 | ], 32 | "ts-node": { 33 | "transpileOnly": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /verifyVersion.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as semver from 'semver'; 4 | import solc from './'; 5 | 6 | const { version: packageVersion } = require('./package.json'); 7 | 8 | const solcVersion = (solc as any).version(); 9 | 10 | console.log('solcVersion: ' + solcVersion); 11 | console.log('packageVersion: ' + packageVersion); 12 | 13 | if (semver.eq(packageVersion, solcVersion)) { 14 | console.log('Version matching'); 15 | process.exit(0); 16 | } else { 17 | console.log('Version mismatch'); 18 | process.exit(1); 19 | } 20 | -------------------------------------------------------------------------------- /wrapper.ts: -------------------------------------------------------------------------------- 1 | import MemoryStream from 'memorystream'; 2 | import { https } from 'follow-redirects'; 3 | 4 | import { formatFatalError } from './formatters'; 5 | import { isNil } from './common/helpers'; 6 | import setupBindings from './bindings'; 7 | import translate from './translate'; 8 | 9 | const Module = module.constructor as any; 10 | 11 | function wrapper (soljson) { 12 | const { 13 | coreBindings, 14 | compileBindings, 15 | methodFlags 16 | } = setupBindings(soljson); 17 | 18 | return { 19 | version: coreBindings.version, 20 | semver: coreBindings.versionToSemver, 21 | license: coreBindings.license, 22 | lowlevel: { 23 | compileSingle: compileBindings.compileJson, 24 | compileMulti: compileBindings.compileJsonMulti, 25 | compileCallback: compileBindings.compileJsonCallback, 26 | compileStandard: compileBindings.compileStandard 27 | }, 28 | features: { 29 | legacySingleInput: methodFlags.compileJsonStandardSupported, 30 | multipleInputs: methodFlags.compileJsonMultiSupported || methodFlags.compileJsonStandardSupported, 31 | importCallback: methodFlags.compileJsonCallbackSuppported || methodFlags.compileJsonStandardSupported, 32 | nativeStandardJSON: methodFlags.compileJsonStandardSupported 33 | }, 34 | compile: compileStandardWrapper.bind(this, compileBindings), 35 | // Loads the compiler of the given version from the github repository 36 | // instead of from the local filesystem. 37 | loadRemoteVersion, 38 | // Use this if you want to add wrapper functions around the pure module. 39 | setupMethods: wrapper 40 | }; 41 | } 42 | 43 | function loadRemoteVersion (versionString, callback) { 44 | const memoryStream = new MemoryStream(null, { readable: false }); 45 | const url = `https://binaries.soliditylang.org/bin/soljson-${versionString}.js`; 46 | 47 | https.get(url, response => { 48 | if (response.statusCode !== 200) { 49 | callback(new Error(`Error retrieving binary: ${response.statusMessage}`)); 50 | } else { 51 | response.pipe(memoryStream); 52 | response.on('end', () => { 53 | // Based on the require-from-string package. 54 | const soljson = new Module(); 55 | soljson._compile(memoryStream.toString(), `soljson-${versionString}.js`); 56 | 57 | if (module.parent && module.parent.children) { 58 | // Make sure the module is plugged into the hierarchy correctly to have parent 59 | // properly garbage collected. 60 | module.parent.children.splice(module.parent.children.indexOf(soljson), 1); 61 | } 62 | 63 | callback(null, wrapper(soljson.exports)); 64 | }); 65 | } 66 | }).on('error', function (error) { 67 | callback(error); 68 | }); 69 | } 70 | 71 | // Expects a Standard JSON I/O but supports old compilers 72 | function compileStandardWrapper (compile, inputRaw, readCallback) { 73 | if (!isNil(compile.compileStandard)) { 74 | return compile.compileStandard(inputRaw, readCallback); 75 | } 76 | 77 | let input: { language: string, sources: any[], settings: any }; 78 | 79 | try { 80 | input = JSON.parse(inputRaw); 81 | } catch (e) { 82 | return formatFatalError(`Invalid JSON supplied: ${e.message}`); 83 | } 84 | 85 | if (input.language !== 'Solidity') { 86 | return formatFatalError('Only "Solidity" is supported as a language.'); 87 | } 88 | 89 | // NOTE: this is deliberately `== null` 90 | if (isNil(input.sources) || input.sources.length === 0) { 91 | return formatFatalError('No input sources specified.'); 92 | } 93 | 94 | const sources = translateSources(input); 95 | const optimize = isOptimizerEnabled(input); 96 | const libraries = librariesSupplied(input); 97 | 98 | if (isNil(sources) || Object.keys(sources).length === 0) { 99 | return formatFatalError('Failed to process sources.'); 100 | } 101 | 102 | // Try to wrap around old versions 103 | if (!isNil(compile.compileJsonCallback)) { 104 | const inputJson = JSON.stringify({ sources: sources }); 105 | const output = compile.compileJsonCallback(inputJson, optimize, readCallback); 106 | return translateOutput(output, libraries); 107 | } 108 | 109 | if (!isNil(compile.compileJsonMulti)) { 110 | const output = compile.compileJsonMulti(JSON.stringify({ sources: sources }), optimize); 111 | return translateOutput(output, libraries); 112 | } 113 | 114 | // Try our luck with an ancient compiler 115 | if (!isNil(compile.compileJson)) { 116 | if (Object.keys(sources).length > 1) { 117 | return formatFatalError('Multiple sources provided, but compiler only supports single input.'); 118 | } 119 | 120 | const input = sources[Object.keys(sources)[0]]; 121 | const output = compile.compileJson(input, optimize); 122 | return translateOutput(output, libraries); 123 | } 124 | 125 | return formatFatalError('Compiler does not support any known interface.'); 126 | } 127 | 128 | function isOptimizerEnabled (input) { 129 | return input.settings && input.settings.optimizer && input.settings.optimizer.enabled; 130 | } 131 | 132 | function translateSources (input) { 133 | const sources = {}; 134 | 135 | for (const source in input.sources) { 136 | if (input.sources[source].content !== null) { 137 | sources[source] = input.sources[source].content; 138 | } else { 139 | // force failure 140 | return null; 141 | } 142 | } 143 | 144 | return sources; 145 | } 146 | 147 | function librariesSupplied (input) { 148 | if (!isNil(input.settings)) return input.settings.libraries; 149 | } 150 | 151 | function translateOutput (outputRaw, libraries) { 152 | let parsedOutput; 153 | 154 | try { 155 | parsedOutput = JSON.parse(outputRaw); 156 | } catch (e) { 157 | return formatFatalError(`Compiler returned invalid JSON: ${e.message}`); 158 | } 159 | 160 | const output = translate.translateJsonCompilerOutput(parsedOutput, libraries); 161 | 162 | if (isNil(output)) { 163 | return formatFatalError('Failed to process output.'); 164 | } 165 | 166 | return JSON.stringify(output); 167 | } 168 | 169 | export = wrapper; 170 | --------------------------------------------------------------------------------