diff --git a/README.md b/README.md index d2ec54a9..702ac567 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,12 @@ $ tronbox compile To compile all contracts, use the `--compile-all` option. +To compile an specific set of contracts: + +```bash +tronbox compile contracts/your_contract.sol contracts/another_contract.sol ... +``` + Specify a network using the `--network` option. Network name must exist in the configuration. For details, see [Compile a Project](https://tronbox.io/docs/guides/compile-contracts). ### Migrate diff --git a/package-lock.json b/package-lock.json index ae2918a2..176b34c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2646,6 +2646,15 @@ "node": ">=4" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -4009,6 +4018,12 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -4384,6 +4399,17 @@ "node": ">= 0.4" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -6020,4 +6046,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index a54bba4d..7777c2d5 100644 --- a/package.json +++ b/package.json @@ -103,4 +103,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/src/components/Compile/index.js b/src/components/Compile/index.js index 39cabef5..914f893c 100755 --- a/src/components/Compile/index.js +++ b/src/components/Compile/index.js @@ -85,7 +85,7 @@ const compile = function (sources, options, callback) { // Nothing to compile? Bail. if (!Object.keys(sources).length) { - return callback(null, [], []); + return callback(null, [], [], {}); } Object.keys(operatingSystemIndependentSources).forEach(function (file_path) { @@ -160,6 +160,7 @@ const compile = function (sources, options, callback) { deployedSourceMap: contract.evm.deployedBytecode.sourceMap, ast: standardOutput.sources[source_path].ast, abi: contract.abi, + metadata: contract.metadata, bytecode: '0x' + contract.evm.bytecode.object, deployedBytecode: '0x' + contract.evm.deployedBytecode.object, unlinked_binary: '0x' + contract.evm.bytecode.object, // deprecated @@ -236,6 +237,8 @@ function replaceLinkReferences(bytecode, linkReferences, libraryName) { return bytecode; } + + function orderABI(contract) { const { abi, contractName, ast } = contract; @@ -288,6 +291,13 @@ compile.all = function (options, callback) { }); }; +//Compile an specific set of contracts or a single one +compile.specific = function (options, callback) { + + options.paths = options.compileTargets; + compile.with_dependencies(options, callback); + +}; // contracts_directory: String. Directory where .sol files can be found. // build_directory: String. Optional. Directory where .sol.js files can be found. Only required if `all` is false. // all: Boolean. Compile all sources found. Defaults to true. If false, will compare sources against built files @@ -316,7 +326,7 @@ compile.necessary = function (options, callback) { if (err) return callback(err); if (updated.length === 0) { - return callback(null, [], {}); + return callback(null, [], {}, {}); } options.paths = updated; diff --git a/src/components/Config.js b/src/components/Config.js index 85f377fc..91d2da5b 100755 --- a/src/components/Config.js +++ b/src/components/Config.js @@ -77,6 +77,9 @@ function Config() { return resolvePathInWorkingDirectory(value, 'build_directory'); } }, + build_info_directory: function () { + return path.join(self.working_directory, 'build-info'); + }, contracts_directory: { default: function () { return path.join(self.working_directory, 'contracts'); diff --git a/src/components/ContractSchema/index.js b/src/components/ContractSchema/index.js index 59d6b426..c68a63ab 100755 --- a/src/components/ContractSchema/index.js +++ b/src/components/ContractSchema/index.js @@ -1,4 +1,4 @@ -const pkgVersion = '2.0.1'; +const pkgVersion = '2.0.2'; const Ajv = require('ajv'); const contractObjectSchema = require('./spec/contract-object.spec.json'); @@ -62,6 +62,12 @@ const properties = { return value; } }, + metadata: { + sources: ['metadata'], + transform(value) { + return typeof value == 'string' ? value : undefined; + } + }, sourceMap: { sources: ['sourceMap', 'srcmap', 'evm.bytecode.sourceMap'] }, diff --git a/src/components/ContractSchema/spec/contract-object.spec.json b/src/components/ContractSchema/spec/contract-object.spec.json index 83b5f8c1..aa4aee6c 100755 --- a/src/components/ContractSchema/spec/contract-object.spec.json +++ b/src/components/ContractSchema/spec/contract-object.spec.json @@ -46,6 +46,10 @@ } ] }, + "metadata": { + "type": "string", + "description": "Solidity compiler metadata JSON" + }, "sourceMap": { "allOf": [ { diff --git a/src/components/WorkflowCompile.js b/src/components/WorkflowCompile.js index 5dc73554..be1d20f2 100755 --- a/src/components/WorkflowCompile.js +++ b/src/components/WorkflowCompile.js @@ -67,11 +67,12 @@ const Contracts = { ? options.networks?.compilers?.solc?.version : options.compilers?.solc?.version; options.logger.log(` - solc${options.evm ? '(EVM)' : ''}: ${solcVersion}`); - callback(err, abstractions, paths); + callback(err, abstractions, paths, solcStandardInput); }); + self.write_buildInfo(solcStandardInput, config, inputFileName) } else { options.logger.log('> Everything is up to date, there is nothing to compile.'); - callback(null, [], paths); + callback(null, [], paths, solcStandardInput); } } @@ -79,11 +80,17 @@ const Contracts = { options.logger.log('Compiling your contracts...'); options.logger.log('==========================='); + // Compile specific contracts + if (config.compileTargets && config.compileTargets.length > 0) { + return compile.specific(config, finished); + } + + //If ALL option is selected compile all contracts if (config.all === true || config.compileAll === true) { - compile.all(config, finished); - } else { - compile.necessary(config, finished); + return compile.all(config, finished); } + //Compile modified contracts if none of the above is true + return compile.necessary(config, finished); } getCompilerVersion(options) @@ -104,8 +111,8 @@ const Contracts = { if (!options.quietWrite) { options.logger.log( 'Writing artifacts to .' + - path.sep + - path.relative(options.working_directory, options.contracts_build_directory) + path.sep + + path.relative(options.working_directory, options.contracts_build_directory) ); } diff --git a/src/lib/commands/compile.js b/src/lib/commands/compile.js index 1ed37811..a5e78d09 100755 --- a/src/lib/commands/compile.js +++ b/src/lib/commands/compile.js @@ -2,7 +2,7 @@ const version = require('../version'); const describe = 'Compile contract source files'; const command = { - command: 'compile', + command: 'compile [contracts...]', describe, builder: yargs => { yargs @@ -10,6 +10,10 @@ const command = { `TronBox v${version.bundle}\n\n${describe}\n Usage: $0 compile [] [--all] [--evm] [--quiet] ` ) + .positional('contracts', { + describe: 'Specific contract source files to compile', + type: 'string' + }) .version(false) .positional('files', { describe: 'Specific contract files to compile', @@ -31,6 +35,14 @@ Usage: $0 compile [] [--all] [--evm] [--quiet] ` } }) .example('$0 compile', 'Compile all contracts in the project') + .example( + '$0 compile contracts/MyContract.sol', + 'Compile a specific contract and its dependencies' + ) + .example( + '$0 compile contracts/A.sol contracts/B.sol', + 'Compile multiple specific contracts' + ) .example('$0 compile --all', 'Compile all contracts, even if not changed') .example('$0 compile --evm', 'Compile using EVM configuration') .example('$0 compile contracts/Foo.sol', 'Compile a specific file') @@ -43,13 +55,18 @@ Usage: $0 compile [] [--all] [--evm] [--quiet] ` if (options.quiet || options.silent) { options.logger = { - log: function () {} + log: function () { } }; } options.files = options._.slice(); const config = Config.detect(options); + + if (options.contracts && options.contracts.length > 0) { + config.compileTargets = options.contracts; + } + Contracts.compile(config, done); } }; diff --git a/test/abiv2/.gitignore b/test/abiv2/.gitignore index 710eb82d..43214565 100644 --- a/test/abiv2/.gitignore +++ b/test/abiv2/.gitignore @@ -2,5 +2,6 @@ src/js/metacoin-config.js node_modules build +build-info .env diff --git a/test/consolelogs/.gitignore b/test/consolelogs/.gitignore index c7c57403..6efd1981 100644 --- a/test/consolelogs/.gitignore +++ b/test/consolelogs/.gitignore @@ -2,6 +2,7 @@ src/js/metacoin-config.js node_modules build +build-info .env actual.log diff --git a/test/evm/.gitignore b/test/evm/.gitignore index 710eb82d..43214565 100644 --- a/test/evm/.gitignore +++ b/test/evm/.gitignore @@ -2,5 +2,6 @@ src/js/metacoin-config.js node_modules build +build-info .env diff --git a/test/runTest.sh b/test/runTest.sh index e3e00c0a..265129fe 100755 --- a/test/runTest.sh +++ b/test/runTest.sh @@ -3,6 +3,7 @@ echo 'Test abiv2' cd abiv2 rm -rf build +rm -rf build-info ../../tronbox.dev test cd .. @@ -22,6 +23,7 @@ cd .. echo 'Test init' rm -rf build +rm -rf build-info mkdir build cd build TRONBOX_CREATE_JAVASCRIPT_PROJECT_WITH_DEFAULTS=true ../../tronbox.dev init @@ -30,6 +32,7 @@ cd .. echo 'Test init metacoin' rm -rf build +rm -rf build-info mkdir build cd build TRONBOX_CREATE_JAVASCRIPT_METACOIN_PROJECT_WITH_DEFAULTS=true ../../tronbox.dev init @@ -44,5 +47,12 @@ cd .. echo 'Test evm' cd evm rm -rf build +rm -rf build-info ../../tronbox.dev test --evm cd .. + +echo 'Test compile & solcjson input file generation' +cd solcjsoninput +rm -rf build +rm -rf build-info +../../tronbox.dev test \ No newline at end of file diff --git a/test/solcjsoninput/.gitignore b/test/solcjsoninput/.gitignore new file mode 100644 index 00000000..5450a728 --- /dev/null +++ b/test/solcjsoninput/.gitignore @@ -0,0 +1,143 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# OSX +.DS_Store + +# TronBox build directory +build +build-info \ No newline at end of file diff --git a/test/solcjsoninput/.sample.env b/test/solcjsoninput/.sample.env new file mode 100644 index 00000000..4ff2b2d0 --- /dev/null +++ b/test/solcjsoninput/.sample.env @@ -0,0 +1,4 @@ +PRIVATE_KEY_SHASTA= +PRIVATE_KEY_NILE= +PRIVATE_KEY_MAINNET= +PRIVATE_KEY_DEVELOPMENT=0000000000000000000000000000000000000000000000000000000000000001 \ No newline at end of file diff --git a/test/solcjsoninput/contracts/Greeter.sol b/test/solcjsoninput/contracts/Greeter.sol new file mode 100644 index 00000000..7a222d2f --- /dev/null +++ b/test/solcjsoninput/contracts/Greeter.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title Greeter + * @dev A simple contract that stores and returns a greeting message + * This is a standalone contract with no external dependencies + */ +contract Greeter { + string private greeting; + address public owner; + uint256 public greetingChangeCount; + + event GreetingChanged(string oldGreeting, string newGreeting, address changedBy); + + /** + * @dev Constructor sets the initial greeting + * @param _greeting The initial greeting message + */ + constructor(string memory _greeting) { + greeting = _greeting; + owner = msg.sender; + greetingChangeCount = 0; + } + + /** + * @dev Get the current greeting + * @return The current greeting string + */ + function greet() public view returns (string memory) { + return greeting; + } + + /** + * @dev Set a new greeting (anyone can call) + * @param _greeting The new greeting message + */ + function setGreeting(string memory _greeting) public { + string memory oldGreeting = greeting; + greeting = _greeting; + greetingChangeCount++; + + emit GreetingChanged(oldGreeting, _greeting, msg.sender); + } + + /** + * @dev Reset greeting to default (only owner) + */ + function resetGreeting() public { + require(msg.sender == owner, 'Only owner can reset'); + setGreeting('Hello, World!'); + } + + /** + * @dev Get greeting statistics + * @return message The current greeting + * @return changes Number of times greeting has been changed + * @return currentOwner The contract owner + */ + function getGreetingInfo() public view returns (string memory message, uint256 changes, address currentOwner) { + return (greeting, greetingChangeCount, owner); + } +} diff --git a/test/solcjsoninput/contracts/Migrations.sol b/test/solcjsoninput/contracts/Migrations.sol new file mode 100644 index 00000000..9aac9750 --- /dev/null +++ b/test/solcjsoninput/contracts/Migrations.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.22 <0.9.0; + +contract Migrations { + address public owner = msg.sender; + uint public last_completed_migration; + + modifier restricted() { + require( + msg.sender == owner, + "This function is restricted to the contract's owner" + ); + _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } +} diff --git a/test/solcjsoninput/contracts/SimpleToken.sol b/test/solcjsoninput/contracts/SimpleToken.sol new file mode 100644 index 00000000..c99bd7ae --- /dev/null +++ b/test/solcjsoninput/contracts/SimpleToken.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; +import '@openzeppelin/contracts/access/Ownable.sol'; + +/** + * @title SimpleToken + * @dev A basic ERC20 token with minting capability + * This contract demonstrates OpenZeppelin imports for testing TronBox compilation and JSON input file generation + */ +contract SimpleToken is ERC20, Ownable { + uint256 public constant MAX_SUPPLY = 1000000 * 10 ** 18; // 1 million tokens + + /** + * @dev Constructor that gives msg.sender all of initial supply + * @param initialSupply The initial token supply to mint + */ + constructor(uint256 initialSupply) ERC20('Simple Token', 'SMPL') Ownable(msg.sender) { + require(initialSupply <= MAX_SUPPLY, 'Initial supply exceeds max supply'); + _mint(msg.sender, initialSupply); + } + + /** + * @dev Mint new tokens (only owner) + * @param to Address to receive the minted tokens + * @param amount Amount of tokens to mint + */ + function mint(address to, uint256 amount) external onlyOwner { + require(totalSupply() + amount <= MAX_SUPPLY, 'Would exceed max supply'); + _mint(to, amount); + } + + /** + * @dev Burn tokens from caller's balance + * @param amount Amount of tokens to burn + */ + function burn(uint256 amount) external { + _burn(msg.sender, amount); + } +} diff --git a/test/solcjsoninput/migrations/1_initial_migration.js b/test/solcjsoninput/migrations/1_initial_migration.js new file mode 100644 index 00000000..037d9982 --- /dev/null +++ b/test/solcjsoninput/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require('./Migrations.sol'); + +module.exports = function (deployer) { + deployer.deploy(Migrations); +}; diff --git a/test/solcjsoninput/migrations/2_deploy_contracts.js b/test/solcjsoninput/migrations/2_deploy_contracts.js new file mode 100644 index 00000000..1d63f3f6 --- /dev/null +++ b/test/solcjsoninput/migrations/2_deploy_contracts.js @@ -0,0 +1,5 @@ +const Greeter = artifacts.require('./Greeter.sol'); + +module.exports = function (deployer) { + deployer.deploy(Greeter, "Hello, Tronbox!"); +}; diff --git a/test/solcjsoninput/package-lock.json b/test/solcjsoninput/package-lock.json new file mode 100644 index 00000000..d4bef5ab --- /dev/null +++ b/test/solcjsoninput/package-lock.json @@ -0,0 +1,133 @@ +{ + "name": "jsoninputdemo", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jsoninputdemo", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@openzeppelin/contracts": "5.4.0", + "chai": "^6.2.2", + "picocolors": "^1.1.1", + "sinon": "^21.0.1" + } + }, + "node_modules/@openzeppelin/contracts": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", + "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", + "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/sinon": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", + "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^15.1.0", + "@sinonjs/samsam": "^8.0.3", + "diff": "^8.0.2", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + } + } +} diff --git a/test/solcjsoninput/package.json b/test/solcjsoninput/package.json new file mode 100644 index 00000000..48827725 --- /dev/null +++ b/test/solcjsoninput/package.json @@ -0,0 +1,22 @@ +{ + "name": "jsoninputdemo", + "version": "0.0.1", + "description": "Json input file generation tests", + "keywords": [ + "tron", + "tronbox", + "contracts", + "solidity", + "verification" + ], + "author": { + "name": "Simbad Marino" + }, + "license": "MIT", + "dependencies": { + "@openzeppelin/contracts": "5.4.0", + "chai": "^6.2.2", + "picocolors": "^1.1.1", + "sinon": "^21.0.1" + } +} diff --git a/test/solcjsoninput/test/solcjson_test.js b/test/solcjsoninput/test/solcjson_test.js new file mode 100644 index 00000000..d10647aa --- /dev/null +++ b/test/solcjsoninput/test/solcjson_test.js @@ -0,0 +1,296 @@ +const { assert, expect } = require('chai'); +const fs = require('fs'); +const path = require('path'); +const command = require('../../../src/lib/commands/compile'); + +contract('####### solcjsonInput test #######', function () { + const testContractsDir = path.join(__dirname, '../contracts'); + const buildInfoDir = path.join(__dirname, '../build-info'); + const buildDir = path.join(__dirname, '../build'); + const workingDir = path.join(__dirname, '..'); + + before(function () { + // Delete previous build & build-info folder from last test run + console.log('Cleaning up previous build artifacts...'); + if (fs.existsSync(buildInfoDir)) { + try { + const files = fs.readdirSync(buildInfoDir); + files.forEach(f => { + if (f.endsWith('.json')) { + fs.unlinkSync(path.join(buildInfoDir, f)); + } + }); + } catch (e) { + // Ignore cleanup errors + } + } + + //Clean up generated build-info files + if (fs.existsSync(buildDir)) { + try { + const files = fs.readdirSync(buildDir); + files.forEach(f => { + if (f.endsWith('.json')) { + fs.unlinkSync(path.join(buildDir, f)); + } + }); + } catch (e) { + // Ignore cleanup errors + } + } + }); + + + + it('tc-solc-001: should have correct command structure', function () { + assert.equal(command.command, 'compile [contracts...]'); + assert.include(command.describe, 'Compile contract source files'); + }); + + it('tc-solc-002: should handle compilation errors for non-existent files', function (done) { + this.timeout(30000); + + const options = { + contracts: [path.join(testContractsDir, 'NonExistent.sol')], + working_directory: workingDir + }; + + command.run(options, function (err) { + // Should error on non-existent file + assert.exists(err, 'Should return an error for non-existent contract file'); + assert.isTrue(err instanceof Error, 'Error should be an Error instance'); + + done(); + }); + }); + + it('tc-solc-003: should verify actual contract files exist', function () { + // Ensure test contracts are available + assert.isTrue(fs.existsSync(path.join(testContractsDir, 'SimpleToken.sol')), 'SimpleToken.sol should exist'); + assert.isTrue(fs.existsSync(path.join(testContractsDir, 'Greeter.sol')), 'Greeter.sol should exist'); + }); + + it('tc-solc-004: should properly resolve contract file paths', function (done) { + this.timeout(30000); + + const options = { + // Use relative paths as would be provided by the command + contracts: [ + path.join(testContractsDir, 'SimpleToken.sol') + ], + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + assert.exists(contracts, 'Contracts should compile with absolute paths'); + done(); + }); + }); + + it('tc-solc-005: should compile specific contract', function (done) { + this.timeout(30000); // Compilation can take time + + const contractPath = path.join(testContractsDir, 'Greeter.sol'); + const options = { + contracts: [contractPath], + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + // Verify compilation succeeded with contracts + assert.exists(contracts, 'Contracts should be returned from compilation'); + assert.isObject(contracts, 'Contracts should be an object'); + assert.isTrue(Object.keys(contracts).length > 0, 'Should have compiled contracts'); + + done(); + }); + }); + + it('tc-solc-006: should compile multiple contracts', function (done) { + this.timeout(30000); + + const options = { + contracts: [ + path.join(testContractsDir, 'SimpleToken.sol'), + path.join(testContractsDir, 'Greeter.sol') + ], + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + assert.exists(contracts, 'Multiple contracts should compile'); + assert.isTrue(Object.keys(contracts).length > 0, 'Should have compiled contracts'); + + done(); + }); + }); + + + it('tc-solc-007: should compile all contracts when --all flag is used', function (done) { + this.timeout(30000); + + const options = { + all: true, + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + // Should compile all contracts in the directory + assert.exists(contracts, 'All contracts should be compiled'); + assert.isObject(contracts, 'Should return contracts object'); + + done(); + }); + }); + + it('tc-solc-008: should suppress output with --quiet option', function (done) { + this.timeout(30000); + + const options = { + quiet: true, + all: true, + working_directory: workingDir + }; + + command.run(options, function (err) { + if (err) return done(err); + + // Logger should be a no-op object with a `log` function + assert.isObject(options.logger, 'Logger should be set'); + assert.isFunction(options.logger.log, 'Logger.log should be a function'); + + done(); + }); + }); + + it('tc-solc-009: should suppress output with --silent option', function (done) { + this.timeout(30000); + + const options = { + silent: true, + all: true, + working_directory: workingDir + }; + + command.run(options, function (err) { + if (err) return done(err); + + // Logger should be a no-op object with a `log` function + assert.isObject(options.logger, 'Logger should be set'); + assert.isFunction(options.logger.log, 'Logger.log should be a function'); + + done(); + }); + }); + + it('tc-solc-010: should generate build-info JSON files with compilation data', function (done) { + this.timeout(30000); + + const options = { + all: true, + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + // Check that build-info directory has JSON files + const files = fs.readdirSync(buildInfoDir); + const jsonFiles = files.filter(f => f.endsWith('.json')); + + assert.isTrue(jsonFiles.length > 0, 'Build-info directory should contain JSON files after compilation'); + + // Verify each JSON file contains valid solc standard input format + jsonFiles.forEach(file => { + const content = fs.readFileSync(path.join(buildInfoDir, file), 'utf8'); + const json = JSON.parse(content); + + // Check for solc standard JSON input properties + assert.exists(json.language, `${file} should have language property`); + assert.equal(json.language, 'Solidity', 'Language should be Solidity'); + assert.exists(json.sources, `${file} should have sources property`); + assert.isObject(json.sources, 'sources should be an object'); + assert.exists(json.settings, `${file} should have settings property`); + assert.isObject(json.settings, 'settings should be an object'); + assert.exists(json.settings.outputSelection, 'settings should have outputSelection'); + + // Verify outputSelection has expected structure described in solc documentation (current 0.8.25) + assert.exists(json.settings.outputSelection['*'], 'outputSelection should have * key'); + assert.exists(json.settings.outputSelection['*']['*'], 'outputSelection * should have * key'); + assert.isArray(json.settings.outputSelection['*']['*'], 'outputSelection * * should be an array'); + assert.include(json.settings.outputSelection['*']['*'], 'abi', 'outputSelection should include abi'); + assert.include(json.settings.outputSelection['*']['*'], 'devdoc', 'outputSelection should include devdoc'); + assert.include(json.settings.outputSelection['*']['*'], 'userdoc', 'outputSelection should include userdoc'); + assert.include(json.settings.outputSelection['*']['*'], 'metadata', 'outputSelection should include metadata'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.bytecode.object', 'outputSelection should include evm.bytecode.object'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.bytecode.sourceMap', 'outputSelection should include evm.bytecode.sourceMap'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.bytecode.linkReferences', 'outputSelection should include evm.bytecode.linkReferences'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.deployedBytecode.object', 'outputSelection should include evm.deployedBytecode.object'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.deployedBytecode.sourceMap', 'outputSelection should include evm.deployedBytecode.sourceMap'); + assert.include(json.settings.outputSelection['*']['*'], 'storageLayout', 'outputSelection should include storageLayout'); + assert.include(json.settings.outputSelection['*']['*'], 'evm.methodIdentifiers', 'outputSelection should include evm.methodIdentifiers'); + + }); + + done(); + }); + }); + + it('tc-solc-011: should return compiled contracts with proper ABI and bytecode', function (done) { + this.timeout(30000); + + const options = { + contracts: [path.join(testContractsDir, 'SimpleToken.sol')], + working_directory: workingDir + }; + + command.run(options, function (err, contracts) { + if (err) return done(err); + + // Verify contract structure matches what solc produces + assert.isObject(contracts, 'Contracts should be an object'); + assert.isTrue(Object.keys(contracts).length > 0, 'Should have at least one compiled contract'); + + // Check each contract's structure + Object.keys(contracts).forEach(contractName => { + const contract = contracts[contractName]; + assert.exists(contract.abi, `Contract ${contractName} should have abi`); + assert.isArray(contract.abi, 'abi should be an array'); + assert.exists(contract.bytecode, `Contract ${contractName} should have bytecode`); + }); + + done(); + }); + }); + + it('tc-solc-012: should deploy simple contract using new artifact schema ', async function () { + this.timeout(30000); + + + const Greeter = artifacts.require('./Greeter.sol'); + const c = await Greeter.new("Hello, Tronbox!"); + const greeting = await c.greet(); + assert.equal(greeting, "Hello, Tronbox!", "Greeter should return the correct greeting"); + }); + + it('tc-solc-013: should deploy OpenZeppelin deps contract using new artifact schema ', async function () { + this.timeout(30000); + + + const SimpleToken = artifacts.require('./SimpleToken.sol'); + const c = await SimpleToken.new(7777777); + const minting = await c.mint("TJDMQzjJSh5eC8WezVtnDXDuWXAwjV23eF", 1000); + assert.lengthOf(minting, 64, 'Minting should return a transaction hash of length 64'); + }); + + + +}); diff --git a/test/solcjsoninput/tronbox.js b/test/solcjsoninput/tronbox.js new file mode 100644 index 00000000..d1a49220 --- /dev/null +++ b/test/solcjsoninput/tronbox.js @@ -0,0 +1,63 @@ +module.exports = { + networks: { + mainnet: { + // Don't put your private key here: + privateKey: process.env.PRIVATE_KEY_MAINNET, + /** + * Create a .env file (it must be gitignored) containing something like + * + * export PRIVATE_KEY_MAINNET=4E7FEC...656243 + * + * Then, run the migration with: + * + * source .env && tronbox migrate --network mainnet + */ + userFeePercentage: 100, + feeLimit: 1000 * 1e6, + fullHost: 'https://api.trongrid.io', + network_id: '1' + }, + shasta: { + // Obtain test coin at https://shasta.tronex.io/ + privateKey: process.env.PRIVATE_KEY_SHASTA, + userFeePercentage: 50, + feeLimit: 1000 * 1e6, + fullHost: 'https://api.shasta.trongrid.io', + network_id: '2' + }, + nile: { + // Obtain test coin at https://nileex.io/join/getJoinPage + privateKey: process.env.PRIVATE_KEY_NILE, + userFeePercentage: 100, + feeLimit: 1000 * 1e6, + fullHost: 'https://nile.trongrid.io', + network_id: '3' + }, + development: { + // For tronbox/tre docker image + // See https://hub.docker.com/r/tronbox/tre + privateKey: '0000000000000000000000000000000000000000000000000000000000000001', + userFeePercentage: 0, + feeLimit: 1000 * 1e6, + fullHost: 'http://127.0.0.1:9090', + network_id: '9' + }, + + }, + compilers: { + solc: { + version: '0.8.25', + // An object with the same schema as the settings entry in the Input JSON. + // See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description + settings: { + optimizer: { + enabled: true, + runs: 200 + }, + evmVersion: 'shanghai', + viaIR: false, + metadata: { bytecodeHash: "ipfs", useLiteralContent: true } + } + } + } +}; \ No newline at end of file diff --git a/test/tre/.gitignore b/test/tre/.gitignore index c27e9ba4..e7209c3c 100644 --- a/test/tre/.gitignore +++ b/test/tre/.gitignore @@ -1,3 +1,4 @@ node_modules build +build-info .env \ No newline at end of file