├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── commands │ ├── block.js │ ├── blockdate.js │ ├── calldata.js │ ├── checksum.js │ ├── compile │ │ ├── compile.js │ │ ├── jsonIO.js │ │ └── solcjsResolver.js │ ├── convert.js │ ├── disassemble.js │ ├── docyul.js │ ├── getcode.js │ ├── hex2int.js │ ├── hex2str.js │ ├── hex2uint.js │ ├── info.js │ ├── inheritance.js │ ├── int2hex.js │ ├── liststorage.js │ ├── members.js │ ├── pad.js │ ├── pastevents.js │ ├── selector.js │ ├── selectors.js │ ├── split.js │ ├── storage.js │ ├── str2hex.js │ ├── transaction.js │ ├── txs.js │ └── uint2hex.js ├── globals.js ├── program.js └── utils │ ├── abiUtil.js │ ├── astUtil.js │ ├── cli.js │ ├── etherscanApi.js │ ├── getArtifacts.js │ ├── getWeb3.js │ ├── highlightUtil.js │ ├── log.js │ ├── stringUtil.js │ └── validateUtil.js ├── test ├── artifacts │ ├── EventEmitter.json │ ├── GrandParent.json │ ├── Lib.json │ ├── Parent1.json │ ├── Parent2.json │ ├── Sample.json │ ├── SampleAbstract.json │ ├── SampleDependency.json │ ├── Storage.json │ └── Test.json ├── commands │ ├── block.test.js │ ├── blockdate.test.js │ ├── calldata.test.js │ ├── checksum.test.js │ ├── compile.test.js │ ├── convert.test.js │ ├── disassemble.test.js │ ├── docyul.test.js │ ├── getcode.test.js │ ├── hex2int.test.js │ ├── hex2str.test.js │ ├── hex2uint.test.js │ ├── info.test.js │ ├── inheritance.test.js │ ├── int2hex.test.js │ ├── liststorage.test.js │ ├── members.test.js │ ├── pad.test.js │ ├── pastevents.test.js │ ├── selector.test.js │ ├── selectors.test.js │ ├── split.test.js │ ├── storage.test.js │ ├── str2hex.test.js │ ├── transaction.test.js │ ├── txs.test.js │ └── uint2hex.test.js ├── contracts │ ├── EventEmitter.sol │ ├── NodeModules.sol │ ├── Ranges.sol │ ├── Sample.sol │ ├── SampleAbstract.sol │ ├── SampleDependency.sol │ ├── SearchPaths.sol │ ├── SplitMe.sol │ ├── Storage.sol │ ├── Test.sol │ ├── searchpath │ │ └── Dependency.sol │ └── subdir │ │ └── subsubdir │ │ └── NodeModules.sol ├── flows │ ├── ant.test.js │ ├── cryptokitties.test.js │ └── rep.test.js ├── misc │ ├── getArtifacts.test.js │ ├── help.test.js │ └── program.test.js ├── output │ ├── ant.members.output │ ├── cryptokitties.members.output │ └── rep.members.output └── setup.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/* 3 | !test/node_modules/ 4 | config.json 5 | soljson* 6 | log 7 | notes 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
 2 | _______________________________________________________________________________________/\\\_________        
 3 |  ____________________________________________/\\\______________________________________\/\\\_________       
 4 |   ___/\\\\\\\\\______________________________\/\\\____________________________/\\\______\/\\\_________      
 5 |    __/\\\/////\\\_____/\\\\\________/\\\\\\\\_\/\\\\\\\\________/\\\\\\\\___/\\\\\\\\\\\_\/\\\_________     
 6 |     _\/\\\\\\\\\\____/\\\///\\\____/\\\//////__\/\\\////\\\____/\\\/////\\\_\////\\\////__\/\\\\\\\\\\__    
 7 |      _\/\\\//////____/\\\__\//\\\__/\\\_________\/\\\\\\\\/____/\\\\\\\\\\\_____\/\\\______\/\\\/////\\\_   
 8 |       _\/\\\_________\//\\\__/\\\__\//\\\________\/\\\///\\\___\//\\///////______\/\\\_/\\__\/\\\___\/\\\_  
 9 |        _\/\\\__________\///\\\\\/____\///\\\\\\\\_\/\\\_\///\\\__\//\\\\\\\\\\____\//\\\\\___\/\\\___\/\\\_ 
10 |         _\///_____________\/////________\////////__\///____\///____\//////////______\/////____\///____\///__
11 | 
12 | 13 | A pocket knife for developing and auditing smart contracts. Provides a series of cli commands that allow you to quickly operate on a contract, such as: 14 | 15 | ``` 16 | - getcode [targetFilePath] - Retrieves a contract's code from Etherscan. 17 | - split - Splits Solidity files. 18 | - inheritance - Displays the inheritance tree of a contract. 19 | - members - Lists all members of a contract. 20 | - selectors - Lists all selectors of a contract. 21 | - calldata - Splits up calldata into a more readable format. 22 | - blockdate - Gets the date of a block. 23 | - txs [toBlock] [maxThreads] - Finds transactions. 24 | - pastevents [toBlock] [batchSize] - Finds past events of a contract. 25 | - disassemble - Disassembles bytecode to EVM opcodes. 26 | - hex2str - Converts hex to string. 27 | - str2hex - Converts string to hex. 28 | - transaction - Gets info about a transaction. 29 | - block [blockHashOrNumber] - Gets info about a block. 30 | - info - Retrieves info about a network. 31 | - hex2uint - Converts hex to uint. 32 | - uint2hex - Converts uint to hex. 33 | - convert [value] [sourceDenom] [destDenom] - Converts between ether denominations. 34 | - pad [direction] [amount] [char] - Pads hex numbers. 35 | - compile [solcVersion] - Compiles single Solidity files. 36 | - storage - Reads the storage of a contract. 37 | - liststorage - Reads the storage of a contract. 38 | - checksum
- Checksums an address. 39 | - selector - Calculates a selector. 40 | - docyul [keyword] - Gets yul documentation. 41 | - int2hex - Converts int to hex. 42 | - hex2int - Converts hex to int. 43 | ``` 44 | 45 | #### One script to rule them all! 46 | Pocketh is basically a curated list of useful scripts, packed up in a commanderjs program. 47 | 48 | #### On the fly compilation :rocket: 49 | Pocketh uses AST information for analysing contracts, but it doesn't require you to do any compilation at all. Whenever pocketh sees a Solidity file, it will compile it in background. Pocketh's compiler is super handy btw; it can compile anything, on the spot. 50 | 51 | ### Installation: 52 | ``` 53 | npm install --global pocketh 54 | ``` 55 | 56 | ### Usage: 57 | ``` 58 | pocketh [options] 59 | ``` 60 | 61 | ### Documentation: 62 | Please refer to the inline documentation of the program for a list of available commands, 63 | 64 | ``` 65 | pocketh --help 66 | ``` 67 | or, for command specific documentation. 68 | ``` 69 | pocketh --help 70 | ``` 71 | 72 | ### Contributing: 73 | If you think of something useful that could be added to the tool, or have an idea, please don't hesitate to create a new issue with your feature request. Or, even better, go ahead and create a PR! Pocketh's architecture is intended to be as simple as possible so that it's super easy to add new scripts. Every command is pretty much a standalone script, with the exception of a few convenient tools that are shared between some of the commands. 74 | 75 | #### Running tests 76 | Make sure that you run `npm run ganache` before starting tests. Then run `npm test`. 77 | 78 | _Note: Unfortunately, pocketh currently requires an active internet connection for some of its tests, since some commands interact with public networks like mainnet and ropsten, and use public apis like Etherscan's api._ 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocketh", 3 | "version": "0.1.66", 4 | "description": "A pocket knife for auditing smart contracts.", 5 | "main": "src/program.js", 6 | "bin": { 7 | "pocketh": "src/program.js" 8 | }, 9 | "scripts": { 10 | "ganache": "ganache-cli --deterministic", 11 | "test": "jest --verbose", 12 | "testwatch": "jest --watch" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ajsantander/awesome-eth-tools.git" 17 | }, 18 | "keywords": [ 19 | "ethereum", 20 | "solidity", 21 | "smart contracts", 22 | "audit", 23 | "tools" 24 | ], 25 | "author": "ajsantander", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/ajsantander/awesome-eth-tools/issues" 29 | }, 30 | "homepage": "https://github.com/ajsantander/awesome-eth-tools#readme", 31 | "dependencies": { 32 | "axios": "^0.18.0", 33 | "bn.js": "^4.11.8", 34 | "chalk": "^2.4.2", 35 | "commander": "^2.20.0", 36 | "ethers": "^4.0.47", 37 | "figlet": "^1.2.1", 38 | "find-node-modules": "^2.0.0", 39 | "semver": "^6.1.0", 40 | "solc": "^0.5.8", 41 | "tmp": "^0.1.0", 42 | "treeify": "^1.1.0", 43 | "web3": "1.0.0-beta.37" 44 | }, 45 | "devDependencies": { 46 | "ganache-cli": "^6.4.3", 47 | "jest": "^24.8.0" 48 | }, 49 | "jest": { 50 | "setupFilesAfterEnv": [ 51 | "./test/setup.js" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/commands/block.js: -------------------------------------------------------------------------------- 1 | const getWeb3 = require('../utils/getWeb3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'block [blockHashOrNumber]'; 6 | const description = 'Gets info about a block.'; 7 | const help = chalk` 8 | Given a network and a block number or block hash, retrieves the information for that block. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh block mainnet 1} 13 | \{ 14 | "difficulty": "17171480576", 15 | "extraData": "0x476574682f76312e302e302f6c696e75782f676f312e342e32", 16 | "gasLimit": 5000, 17 | "gasUsed": 0, 18 | "hash": "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6", 19 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 20 | "miner": "0x05a56E2D52c817161883f50c441c3228CFe54d9f", 21 | "mixHash": "0x969b900de27b6ac6a67742365dd65f55a0526c41fd18e1b16f1a1215c2e66f59", 22 | "nonce": "0x539bd4979fef1ec4", 23 | "number": 1, 24 | "parentHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", 25 | "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", 26 | "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", 27 | "size": 537, 28 | "stateRoot": "0xd67e4d450343046425ae4271474353857ab860dbc0a1dde64b41b5cd3a532bf3", 29 | "timestamp": 1438269988, 30 | "totalDifficulty": "34351349760", 31 | "transactions": [], 32 | "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", 33 | "uncles": [] 34 | \} 35 | `; 36 | 37 | module.exports = { 38 | signature, 39 | description, 40 | register: (program) => { 41 | program 42 | .command(signature, {noHelp: true}) 43 | .description(description) 44 | .on('--help', () => console.log(help)) 45 | .action(async (networkUrl, blockHashOrNumber) => { 46 | 47 | // Input validation. 48 | if(!validateUtil.positiveInteger(blockHashOrNumber) && !validateUtil.bytes32(blockHashOrNumber)) 49 | throw new Error(`Invalid blockHashOrNumber: ${blockHashOrNumber}`); 50 | 51 | const web3 = await getWeb3(networkUrl); 52 | 53 | if(!blockHashOrNumber) blockHashOrNumber = await web3.eth.getBlockNumber(); 54 | const tx = await web3.eth.getBlock(blockHashOrNumber); 55 | console.log(`${ JSON.stringify(tx, null, 2) }`); 56 | }); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/commands/blockdate.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const getWeb3 = require('../utils/getWeb3.js'); 3 | const validateUtil = require('../utils/validateUtil'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'blockdate '; 7 | const description = 'Gets the date of a block.'; 8 | const help = chalk` 9 | Gets the date of a block number in the given network. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh blockdate mainnet 5000000} 14 | Tue Jan 30 2018 10:41:33 GMT-0300 (Uruguay Standard Time) 15 | 16 | `; 17 | 18 | module.exports = { 19 | signature, 20 | description, 21 | register: (program) => { 22 | program 23 | .command(signature, {noHelp: true}) 24 | .description(description) 25 | .on('--help', () => console.log(help)) 26 | .action(async (networkUrl, blockHashOrNumber) => { 27 | 28 | // Validate input. 29 | if(!validateUtil.integer(blockHashOrNumber) && !validateUtil.bytes32(blockHashOrNumber)) 30 | throw new Error(`Invalid blockHashOrNumber: ${blockHashOrNumber}`); 31 | 32 | blockHashOrNumber = parseInt(blockHashOrNumber , 10); 33 | 34 | // Connect to network. 35 | const web3 = await getWeb3(networkUrl); 36 | 37 | // Get block info. 38 | const block = await web3.eth.getBlock(blockHashOrNumber); 39 | const date = new Date(parseInt(block.timestamp, 10) * 1000); 40 | process.stdout.write(`${date}\n`); 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/commands/calldata.js: -------------------------------------------------------------------------------- 1 | const validateUtil = require('../utils/validateUtil'); 2 | const etherscanApi = require('../utils/etherscanApi'); 3 | const chalk = require('chalk'); 4 | const { sha3 } = require('web3-utils') 5 | const ethers = require('ethers') 6 | 7 | const signature = 'calldata '; 8 | const description = 'Decodes calldata in calls to contracts'; 9 | const help = chalk` 10 | The command takes an address of a deployed contract and the calldata used for the call. It retrieves the contract's ABI from Etherscan, decodes de calldata against it, and prints out information about the call that can be understood in human terms. 11 | 12 | {red Eg:} 13 | 14 | {blue > pocketh calldata 0x818E6FECD516Ecc3849DAf6845e3EC868087B755 0x29589f61000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000008e0bbf33b07f0a6aa6ceea07bb8f0734e57201cb800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a38af210bdf2cc479000000000000000000000000440bbd6a888a36de6e2f6a25f65bc4e16874faa9000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000045045524d00000000000000000000000000000000000000000000000000000000} 15 | Address: 0x818E6FECD516Ecc3849DAf6845e3EC868087B755 16 | Contract name: KyberNetworkProxy 17 | Function call: 18 | tradeWithHint( 19 | src: address = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 20 | srcAmount: uint256 = 10000000000000000 21 | dest: address = 0x6B175474E89094C44Da98b954EedeAC495271d0F 22 | destAddress: address = 0x8E0Bbf33B07f0A6aa6CEeA07BB8f0734e57201Cb 23 | maxDestAmount: uint256 = 57896044618658097711785492504343953926634992332820282019728792003956564819968 24 | minConversionRate: uint256 = 188551960459016455289 25 | walletId: address = 0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9 26 | hint: bytes = 0x5045524d 27 | ) 28 | Contract code: https://etherscan.io/address/0x818E6FECD516Ecc3849DAf6845e3EC868087B755#code 29 | `; 30 | 31 | module.exports = { 32 | signature, 33 | description, 34 | register: (program) => { 35 | program 36 | .command(signature, {noHelp: true}) 37 | .description(description) 38 | .on('--help', () => console.log(help)) 39 | .action(async (contractAddress, calldata) => { 40 | // Retrieve source code (with abi) from Etherscan. 41 | // Note: could fetch ABI directly, but this brings in additional data. 42 | const result = await etherscanApi.getSourceCodeFull(contractAddress) 43 | const data = result[0] 44 | const contractName = data.ContractName 45 | const abi = JSON.parse(data.ABI) 46 | 47 | // Extract function selector from calldata. 48 | const selector = calldata.slice(2, 10) 49 | 50 | // Sweep ABI items of type 'function' and find a match with the selector. 51 | let matchingAbiItem = abi.find(abiItem => { 52 | if (abiItem.type === 'function') { 53 | const signature = `${abiItem.name}(${abiItem.inputs.map(input => input.type).join(',')})` 54 | const signatureHash = sha3(signature).slice(2, 10) 55 | return signatureHash === selector 56 | } 57 | }) 58 | if (!matchingAbiItem) { 59 | throw new Error(`Unable to find a matching function call for selector ${selector} in the retrieve ABI.`) 60 | } 61 | 62 | // Decode calldata. 63 | const payload = `0x${calldata.slice(10, calldata.length)}` 64 | const types = matchingAbiItem.inputs.map(input => input.type) 65 | const decoded = ethers.utils.defaultAbiCoder.decode(types, payload) 66 | 67 | // Print output. 68 | console.log(`Address: ${contractAddress}`) 69 | console.log(`Contract name: ${contractName}`) 70 | console.log(`Function call:`) 71 | console.log(` ${matchingAbiItem.name}(`) 72 | let idx = 0 73 | matchingAbiItem.inputs.map(input => { 74 | console.log(` ${input.name}: ${input.type} = ${ 75 | decoded[idx] 76 | }`) 77 | idx++ 78 | }) 79 | console.log(' )') 80 | console.log(`Contract code: https://etherscan.io/address/${contractAddress}#code`) 81 | }); 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /src/commands/checksum.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'checksum
'; 6 | const description = 'Checksums an address.'; 7 | const help = chalk` 8 | Converts a non checksummed address to it's checksummed version. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh checksum 0x06012c8cf97bead5deae237070f9587f8e7a266d} 13 | 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d 14 | 15 | `; 16 | 17 | module.exports = { 18 | signature, 19 | description, 20 | register: (program) => { 21 | program 22 | .command(signature, {noHelp: true}) 23 | .description(description) 24 | .on('--help', () => console.log(help)) 25 | .action((address) => { 26 | 27 | // Input validation. 28 | if(!validateUtil.address(address)) 29 | throw new Error(`Invalid address: ${address}`); 30 | 31 | const web3 = new Web3(); 32 | console.log(web3.utils.toChecksumAddress(address)); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/compile/compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const solcjsResolver = require('./solcjsResolver'); 4 | const jsonIO = require('./jsonIO'); 5 | const chalk = require('chalk'); 6 | const findNodeModules = require('find-node-modules'); 7 | 8 | const signature = 'compile [solcVersion]'; 9 | const description = 'Compiles single Solidity files.'; 10 | const help = chalk` 11 | Compiles a source file with solcjs. 12 | 13 | The solcVersion parameter can be used to specify a semver version of the compiler to use. This parameter can be any valid semver expression (use quotes for special characters like ^). 14 | If the parameter is not specified, pocketh will look at the source code and use the semver from the pragma directive. 15 | Valid version specifiers: 16 | 0.5.8 17 | ^0.5.0 18 | ~0.4.24 19 | >=0.4.0 <0.6.0 20 | etc... 21 | 22 | The compilation output produces output split into several .json files, comaptible with Truffle's compiler output. 23 | 24 | {red Eg:} 25 | 26 | {blue > pocketh compile test/contracts/Test.sol test/artifacts/ 0.5.7} 27 | Retrieving list of available solcjs versions... 28 | Source version: ^0.5.7 29 | Target version: 0.5.7 30 | Using compiler 0.5.7+commit.6da8b019.Emscripten.clang 31 | Compiled Test.sol succesfully to /tmp/ 32 | 33 | {blue > pocketh compile ~/tabookey-gasless/contracts/RelayHub.sol /tmp/} 34 | Retrieving list of available solcjs versions... 35 | Source version: >=0.4.0 <0.6.0 36 | Target version: >=0.4.0 <0.6.0 37 | Using compiler 0.5.8+commit.23d335f2.Emscripten.clang 38 | Compiled RelayHub.sol succesfully to /tmp/ 39 | `; 40 | 41 | let searchPaths = [ 42 | '' 43 | ]; 44 | 45 | let sourceDir; 46 | 47 | module.exports = { 48 | signature, 49 | description, 50 | register: (program) => { 51 | program 52 | .command(signature, {noHelp: true}) 53 | .description(description) 54 | .option(`--searchPaths `, `Specify additional search paths for dependencies as a list of comma separated values. Note that search paths must be relative to sourcePath.`) 55 | .option(`--solcbin `, `Specify the url to use to fetch solcjs compiler versions.`) 56 | .option(`--getall`, `Download and cache all available compiler versions.`) 57 | .on('--help', () => console.log(help)) 58 | .action(async (sourcePath, outputDirectory, solcVersion, options) => { 59 | 60 | // Validate sourcePath. 61 | if(path.basename(sourcePath).split('.')[1] !== 'sol') 62 | throw new Error(`Invalid source file ${sourcePath}`); 63 | if(!fs.existsSync(sourcePath)) 64 | throw new Error(`Cannot find ${sourcePath}.`); 65 | 66 | // Validate output directory. 67 | if(!fs.existsSync(outputDirectory)) 68 | throw new Error(`Cannot find ${outputDirectory}.`); 69 | if(!fs.lstatSync(outputDirectory).isDirectory()) 70 | throw new Error('outputDirectory must be a directory path.'); 71 | 72 | // Parse search paths. 73 | if(options.searchPaths) { 74 | const paths = options.searchPaths.split(','); 75 | searchPaths = [...searchPaths, ...paths]; 76 | } 77 | 78 | // Retrieve contract source. 79 | if(!fs.existsSync(sourcePath)) throw new Error(`Cannot find ${sourcePath}.`); 80 | sourceDir = path.dirname(sourcePath); 81 | const filename = path.basename(sourcePath); 82 | const source = fs.readFileSync(sourcePath, 'utf8'); 83 | 84 | // Retrieve the appropriate solcjs compiler. 85 | const solc = await solcjsResolver.getCompiler(source, solcVersion, options.solcbin, options.getall); 86 | console.log(`Using compiler ${solc.version()}`); 87 | 88 | // Prepare the json standard input for the compiler. 89 | const jsonInput = jsonIO.buildStandardJsonInput(filename, source); 90 | 91 | // Compile and display errors/warnings. 92 | const output = JSON.parse(solc.compile(JSON.stringify(jsonInput), resolveImports)); 93 | if(output.errors && output.errors.length > 0) displayErrors(output.errors); 94 | 95 | // Split the output so that there is one json file per contract. 96 | const splitOutput = jsonIO.oneJsonPerContract(output, filename); 97 | 98 | // Write json files to disk. 99 | splitOutput.forEach(json => { 100 | const destPath = path.resolve(outputDirectory, json.contractName + '.json'); 101 | fs.writeFileSync(destPath, JSON.stringify(json, null, 2)); 102 | }); 103 | 104 | // Report. 105 | console.log(`Compiled ${filename} succesfully to ${outputDirectory}`); 106 | }); 107 | } 108 | }; 109 | 110 | function resolveImports(sourcePath) { 111 | 112 | // Resolve imports using each of the search paths. 113 | for(let i = 0; i < searchPaths.length; i++) { 114 | const path = searchPaths[i]; 115 | const contents = tryToResolveImport(path, sourcePath); 116 | if(contents) return { contents }; 117 | } 118 | 119 | // Try to resolve the imports using node_modules. 120 | const modules = findNodeModules({ cwd: sourceDir }); 121 | for(let i = 0; i < modules.length; i++) { 122 | const path = modules[i]; 123 | const contents = tryToResolveImport(path, sourcePath); 124 | if(contents) return { contents }; 125 | } 126 | 127 | throw new Error(`Unable to resolve import ${sourcePath}`); 128 | } 129 | 130 | function tryToResolveImport(searchPath, sourcePath) { 131 | 132 | // Normalize the path. 133 | const normPath = path.resolve(sourceDir, searchPath, sourcePath); 134 | // console.log(`Trying to resolve import at: ${normPath}`); 135 | 136 | // Read the file. 137 | if(!fs.existsSync(normPath)) return undefined; 138 | return fs.readFileSync(normPath, 'utf8'); 139 | } 140 | 141 | function displayErrors(errors) { 142 | console.log(`\nCompilation produced the following warnings/errors:\n`); 143 | errors.map(err => { 144 | const msg = err.formattedMessage; 145 | if(msg.includes('Error')) throw new Error(msg); 146 | else console.log(msg); 147 | }); 148 | } 149 | -------------------------------------------------------------------------------- /src/commands/compile/jsonIO.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 5 | buildStandardJsonInput: (filename, source) => { 6 | return { 7 | language: "Solidity", 8 | sources: { 9 | [filename]: { 10 | content: source 11 | } 12 | }, 13 | settings: { 14 | optimizer: { 15 | enabled: false, 16 | runs: 200 17 | }, 18 | outputSelection: { 19 | "*": { 20 | "*": [ 21 | "abi", 22 | "metadata", 23 | "evm.bytecode.object", 24 | "evm.bytecode.sourceMap", 25 | "evm.deployedBytecode.object", 26 | "evm.deployedBytecode.sourceMap" 27 | ], 28 | "": [ "*" ] 29 | } 30 | } 31 | } 32 | }; 33 | }, 34 | 35 | /* 36 | * Solcjs output and truffle compiler output are different. 37 | * Solcjs outputs one json file per source file (a source file may contain multiple contracts). 38 | * Truffle re-shuffles the original solcjs output and splits it into multiple json files, 39 | * one per contract. To maintain compatibility, we do the same thing here. 40 | * */ 41 | oneJsonPerContract: (output, filename) => { 42 | // require('../../utils/log.js')( JSON.stringify(output, null, 2) ); 43 | const jsons = []; 44 | const filepaths = Object.keys(output.contracts); 45 | filepaths.map(filepath => { 46 | const source = output.sources[filepath] || output.sources[filename]; 47 | const contractNames = Object.keys(output.contracts[filepath]); 48 | contractNames.map(contractName => { 49 | const contract = output.contracts[filepath][contractName]; 50 | jsons.push({ 51 | contractName, 52 | abi: contract.abi, 53 | metadata: contract.metadata, 54 | bytecode: contract.evm.bytecode.object, 55 | deployedBytecode: contract.evm.deployedBytecode.object, 56 | ast: source.ast 57 | }); 58 | }); 59 | }); 60 | return jsons; 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/commands/compile/solcjsResolver.js: -------------------------------------------------------------------------------- 1 | const vm = require('vm'); 2 | const os = require('os'); 3 | const fs = require('fs'); 4 | const axios = require('axios'); 5 | const semver = require('semver'); 6 | 7 | // Downloaded compilers will be stored here. 8 | const SOLJSON_PATH = `${os.homedir()}/.soljson/`; 9 | let solc = require('solc'); // Can be modified by downloading a new compiler snapshot. 10 | 11 | // List of available solcjs versions. 12 | let SOLC_BIN_URL = `https://solc-bin.ethereum.org/bin/`; 13 | let SOLC_BIN_LIST_URL; 14 | 15 | module.exports = { 16 | 17 | getCompiler: async (source, requiredSemver, solcbin, getall) => { 18 | 19 | // User specified solcbin url to fetch compiler? 20 | if(solcbin) SOLC_BIN_URL = solcbin; 21 | SOLC_BIN_LIST_URL = `${SOLC_BIN_URL}list.js`; 22 | 23 | const sourceSemver = detectSolcVersionFromSource(source); 24 | console.log(`Version found in sources:`, sourceSemver); 25 | 26 | // If the user specified a required version, validate it against the source. 27 | if(requiredSemver) { 28 | if(!semverVersionsIntersect(sourceSemver, requiredSemver)) 29 | throw new Error(`Required version ${requiredSemver} is not compatible with the version specified in the source code ${sourceSemver}`); 30 | } 31 | 32 | const availableVersions = await getAvailableCompilerVersions(); 33 | if(getall) await downloadAndCacheAllVerions(availableVersions); 34 | 35 | // Use specified semver, or detect it form source. 36 | if(requiredSemver) console.log(`Version required by the user:`, requiredSemver); 37 | const targetSemver = requiredSemver || sourceSemver; 38 | 39 | // Translate semver to an actual version. 40 | // E.g. '0.5.8' to 'soljson-v0.5.8+commit.23d335f2.js'. 41 | const version = findVersionFromSemver(targetSemver, availableVersions); 42 | 43 | // Retrieve compiler source. 44 | let compilerSource; 45 | if(fs.existsSync(`${SOLJSON_PATH}${version}`)) compilerSource = fs.readFileSync(`${SOLJSON_PATH}${version}`, 'utf8'); 46 | else compilerSource = await downloadAndCacheCompilerSource(version); 47 | 48 | // "Build" compiler source. 49 | solc = solc.setupMethods(requireFromString(compilerSource, version)); 50 | 51 | return solc; 52 | } 53 | }; 54 | 55 | async function downloadAndCacheAllVerions(versions) { 56 | console.log(`Downloading all available compiler versions...`); 57 | 58 | // Filter out versions that are already downloaded. 59 | let versionsToDownload = versions.filter((version) => { 60 | return !fs.existsSync(`${SOLJSON_PATH}${version}`); 61 | }); 62 | 63 | // Filter out nightly versions. 64 | versionsToDownload = versionsToDownload.filter((version) => { 65 | return !version.includes('nightly'); 66 | }); 67 | 68 | // Resolve promises sequentially. 69 | console.log(`Versions to download:`, versionsToDownload); 70 | versionsToDownload.reduce( async (previousPromise, nextVersion) => { 71 | await previousPromise; 72 | return downloadAndCacheCompilerSource(nextVersion); 73 | }, Promise.resolve()); 74 | } 75 | 76 | function semverVersionsIntersect(semver1, semver2) { 77 | const range1 = semver.Range(semver1).range; 78 | const range2 = semver.Range(semver2).range; 79 | return semver.intersects(range1, range2); 80 | } 81 | 82 | function detectSolcVersionFromSource(source) { 83 | // Return `pragma solidity ;` 84 | return source.match(/(?<=pragma solidity ).*(?=;)/gm)[0]; 85 | } 86 | 87 | async function getAvailableCompilerVersions() { 88 | console.log(`Retrieving list of available solcjs versions...`); 89 | return new Promise((resolve, reject) => { 90 | // Try to get list online... 91 | getAvailableVersionsFromSolcjsBin() 92 | .then(versions => resolve(versions)) 93 | .catch(error => { // Otherwise try to form a list from already downloaded versions... 94 | console.log(error); 95 | console.log(`Using one of the already downloaded solcjs versions...`); 96 | if(!fs.existsSync(SOLJSON_PATH)) reject('Unable to find any solcjs versions.'); 97 | else resolve(fs.readdirSync(SOLJSON_PATH)); 98 | }); 99 | }); 100 | } 101 | 102 | async function getAvailableVersionsFromSolcjsBin() { 103 | return new Promise((resolve, reject) => { 104 | axios.get(SOLC_BIN_LIST_URL) 105 | .then((result) => { 106 | if(result.status === 200) { 107 | 108 | // Retrieved text is a js file. 109 | // It needs to be executed to retrieve the list of sources. 110 | let scriptSource = result.data; 111 | const script = new vm.Script(scriptSource); 112 | const output = {}; 113 | script.runInNewContext(output); 114 | const sources = output.soljsonSources; 115 | 116 | resolve(sources); 117 | } 118 | }) 119 | .catch((error) => { 120 | reject(`Unable to retrieve available solcjs sources from ${SOLC_BIN_URL}, ${error.message}`); 121 | }); 122 | }); 123 | } 124 | 125 | function findVersionFromSemver(targetSemver, availableVersions) { 126 | 127 | // Filter out nightly versions. 128 | const candidates = availableVersions.filter(v => !v.includes('nightly')); 129 | 130 | // Map the candidates to pure semver. 131 | const versions = candidates.map(version => { 132 | // Return `soljson-.js;` 133 | return semver.clean(version.match(/(?<=soljson-).*(?=\.js)/)[0]); 134 | }); 135 | 136 | // Find best fit. 137 | const best = semver.maxSatisfying(versions, targetSemver); 138 | const idx = versions.indexOf(best); 139 | if(idx >= 0) return candidates[idx]; 140 | 141 | // Nothing found =( 142 | throw new Error(`No available version satisfies the required version ${targetSemver}`); 143 | } 144 | 145 | function requireFromString(src, filename) { 146 | var Module = module.constructor; 147 | var m = new Module(); 148 | m._compile(src, filename); 149 | return m.exports; 150 | } 151 | 152 | async function downloadAndCacheCompilerSource(version) { 153 | console.log(`Downloading compiler ${version}...`); 154 | return new Promise((resolve, reject) => { 155 | const url = `${SOLC_BIN_URL}${version}`; 156 | axios.get(url) 157 | .then((result) => { 158 | if(result.status === 200) { 159 | 160 | // Store downloaded compiler source for future usage. 161 | const compilerSource = result.data; 162 | const path = `${SOLJSON_PATH}${version}`; 163 | if(!fs.existsSync(SOLJSON_PATH)) fs.mkdirSync(SOLJSON_PATH); 164 | fs.writeFileSync(path, compilerSource); 165 | console.log(`Compiler stored in ${path}`); 166 | 167 | resolve(compilerSource); 168 | } 169 | }) 170 | .catch((error) => { 171 | reject(`Unable to download compiler ${url}, ${error.message}`); 172 | }); 173 | }); 174 | } 175 | -------------------------------------------------------------------------------- /src/commands/convert.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const chalk = require('chalk'); 3 | 4 | const signature = 'convert [value] [sourceDenom] [destDenom]'; 5 | const description = 'Converts between ether denominations.'; 6 | const help = chalk` 7 | Converts a given Ether denomination into another. If no value is specified, will list all denominations. Default sourceDenom is wei. Default destDenom is ether. 8 | 9 | {red Eg:} 10 | 11 | {blue > pocketh convert 1000000 wei ether} 12 | 0.000000000001 ether 13 | 14 | `; 15 | 16 | module.exports = { 17 | signature, 18 | description, 19 | register: (program) => { 20 | program 21 | .command(signature, {noHelp: true}) 22 | .description(description) 23 | .on('--help', () => console.log(help)) 24 | .action((value, sourceDenom, destDenom) => { 25 | const web3 = new Web3(); 26 | 27 | // If no value is given, list all denominations. 28 | if(!value) { 29 | console.log(units); 30 | return; 31 | } 32 | 33 | // Default denominations. 34 | sourceDenom = sourceDenom || 'wei'; 35 | destDenom = destDenom || 'ether'; 36 | 37 | // Identify units. 38 | if(!units[sourceDenom]) throw new Error(`Unrecognized source denomination: ${sourceDenom}.`); 39 | if(!units[destDenom]) throw new Error(`Unrecognized dest denomination: ${destDenom}.`); 40 | 41 | // Convert source to wei. 42 | const sourceWei = web3.utils.toWei(value, sourceDenom); 43 | 44 | // Convert source to dest. 45 | const dest = web3.utils.fromWei(sourceWei, destDenom); 46 | 47 | // Output. 48 | console.log(`${dest} ${destDenom}`); 49 | }); 50 | } 51 | }; 52 | 53 | const units = { 54 | wei: '1', 55 | kwei: '1000', 56 | Kwei: '1000', 57 | babbage: '1000', 58 | femtoether: '1000', 59 | mwei: '1000000', 60 | Mwei: '1000000', 61 | lovelace: '1000000', 62 | picoether: '1000000', 63 | gwei: '1000000000', 64 | Gwei: '1000000000', 65 | shannon: '1000000000', 66 | nanoether: '1000000000', 67 | nano: '1000000000', 68 | szabo: '1000000000000', 69 | microether: '1000000000000', 70 | micro: '1000000000000', 71 | finney: '1000000000000000', 72 | milliether: '1000000000000000', 73 | milli: '1000000000000000', 74 | ether: '1000000000000000000', 75 | kether: '1000000000000000000000', 76 | grand: '1000000000000000000000', 77 | mether: '1000000000000000000000000', 78 | gether: '1000000000000000000000000000', 79 | tether: '1000000000000000000000000000000' 80 | } 81 | -------------------------------------------------------------------------------- /src/commands/disassemble.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const getWeb3 = require('../utils/getWeb3.js'); 4 | const getArtifacts = require('../utils/getArtifacts'); 5 | const chalk = require('chalk'); 6 | 7 | const signature = 'disassemble '; 8 | const description = 'Disassembles bytecode to EVM opcodes.'; 9 | const help = chalk` 10 | Disassembles the provided contract (compiled or not) to EVM opcodes with additional information. 11 | 12 | Nomenclature: 13 | \{\} [c, r] () 14 | 15 | {red Eg:} 16 | 17 | {blue > pocketh disassemble test/artifacts/Test.json} 18 | 0 \{0x80\} [c0] DUP1 19 | 1 \{0x60\} [c1] PUSH1 0x40 (dec 64) 20 | 2 \{0x52\} [c3] MSTORE 21 | 3 \{0x34\} [c4] CALLVALUE 22 | 4 \{0x80\} [c5] DUP1 23 | 5 \{0x15\} [c6] ISZERO 24 | 6 \{0x61\} [c7] PUSH2 0x0010 (dec 16) 25 | 7 \{0x57\} [c10] JUMPI 26 | 8 \{0x60\} [c11] PUSH1 0x00 (dec 0) 27 | 9 \{0x80\} [c13] DUP1 28 | 10 \{0xfd\} [c14] REVERT 29 | ... 30 | 74 \{0x80\} [c126, r86] DUP1 31 | 75 \{0x63\} [c127, r87] PUSH4 0xf0686273 (dec 4033372787) 32 | 76 \{0x14\} [c132, r92] EQ 33 | 77 \{0x61\} [c133, r93] PUSH2 0x010d (dec 269) 34 | 78 \{0x57\} [c136, r96] JUMPI 35 | 79 \{0x5b\} [c137, r97] JUMPDEST 36 | 80 \{0x60\} [c138, r98] PUSH1 0x00 (dec 0) 37 | 81 \{0x80\} [c140, r100] DUP1 38 | 82 \{0xfd\} [c141, r101] REVERT 39 | 83 \{0x5b\} [c142, r102] JUMPDEST 40 | ... 41 | `; 42 | 43 | module.exports = { 44 | signature, 45 | description, 46 | register: (program) => { 47 | program 48 | .command(signature, {noHelp: true}) 49 | .description(description) 50 | .on('--help', () => console.log(help)) 51 | .action(async (contractPath) => { 52 | 53 | // Retrieve contract artifacts and abi. 54 | const { artifacts } = await getArtifacts(contractPath); 55 | 56 | // Retrieve bytecode. 57 | const output = disassemble( 58 | artifacts.bytecode, 59 | artifacts.deployedBytecode 60 | ); 61 | process.stdout.write(output); 62 | }); 63 | 64 | let opcodes; 65 | 66 | function disassemble(bytecode, deployedBytecode) { 67 | 68 | // Remove '0x'. 69 | bytecode = bytecode.substring(2, bytecode.length - 1); 70 | deployedBytecode = deployedBytecode.substring(2, deployedBytecode.length - 1); 71 | 72 | // Calculate deployed bytecode offset. 73 | const bytecodeLen = bytecode.length / 2; 74 | const deployedBytecodeLen = deployedBytecode.length / 2; 75 | const runtimeOffset = bytecodeLen - deployedBytecodeLen; 76 | 77 | let res = ''; // Will contain the disassembled output. 78 | let currentInstructionIdx = 0; 79 | let currentByteIdx = 0; 80 | let offset = 0; 81 | 82 | // Sweep bytecode string. 83 | while(offset < bytecode.length) { 84 | 85 | // Append line break. 86 | if(currentInstructionIdx !== 0) { 87 | res += '\n'; 88 | } 89 | 90 | // Grab the next substring. 91 | const hex = bytecode.substring(offset, offset + 2); 92 | offset += 2; 93 | 94 | // Convert the opcode from hex. 95 | const opcode = hexToOpcode(hex); 96 | 97 | // Binary opcode like PUSH? 98 | const binaryLen = binaryChunkLength(opcode); 99 | if(binaryLen > 0) { 100 | 101 | // Read number. 102 | const num = bytecode.substring(offset, offset + binaryLen); 103 | offset += binaryLen; 104 | 105 | // Build output. 106 | const offsetStr = currentByteIdx < runtimeOffset ? '' : `, r${currentByteIdx - runtimeOffset}`; 107 | res += `${currentInstructionIdx} {0x${hex}} [c${currentByteIdx}${offsetStr}] ${opcode} 0x${num} (dec ${parseInt(num, 16)})`; 108 | currentByteIdx += 1 + binaryLen / 2; 109 | } 110 | else { 111 | 112 | // Build output. 113 | const offsetStr = currentByteIdx < runtimeOffset ? '' : `, r${currentByteIdx - runtimeOffset}`; 114 | res += `${currentInstructionIdx} {0x${hex}} [c${currentByteIdx}${offsetStr}] ${opcode}`; 115 | currentByteIdx++; 116 | } 117 | currentInstructionIdx++; 118 | } 119 | 120 | return res; 121 | } 122 | 123 | function binaryChunkLength(opcode) { 124 | const isPush = opcode.includes('PUSH'); 125 | if(isPush) { 126 | const len = opcode.split('PUSH')[1]; 127 | return parseInt(len * 2, 10); 128 | } 129 | else return 0; 130 | } 131 | 132 | function hexToOpcode(hex) { 133 | if(!opcodes) opcodes = buildOpcodes(); 134 | return opcodes[hex] || `INVALID (${hex})`; 135 | } 136 | 137 | function buildOpcodes() { 138 | 139 | const opcodes = { 140 | '00': 'STOP', 141 | '01': 'ADD', 142 | '02': 'MUL', 143 | '03': 'SUB', 144 | '04': 'DIV', 145 | '05': 'SDIV', 146 | '06': 'MOD', 147 | '07': 'SMOD', 148 | '08': 'ADDMOD', 149 | '09': 'MULMOD', 150 | '0a': 'EXP', 151 | '0b': 'SIGNEXTEND', 152 | '10': 'LT', 153 | '11': 'GT', 154 | '12': 'SLT', 155 | '13': 'SGT', 156 | '14': 'EQ', 157 | '15': 'ISZERO', 158 | '16': 'AND', 159 | '17': 'OR', 160 | '18': 'XOR', 161 | '19': 'NOT', 162 | '1a': 'BYTE', 163 | '20': 'SHA3', 164 | '30': 'ADDRESS', 165 | '31': 'BALANCE', 166 | '32': 'ORIGIN', 167 | '33': 'CALLER', 168 | '34': 'CALLVALUE', 169 | '35': 'CALLDATALOAD', 170 | '36': 'CALLDATASIZE', 171 | '37': 'CALLDATACOPY', 172 | '38': 'CODESIZE', 173 | '39': 'CODECOPY', 174 | '3a': 'GASPRICE', 175 | '3b': 'EXTCODESIZE', 176 | '3c': 'EXTCODECOPY', 177 | '3d': 'RETURNDATASIZE', 178 | '3e': 'RETURNDATACOPY', 179 | '40': 'BLOCKHASH', 180 | '41': 'COINBASE', 181 | '42': 'TIMESTAMP', 182 | '43': 'NUMBER', 183 | '44': 'DIFFICULTY', 184 | '45': 'GASLIMIT', 185 | '50': 'POP', 186 | '51': 'MLOAD', 187 | '52': 'MSTORE', 188 | '53': 'MSTORE8', 189 | '54': 'SLOAD', 190 | '55': 'SSTORE', 191 | '56': 'JUMP', 192 | '57': 'JUMPI', 193 | '58': 'PC', 194 | '59': 'MSIZE', 195 | '5a': 'GAS', 196 | '5b': 'JUMPDEST', 197 | 'a0': 'LOG0', 198 | 'a1': 'LOG1', 199 | 'a2': 'LOG2', 200 | 'a3': 'LOG3', 201 | 'a4': 'LOG4', 202 | 'f0': 'CREATE', 203 | 'f1': 'CALL', 204 | 'f2': 'CALLCODE', 205 | 'f3': 'RETURN', 206 | 'f4': 'DELEGATECALL', 207 | 'f5': 'CALLBLACKBOX', 208 | 'fa': 'STATICCALL', 209 | 'fd': 'REVERT', 210 | 'ff': 'SUICIDE' 211 | }; 212 | 213 | // PUSH[1-32] opcodes. 214 | for(let i = 1; i <= 32; i++) { 215 | let hexKey = (parseInt('60', 16) -1 + i).toString(16); // Convert to decimal for addition, and return to hex. 216 | opcodes[hexKey] = `PUSH${i}`; 217 | } 218 | 219 | // DUP[1-16] opcodes. 220 | for(let i = 1; i <= 16; i++) { 221 | let hexKey = (parseInt('80', 16) -1 + i).toString(16); 222 | opcodes[hexKey] = `DUP${i}`; 223 | } 224 | 225 | // SWAP[1-16] opcodes. 226 | for(let i = 1; i <= 16; i++) { 227 | let hexKey = (parseInt('90', 16) -1 + i).toString(16); 228 | opcodes[hexKey] = `SWAP${i}`; 229 | } 230 | 231 | return opcodes; 232 | } 233 | } 234 | }; 235 | -------------------------------------------------------------------------------- /src/commands/docyul.js: -------------------------------------------------------------------------------- 1 | const signature = 'docyul [keyword]'; 2 | const description = 'Gets yul documentation.'; 3 | const chalk = require('chalk'); 4 | const help = chalk` 5 | Gets yul documentation for a given keyword. If no keyword is provided, all available keywords are listed 6 | 7 | {red Eg:} 8 | 9 | {blue > pocketh docyul sstore} 10 | sstore(p:u256, v:u256) 11 | "storage[p] := v" 12 | 13 | {blue > pocketh docyul} 14 | not(x:bool) ‑> z:bool 15 | and(x:bool, y:bool) ‑> z:bool 16 | or(x:bool, y:bool) ‑> z:bool 17 | xor(x:bool, y:bool) ‑> z:bool 18 | addu256(x:u256, y:u256) ‑> z:u256 19 | subu256(x:u256, y:u256) ‑> z:u256 20 | mulu256(x:u256, y:u256) ‑> z:u256 21 | divu256(x:u256, y:u256) ‑> z:u256 22 | divs256(x:s256, y:s256) ‑> z:s256 23 | modu256(x:u256, y:u256) ‑> z:u256 24 | mods256(x:s256, y:s256) ‑> z:s256 25 | ... 26 | 27 | `; 28 | 29 | module.exports = { 30 | signature, 31 | description, 32 | register: (program) => { 33 | program 34 | .command(signature, {noHelp: true}) 35 | .description(description) 36 | .on('--help', () => console.log(help)) 37 | .action((keyword) => { 38 | 39 | const keys = Object.keys(docs); 40 | 41 | if(!keyword) { 42 | let str = ''; 43 | keys.forEach(key => str += `${key}\n`); 44 | console.log(str); 45 | } 46 | else { 47 | const matches = keys.filter(key => key.includes(keyword)); 48 | if(matches.length === 0) { 49 | return console.log(`No Yul documentation found for '${keyword}'`); 50 | } 51 | matches.forEach(match => { 52 | console.log(`${match}\n"${docs[match]}"\n`); 53 | }); 54 | } 55 | }); 56 | } 57 | }; 58 | 59 | const docs = { 60 | "not(x:bool) ‑> z:bool": "logical not", 61 | "and(x:bool, y:bool) ‑> z:bool": "logical and", 62 | "or(x:bool, y:bool) ‑> z:bool": "logical or", 63 | "xor(x:bool, y:bool) ‑> z:bool": "xor", 64 | "addu256(x:u256, y:u256) ‑> z:u256": "x + y", 65 | "subu256(x:u256, y:u256) ‑> z:u256": "x - y", 66 | "mulu256(x:u256, y:u256) ‑> z:u256": "x * y", 67 | "divu256(x:u256, y:u256) ‑> z:u256": "x / y", 68 | "divs256(x:s256, y:s256) ‑> z:s256": "x / y, for signed numbers in two’s complement", 69 | "modu256(x:u256, y:u256) ‑> z:u256": "x % y", 70 | "mods256(x:s256, y:s256) ‑> z:s256": "x % y, for signed numbers in two’s complement", 71 | "signextendu256(i:u256, x:u256) ‑> z:u256": "sign extend from (i*8+7)th bit counting from least significant", 72 | "expu256(x:u256, y:u256) ‑> z:u256": "x to the power of y", 73 | "addmodu256(x:u256, y:u256, m:u256) ‑> z:u256": "(x + y) % m with arbitrary precision arithmetic", 74 | "mulmodu256(x:u256, y:u256, m:u256) ‑> z:u256": "(x * y) % m with arbitrary precision arithmetic", 75 | "ltu256(x:u256, y:u256) ‑> z:bool": "true if x < y, false otherwise", 76 | "gtu256(x:u256, y:u256) ‑> z:bool": "true if x > y, false otherwise", 77 | "lts256(x:s256, y:s256) ‑> z:bool": "true if x < y, false otherwise (for signed numbers in two’s complement)", 78 | "gts256(x:s256, y:s256) ‑> z:bool": "true if x > y, false otherwise (for signed numbers in two’s complement)", 79 | "equ256(x:u256, y:u256) ‑> z:bool": "true if x == y, false otherwise", 80 | "iszerou256(x:u256) ‑> z:bool": "true if x == 0, false otherwise", 81 | "notu256(x:u256) ‑> z:u256": "~x, every bit of x is negated", 82 | "andu256(x:u256, y:u256) ‑> z:u256": "bitwise and of x and y", 83 | "oru256(x:u256, y:u256) ‑> z:u256": "bitwise or of x and y", 84 | "xoru256(x:u256, y:u256) ‑> z:u256": "bitwise xor of x and y", 85 | "shlu256(x:u256, y:u256) ‑> z:u256": "logical left shift of x by y", 86 | "shru256(x:u256, y:u256) ‑> z:u256": "logical right shift of x by y", 87 | "sars256(x:s256, y:u256) ‑> z:u256": "arithmetic right shift of x by y", 88 | "byte(n:u256, x:u256) ‑> v:u256": "nth byte of x, where the most significant byte is the 0th byte Cannot this be just replaced by and256(shr256(n, x), 0xff) and let it be optimised out by the EVM backend?", 89 | "mload(p:u256) ‑> v:u256": "mem[p..(p+32))", 90 | "mstore(p:u256, v:u256)": "mem[p..(p+32)) := v", 91 | "mstore8(p:u256, v:u256)": "mem[p] := v & 0xff - only modifies a single byte", 92 | "sload(p:u256) ‑> v:u256": "storage[p]", 93 | "sstore(p:u256, v:u256)": "storage[p] := v", 94 | "msize() ‑> size:u256": "size of memory, i.e. largest accessed memory index, albeit due due to the memory extension function, which extends by words, this will always be a multiple of 32 bytes", 95 | "create(v:u256, p:u256, n:u256)": "create new contract with code mem[p..(p+n)) and send v wei and return the new address", 96 | "create2(v:u256, p:u256, n:u256, s:u256)": "create new contract with code mem[p…(p+n)) at address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and send v wei and return the new address, where 0xff is a 8 byte value, this is the current contract’s address as a 20 byte value and s is a big-endian 256-bit value", 97 | "call(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) ‑> r:u256": "call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success", 98 | "callcode(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) ‑> r:u256": "identical to call but only use the code from a and stay in the context of the current contract otherwise", 99 | "delegatecall(g:u256, a:u256, in:u256, insize:u256, out:u256, outsize:u256) ‑> r:u256": "identical to callcode, but also keep caller and callvalue", 100 | "abort()": "abort (equals to invalid instruction on EVM)", 101 | "return(p:u256, s:u256)": "end execution, return data mem[p..(p+s))", 102 | "revert(p:u256, s:u256)": "end execution, revert state changes, return data mem[p..(p+s))", 103 | "selfdestruct(a:u256)": "end execution, destroy current contract and send funds to a", 104 | "log0(p:u256, s:u256)": "log without topics and data mem[p..(p+s))", 105 | "log1(p:u256, s:u256, t1:u256)": "log with topic t1 and data mem[p..(p+s))", 106 | "log2(p:u256, s:u256, t1:u256, t2:u256)": "log with topics t1, t2 and data mem[p..(p+s))", 107 | "log3(p:u256, s:u256, t1:u256, t2:u256, t3:u256)": "log with topics t, t2, t3 and data mem[p..(p+s))", 108 | "log4(p:u256, s:u256, t1:u256, t2:u256, t3:u256, t4:u256)": "log with topics t1, t2, t3, t4 and data mem[p..(p+s))", 109 | "blockcoinbase() ‑> address:u256": "current mining beneficiary", 110 | "blockdifficulty() ‑> difficulty:u256": "difficulty of the current block", 111 | "blockgaslimit() ‑> limit:u256": "block gas limit of the current block", 112 | "blockhash(b:u256) ‑> hash:u256": "hash of block nr b - only for last 256 blocks excluding current", 113 | "blocknumber() ‑> block:u256": "current block number", 114 | "blocktimestamp() ‑> timestamp:u256": "timestamp of the current block in seconds since the epoch", 115 | "txorigin() ‑> address:u256": "transaction sender", 116 | "txgasprice() ‑> price:u256": "gas price of the transaction", 117 | "gasleft() ‑> gas:u256": "gas still available to execution", 118 | "balance(a:u256) ‑> v:u256": "wei balance at address a", 119 | "this() ‑> address:u256": "address of the current contract / execution context", 120 | "caller() ‑> address:u256": "call sender (excluding delegatecall)", 121 | "callvalue() ‑> v:u256": "wei sent together with the current call", 122 | "calldataload(p:u256) ‑> v:u256": "call data starting from position p (32 bytes)", 123 | "calldatasize() ‑> v:u256": "size of call data in bytes", 124 | "calldatacopy(t:u256, f:u256, s:u256)": "copy s bytes from calldata at position f to mem at position t", 125 | "codesize() ‑> size:u256": "size of the code of the current contract / execution context", 126 | "codecopy(t:u256, f:u256, s:u256)": "copy s bytes from code at position f to mem at position t", 127 | "extcodesize(a:u256) ‑> size:u256": "size of the code at address a", 128 | "extcodecopy(a:u256, t:u256, f:u256, s:u256)": "like codecopy(t, f, s) but take code at address a", 129 | "extcodehash(a:u256)": "code hash of address a", 130 | "discard(unused:bool)": "discard value", 131 | "discardu256(unused:u256)": "discard value", 132 | "splitu256tou64(x:u256) ‑> (x1:u64, x2:u64, x3:u64, x4:u64)": "split u256 to four u64’s", 133 | "combineu64tou256(x1:u64, x2:u64, x3:u64, x4:u64) ‑> (x:u256)": "combine four u64’s into a single u256", 134 | "keccak256(p:u256, s:u256) ‑> v:u256": "keccak(mem[p…(p+s)))", 135 | "datasize(name:string) ‑> size:u256": "size of the data object in bytes, name has to be string literal", 136 | "dataoffset(name:string) ‑> offset:u256": "offset of the data object inside the data area in bytes, name has to be string literal", 137 | "datacopy(dst:u256, src:u256, len:u256)": "copy len bytes from the data area starting at offset src bytes to memory at position dst" 138 | }; 139 | -------------------------------------------------------------------------------- /src/commands/getcode.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const etherscanApi = require('../utils/etherscanApi'); 3 | const validateUtil = require('../utils/validateUtil'); 4 | const fs = require('fs'); 5 | 6 | const signature = 'getcode [targetFilePath]'; 7 | const description = 'Retrieves a contract\'s code from Etherscan.'; 8 | const help = chalk` 9 | Retrieves a contract's source from Etherscan (mainnet only). 10 | If a targetFilePath is specified, contents will be written to the specified file, otherwise they will simply be sent to stdout. 11 | 12 | {red Eg:} 13 | 14 | {blue > pocketh getcode 0x06012c8cf97bead5deae237070f9587f8e7a266d kit.sol} 15 | Retrieving source code at 0x06012c8cf97bead5deae237070f9587f8e7a266d from etherscan... 16 | Source code written to kit.sol 17 | 18 | `; 19 | 20 | module.exports = { 21 | signature, 22 | description, 23 | register: (program) => { 24 | program 25 | .command(signature, {noHelp: true}) 26 | .description(description) 27 | .on('--help', () => console.log(help)) 28 | .action(async (contractAddress, targetFilePath) => { 29 | 30 | // Input validation. 31 | if(!validateUtil.address(contractAddress)) 32 | throw new Error(`Invalid contract address: ${contractAddress}`); 33 | 34 | // Retrieve the code. 35 | console.log(`Retrieving source code at ${contractAddress} from etherscan...`); 36 | const code = await etherscanApi.getSourceCode(contractAddress); 37 | 38 | // Write code to file, or send it ot stdout. 39 | if(targetFilePath) { 40 | fs.writeFileSync(targetFilePath, code); 41 | console.log(`Source code written to ${targetFilePath}`); 42 | } 43 | else console.log(code); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/commands/hex2int.js: -------------------------------------------------------------------------------- 1 | const stringUtil = require('../utils/stringUtil.js'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const abiUtil = require('../utils/abiUtil.js'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'hex2int '; 7 | const description = 'Converts hex to int.'; 8 | const help = chalk` 9 | Converts a hex number to its integer base 10 representation. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh hex2int 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6} 14 | -42 15 | `; 16 | 17 | module.exports = { 18 | signature, 19 | description, 20 | register: (program) => { 21 | program 22 | .command(signature, {noHelp: true}) 23 | .description(description) 24 | .on('--help', () => console.log(help)) 25 | .action((hexString) => { 26 | 27 | // Input validation. 28 | if(!validateUtil.hex(hexString)) 29 | throw new Error(`Invalid hex string: ${hexString}`); 30 | 31 | const decString = abiUtil.parseVariableValue('int', hexString); 32 | console.log(decString); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/hex2str.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'hex2str '; 6 | const description = 'Converts hex to string.'; 7 | const help = chalk` 8 | Converts a hex string to its ascii representation. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh hex2str 0x48656c6c6f} 13 | Hello 14 | `; 15 | 16 | module.exports = { 17 | signature, 18 | description, 19 | register: (program) => { 20 | program 21 | .command(signature, {noHelp: true}) 22 | .description(description) 23 | .on('--help', () => console.log(help)) 24 | .action((hexString) => { 25 | 26 | // Input validation. 27 | if(!validateUtil.hex(hexString)) 28 | throw new Error(`Invalid hex string: ${hexString}`); 29 | 30 | const web3 = new Web3(); 31 | const asciiString = web3.utils.toAscii(hexString); 32 | console.log(`${asciiString}`); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/hex2uint.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js'); 2 | const stringUtil = require('../utils/stringUtil.js'); 3 | const validateUtil = require('../utils/validateUtil'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'hex2uint '; 7 | const description = 'Converts hex to uint.'; 8 | const help = chalk` 9 | Converts a hex number to its positive integer base 10 representation. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh hex2uint 0x7a120} 14 | 500000 15 | `; 16 | 17 | module.exports = { 18 | signature, 19 | description, 20 | register: (program) => { 21 | program 22 | .command(signature, {noHelp: true}) 23 | .description(description) 24 | .on('--help', () => console.log(help)) 25 | .action((hexString) => { 26 | 27 | // Input validation. 28 | if(!validateUtil.hex(hexString)) 29 | throw new Error(`Invalid hex string: ${hexString}`); 30 | 31 | const decString = (new BN(stringUtil.remove0x(hexString), 16)).toString(10); 32 | console.log(decString); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/info.js: -------------------------------------------------------------------------------- 1 | const getWeb3 = require('../utils/getWeb3'); 2 | const BN = require('bn.js'); 3 | const chalk = require('chalk'); 4 | const etherscanApi = require('../utils/etherscanApi'); 5 | 6 | const signature = 'info '; 7 | const description = 'Retrieves info about a network.'; 8 | const help = chalk` 9 | Retrieves info about a network using web3 and Etherscan. 10 | NOTE: Etherscan info is only available if networkUrlOrName is 'mainnet'. 11 | 12 | {red Eg:} 13 | 14 | {blue > pocketh info mainnet} 15 | Collecting network info for mainnet... 16 | latestBlock: 7823193 17 | ethPrice: 253.35 USD 18 | `; 19 | 20 | module.exports = { 21 | signature, 22 | description, 23 | register: (program) => { 24 | program 25 | .command(signature, {noHelp: true}) 26 | .description(description) 27 | .on('--help', () => console.log(help)) 28 | .action(async (networkUrlOrName) => { 29 | console.log(`Collecting network info for ${networkUrlOrName}...`); 30 | 31 | // All info will be collected here. 32 | const info = {}; 33 | 34 | // Collect info with web3 connection. 35 | const web3 = await getWeb3(networkUrlOrName); 36 | info.latestBlock = await web3.eth.getBlockNumber(); 37 | 38 | // Collect info with etherscan. 39 | if(networkUrlOrName === 'mainnet') { 40 | info.ethPrice = `${await etherscanApi.getEtherPrice()} USD`; 41 | } 42 | 43 | // Output collected info. 44 | Object.keys(info).map(key => { 45 | console.log(` ${key}: ${info[key]}`); 46 | }); 47 | }); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/commands/inheritance.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const treeify = require('treeify'); 3 | const getArtifacts = require('../utils/getArtifacts'); 4 | const astUtil = require('../utils/astUtil'); 5 | const chalk = require('chalk'); 6 | 7 | const signature = 'inheritance '; 8 | const description = 'Displays the inheritance tree of a contract.'; 9 | const help = chalk` 10 | Displays the inheritance tree of the provided contract (compiled or not). 11 | 12 | {red Eg:} 13 | 14 | {blue > pocketh inheritance test/artifacts/Test.json } 15 | └─ Test 16 | ├─ Parent1 17 | │ └─ GrandParent 18 | └─ Parent2 19 | `; 20 | 21 | const tree = {}; 22 | 23 | module.exports = { 24 | signature, 25 | description, 26 | register: (program) => { 27 | program 28 | .command(signature, {noHelp: true}) 29 | .description(description) 30 | .on('--help', () => console.log(help)) 31 | .option(`--linearized`, `linearize inheritance tree`) 32 | .action(async (contractPath, options) => { 33 | 34 | // Retrieve contract artifacts. 35 | const { artifacts, basedir } = await getArtifacts(contractPath); 36 | 37 | // Retrieve the ast. 38 | const ast = artifacts.ast; 39 | if(!ast) throw new Error('AST data not found.'); 40 | 41 | // Retrieve the target contract definition node. 42 | const rootContractName = path.basename(contractPath).split('.')[0]; 43 | const rootContractDefinition = astUtil.findNodeWithTypeAndName(ast, 'ContractDefinition', rootContractName); 44 | 45 | // Start the inheritance tree structure. 46 | tree[rootContractName] = {}; 47 | if(options.linearized) await processAllBaseContractsFromContractDefinition(ast, rootContractDefinition, basedir); 48 | else await traverseContractParents(ast, rootContractDefinition, tree[rootContractName], basedir); 49 | 50 | // Print tree after all branches 51 | // have been traversed. 52 | console.log(treeify.asTree(tree, true)); 53 | }); 54 | } 55 | }; 56 | 57 | async function processAllBaseContractsFromContractDefinition(ast, contractDefinition, basedir) { 58 | 59 | // Retrieve the linearized base contract nodes of the contract. 60 | const linearizedContractDefs = await astUtil.getLinearizedBaseContractNodes(ast, contractDefinition, basedir); 61 | 62 | // Traverse each base contract in the linearized order, and process their variables. 63 | for(let i = 1; i < linearizedContractDefs.length; i++) { 64 | const contractDefinition = linearizedContractDefs[i]; 65 | if(contractDefinition) { 66 | tree[contractDefinition.name] = {}; 67 | } 68 | else console.log("WARNING: Contract definition not found for a base contract."); 69 | } 70 | } 71 | 72 | async function traverseContractParents(ast, contractDefinition, branch, basedir) { 73 | const parents = contractDefinition.baseContracts; 74 | for(let i = 0; i < parents.length; i++) { 75 | 76 | const parent = parents[i]; 77 | const parentName = parent.baseName.name; 78 | branch[parentName] = {}; 79 | 80 | let parentDefinition = astUtil.findNodeWithTypeAndName(ast, 'ContractDefinition', parentName); 81 | if(parentDefinition) await traverseContractParents(ast, parentDefinition, branch[parentName], basedir); 82 | else { // Target definition may be in another file. 83 | const baseContractPath = `${basedir}/${parentName}.json`; 84 | const { artifacts }= await getArtifacts(baseContractPath); 85 | const baseContractAst = artifacts.ast; 86 | if(!baseContractAst) throw new Error('AST data not found.'); 87 | parentDefinition = astUtil.findNodeWithTypeAndName(baseContractAst, 'ContractDefinition', parentName); 88 | if(parentDefinition) await traverseContractParents(ast, parentDefinition, branch[parentName], basedir); 89 | else throw new Error(`Parent contract definition not found: ${parentName}`); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/commands/int2hex.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const BN = require('bn.js'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'int2hex '; 7 | const description = 'Converts int to hex.'; 8 | const help = chalk` 9 | Converts an integer in base 10 to its hexadecimal representation. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh int2hex -n 42} 14 | 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6 15 | `; 16 | 17 | module.exports = { 18 | signature, 19 | description, 20 | register: (program) => { 21 | program 22 | .command(signature, {noHelp: true}) 23 | .description(description) 24 | .on('--help', () => console.log(help)) 25 | .option('-n, --negative', 'Negative number.') 26 | .action((decNumber, options) => { 27 | 28 | // Input validation. 29 | if(options.negative) decNumber = `-${decNumber}`; 30 | if(!validateUtil.integer(decNumber)) 31 | throw new Error(`Invalid integer: ${decNumber}`); 32 | 33 | const web3 = new Web3(); 34 | const hexString = (new BN(decNumber, 10)).toTwos(256).toString(16); 35 | console.log('0x' + hexString); 36 | }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/commands/liststorage.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const getWeb3 = require('../utils/getWeb3.js'); 4 | const BN = require('bn.js'); 5 | const getArtifacts = require('../utils/getArtifacts.js'); 6 | const astUtil = require('../utils/astUtil.js'); 7 | const abiUtil = require('../utils/abiUtil.js'); 8 | const chalk = require('chalk'); 9 | const validateUtil = require('../utils/validateUtil'); 10 | 11 | const signature = 'liststorage '; 12 | const description = 'Reads the storage of a contract.'; 13 | const help = chalk` 14 | Query the storage of a contract deployed at a given address. 15 | 16 | {red Eg:} 17 | 18 | {blue > pocketh liststorage mainnet ~/tmp/artifacts/ANT.json 0x960b236A07cf122663c4303350609A66A7B288C0} 19 | mapping(address => struct MiniMeIrrevocableVestedToken.TokenGrant[]) public grants; 20 | size: 32 bytes 21 | slot: 0 22 | word: 000000000000000000000000d39902f046b5885d70e9e66594b65f84d4d1c952 23 | subword: 000000000000000000000000d39902f046b5885d70e9e66594b65f84d4d1c952 24 | value: dynamic value 25 | mapping(address => bool) canCreateGrants; 26 | size: 32 bytes 27 | slot: 1 28 | word: 417261676f6e204e6574776f726b20546f6b656e000000000000000000000028 29 | subword: 417261676f6e204e6574776f726b20546f6b656e000000000000000000000028 30 | value: dynamic value 31 | address vestingWhitelister; 32 | size: 20 bytes 33 | slot: 2 34 | word: 0000000000000000000000000000000000000000000000000000000000000012 35 | subword: 0000000000000000000000000000000000000012 36 | value: 0x0000000000000000000000000000000000000012 37 | string public name; 38 | size: 32 bytes 39 | slot: 3 40 | word: 414e540000000000000000000000000000000000000000000000000000000006 41 | subword: 414e5400000000000000000000000000000000000000000000000000000000 42 | value: ANT 43 | ... 44 | `; 45 | 46 | let slot = 0; 47 | let rightOffset = 0; 48 | 49 | module.exports = { 50 | signature, 51 | description, 52 | register: (program) => { 53 | program 54 | .command(signature, {noHelp: true}) 55 | .description(description) 56 | .on('--help', () => console.log(help)) 57 | .action(async (networkUrl, contractPath, contractAddress) => { 58 | chalk.enabled = !program.disableColors; 59 | 60 | // Input validation. 61 | if(!validateUtil.address(contractAddress)) 62 | throw new Error(`Invalid address: ${contractAddress}`); 63 | 64 | // Connect to network. 65 | const web3 = await getWeb3(networkUrl); 66 | 67 | // Retrieve contract artifacts. 68 | const { artifacts, basedir }= await getArtifacts(contractPath); 69 | 70 | // Retrieve the ast. 71 | const ast = artifacts.ast; 72 | if(!ast) throw new Error('AST data not found.'); 73 | 74 | // Retrieve the target contract definition node. 75 | const rootContractName = path.basename(contractPath).split('.')[0]; 76 | const rootContraContractDefinition = astUtil.findNodeWithTypeAndName(ast, 'ContractDefinition', rootContractName); 77 | 78 | // Retrieve the linearized base contract nodes of the contract. 79 | const linearizedContractDefs = await astUtil.getLinearizedBaseContractNodes(ast, rootContraContractDefinition, basedir); 80 | 81 | // Traverse each base contract in the linearized order, and process their variables. 82 | for(let i = 0; i < linearizedContractDefs.length; i++) { 83 | const contractDefinition = linearizedContractDefs[linearizedContractDefs.length - i - 1]; 84 | await processAllVariableDeclarationsInContractDefinition( 85 | contractDefinition, 86 | contractAddress, 87 | web3 88 | ); 89 | } 90 | }); 91 | } 92 | }; 93 | 94 | async function processAllVariableDeclarationsInContractDefinition(contractDefinition, contractAddress, web3) { 95 | const nodes = contractDefinition.nodes; 96 | for(let i = 0; i < nodes.length; i++) { 97 | const node = nodes[i]; 98 | if(node.nodeType === 'VariableDeclaration') { 99 | await processVariableDeclaration(contractDefinition, node, contractAddress, web3); 100 | } 101 | } 102 | } 103 | 104 | async function processVariableDeclaration(contractDefinition, node, contractAddress, web3) { 105 | 106 | // Constant variables do not use storage. 107 | if(node.constant) return; 108 | 109 | // Print variable declaration. 110 | const declaration = astUtil.parseNodeToString(node); 111 | console.log(declaration); 112 | // console.log(` offset: ${rightOffset}`); 113 | 114 | // Get variable type. 115 | const type = node.typeDescriptions.typeString; 116 | // console.log(` type: ${type}`); 117 | 118 | // Calculate variable size. 119 | const charCount = astUtil.getVariableDeclarationBytesSize(contractDefinition, node) * 2; 120 | const sizeRemainingInWord = 64 - rightOffset; 121 | if(sizeRemainingInWord < charCount) advanceSlot(charCount); 122 | console.log(` size: ${charCount / 2} bytes`); 123 | 124 | // Read corresponding storage. 125 | console.log(` slot: ${slot}`); 126 | const raw = await web3.eth.getStorageAt(contractAddress, slot); 127 | const word = web3.utils.padLeft(raw, 64, '0').substring(2, 66); 128 | console.log(` word: ${word}`); 129 | 130 | // Read sub-word. 131 | const start = 64 - rightOffset - charCount; 132 | const end = start + charCount; 133 | let subword; 134 | if(type === 'string') subword = word.substring(start, end - 2); 135 | else subword = word.substring(start, end); 136 | console.log(` subword: ${subword}`); 137 | 138 | // Read value in word according to type. 139 | const value = abiUtil.parseVariableValue(type, subword); 140 | console.log(` value: ${value}`); 141 | 142 | advanceSlot(charCount); 143 | } 144 | 145 | function advanceSlot(size) { 146 | rightOffset += size; 147 | if(rightOffset >= 64) { 148 | rightOffset = 0; 149 | slot++; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/commands/members.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const getArtifacts = require('../utils/getArtifacts'); 3 | const astUtil = require('../utils/astUtil.js'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'members '; 7 | const description = 'Lists all members of a contract.'; 8 | const help = chalk` 9 | Provides a list of all the members of the provided contract (compiled or not). Uses the provided contract (compiled or not) to analyze the AST. Can list linearized inherited members and sort each by type. Also accepts a term to highlight in the output, for visually identifying certain things. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh members --inherited --sort test/artifacts/Sample.json} 14 | 15 | ¬ Sample 16 | function testSample() public pure returns(string) \{...\} 17 | 18 | ¬ SampleDependency 19 | function test() public pure returns(string) \{...\} 20 | function testSampleDependency() public pure returns(string) \{...\} 21 | 22 | ¬ SampleAbstract 23 | event AnEvent(address addr); 24 | function test() public pure returns(string); 25 | `; 26 | 27 | let highlightTerm; 28 | let sort; 29 | 30 | module.exports = { 31 | signature, 32 | description, 33 | register: (program) => { 34 | program 35 | .command(signature, {noHelp: true}) 36 | .description(description) 37 | .on('--help', () => console.log(help)) 38 | .option(`--inherited`, `list inherited contracts' members as well`) 39 | .option(`--highlight `, `highlight a specific term in the output`) 40 | .option(`--sort`, `sort members by kind (if not set members will be listed as they appear in the AST)`) 41 | .action(async (contractPath, options) => { 42 | chalk.enabled = !program.disableColors; 43 | 44 | // Validate input. 45 | const listInherited = options.inherited; 46 | if(options.highlight) highlightTerm = options.highlight; 47 | sort = options.sort; 48 | 49 | // Retrieve contract artifacts. 50 | const { artifacts, basedir } = await getArtifacts(contractPath); 51 | 52 | // Retrieve the ast. 53 | const ast = artifacts.ast; 54 | if(!ast) throw new Error('AST data not found.'); 55 | 56 | // Retrieve the target contract definition node. 57 | const rootContractName = path.basename(contractPath).split('.')[0]; 58 | const rootContractDefinition = astUtil.findNodeWithTypeAndName(ast, 'ContractDefinition', rootContractName); 59 | 60 | // Process single contract of all base contracts. 61 | if(listInherited) await processAllBaseContractsFromContractDefinition(ast, rootContractDefinition, basedir); 62 | else processAllNodesInContractDefinition(rootContractDefinition, false); 63 | }); 64 | } 65 | }; 66 | 67 | async function processAllBaseContractsFromContractDefinition(ast, contractDefinition, basedir) { 68 | 69 | // Retrieve the linearized base contract nodes of the contract. 70 | const linearizedContractDefs = await astUtil.getLinearizedBaseContractNodes(ast, contractDefinition, basedir); 71 | 72 | // Traverse each base contract in the linearized order, and process their variables. 73 | for(let i = 0; i < linearizedContractDefs.length; i++) { 74 | const contractDefinition = linearizedContractDefs[i]; 75 | if(contractDefinition && contractDefinition.nodes) processAllNodesInContractDefinition(contractDefinition, true); 76 | else console.log("WARNING: Contract definition not found for a base contract."); 77 | } 78 | } 79 | 80 | function processAllNodesInContractDefinition(contractDefinition) { 81 | const nodes = sort ? astUtil.sortNodes(contractDefinition.nodes) : contractDefinition.nodes; 82 | console.log(chalk`\n{redBright.bold ¬ ${contractDefinition.name}}`); 83 | for(let i = 0; i < nodes.length; i++) { 84 | console.log(` ${astUtil.parseNodeToString(nodes[i], highlightTerm)}`); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/commands/pad.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'pad [direction] [amount] [char]'; 6 | const description = 'Pads hex numbers.'; 7 | const help = chalk` 8 | Pads value left or right with a given amount. Defaults to 64 (32 bytes), left. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh pad 0x06012c8cf97bead5deae237070f9587f8e7a266d left 64} 13 | 0x00000000000000000000000006012c8cf97bead5deae237070f9587f8e7a266d 14 | `; 15 | 16 | module.exports = { 17 | signature, 18 | description, 19 | register: (program) => { 20 | program 21 | .command(signature, {noHelp: true}) 22 | .description(description) 23 | .on('--help', () => console.log(help)) 24 | .action((value, direction, amount, char) => { 25 | 26 | // Input validation. 27 | if(!validateUtil.hex(value)) 28 | throw new Error(`Invalid hex value: ${value}`); 29 | 30 | const web3 = new Web3(); 31 | 32 | // Defaults. 33 | direction = direction || 'left'; 34 | amount = amount || 64; 35 | char = char || '0'; 36 | 37 | // Validate direction. 38 | if(direction !== 'right' && direction !== 'left') { 39 | throw new Error('Invalid direction. Please use "right" for padRight and "left" for padLeft.'); 40 | } 41 | 42 | // Pad! 43 | const func = direction === 'left' ? web3.utils.padLeft : web3.utils.padRight; 44 | console.log(func(value, amount, char)); 45 | }); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/commands/pastevents.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const getWeb3 = require('../utils/getWeb3.js'); 4 | const getArtifacts = require('../utils/getArtifacts'); 5 | const validateUtil = require('../utils/validateUtil'); 6 | const chalk = require('chalk'); 7 | 8 | const signature = 'pastevents [toBlock] [batchSize]'; 9 | const description = 'Finds past events of a contract.'; 10 | const help = chalk` 11 | Finds past events for a given deployed contract. Requires a network to be specified, a contractPath (compiled or not), a deployed contractAddress, the eventName to query, a block range, and a batchSize that determines how many blocks are queried at a time. 12 | 13 | {red Eg:} 14 | 15 | {blue > pocketh pastevents mainnet test/artifacts/KittyCore.json 0x06012c8cf97bead5deae237070f9587f8e7a266d Transfer 7729780 7729781} 16 | Querying for event "Transfer" in block range: [7729780, 7729781] - 0% 17 | \{ 18 | "address": "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d", 19 | "blockHash": "0x413bbd2803a7848ecbb74cf3f3b70406713474f75ed9bbeb6c8c71fa9a82d4cb", 20 | "blockNumber": 7729780, 21 | "logIndex": 55, 22 | "removed": false, 23 | "transactionHash": "0xf08da17d43d543318b9012c596ca5b1a6e415114cf8a4905ff90b001dc65ece5", 24 | "transactionIndex": 59, 25 | "id": "log_0xc66d511401664f74e887ba799f15f2d161e18a129353fa47d0b9ad16d9cd58ca", 26 | "returnValues": \{ 27 | "0": "0x0000000000000000000000000000000000000000", 28 | "1": "0x8820A512CE3B3b51c0340A81930941d3339D3EDA", 29 | "2": \{ 30 | "_hex": "0x17f279" 31 | \}, 32 | "from": "0x0000000000000000000000000000000000000000", 33 | "to": "0x8820A512CE3B3b51c0340A81930941d3339D3EDA", 34 | "tokenId": \{ 35 | "_hex": "0x17f279" 36 | \} 37 | \}, 38 | "event": "Transfer", 39 | "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 40 | "raw": \{ 41 | "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008820a512ce3b3b51c0340a81930941d3339d3eda000000000000000000000000000000000000000000000000000000000017f279", 42 | "topics": [ 43 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" 44 | ] 45 | \} 46 | \} 47 | ... 48 | Total "Transfer" events found: 6 49 | `; 50 | 51 | module.exports = { 52 | signature, 53 | description, 54 | register: (program) => { 55 | program 56 | .command(signature, {noHelp: true}) 57 | .description(description) 58 | .on('--help', () => console.log(help)) 59 | .action(async (networkUrl, contractPath, contractAddress, eventName, fromBlock, toBlock, batchSize) => { 60 | 61 | // Input validation. 62 | if(!validateUtil.address(contractAddress)) 63 | throw new Error(`Invalid contract address: ${contractAddress}`); 64 | 65 | // Validate input. 66 | batchSize = batchSize ? parseInt(batchSize, 10) : 100; 67 | fromBlock = parseInt(fromBlock, 10); 68 | toBlock = toBlock ? toBlock : 'latest'; 69 | 70 | // Connect to network. 71 | const web3 = await getWeb3(networkUrl); 72 | 73 | // Retrieve contract artifacts. 74 | const { artifacts }= await getArtifacts(contractPath); 75 | 76 | // Retrieve contract instance. 77 | const instance = await new web3.eth.Contract(artifacts.abi, contractAddress); 78 | 79 | // Find events in a given block range. 80 | let count = 0; 81 | async function logEventsInBatch(from, to) { 82 | await instance.getPastEvents( 83 | eventName, 84 | {fromBlock: from, toBlock: to}, 85 | (err, events) => { 86 | if(err) process.stdout.write(err); 87 | else if(events && events.length > 0) { 88 | events.map((event) => { 89 | process.stdout.write(`\n${JSON.stringify(event, null, 2)}`); 90 | }); 91 | count += events.length; 92 | } 93 | } 94 | ); 95 | } 96 | 97 | // Find events by batches. 98 | if(toBlock === 'latest') toBlock = await web3.eth.getBlockNumber(); 99 | let currentBlock = fromBlock; 100 | const numBlocks = toBlock - fromBlock; 101 | async function logNextBatch() { 102 | if(currentBlock >= toBlock) { 103 | process.stdout.write(`\nTotal "${eventName}" events found: ${count}\n`); 104 | process.exit(); 105 | } 106 | const to = Math.min(currentBlock + batchSize, toBlock); 107 | const percentage = Math.floor(( currentBlock - fromBlock ) / numBlocks * 100, 2); 108 | process.stdout.write(`\rQuerying for event "${eventName}" in block range: [${currentBlock}, ${to}] - ${percentage}%`); 109 | await logEventsInBatch(currentBlock, to); 110 | currentBlock = to; 111 | await logNextBatch(); 112 | } 113 | await logNextBatch(); 114 | }); 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /src/commands/selector.js: -------------------------------------------------------------------------------- 1 | const abiUtil = require('../utils/abiUtil.js'); 2 | const chalk = require('chalk'); 3 | 4 | const signature = 'selector '; 5 | const description = 'Calculates a selector.'; 6 | const help = chalk` 7 | Calculates the selector for a given function signature. Please use quotes around the signature, e.g. "value(address)" 8 | 9 | {red Eg:} 10 | 11 | {blue > pocketh selector 'transfer(address,uint256)'} 12 | 0xa9059cbb 13 | `; 14 | 15 | module.exports = { 16 | signature, 17 | description, 18 | register: (program) => { 19 | program 20 | .command(signature, {noHelp: true}) 21 | .description(description) 22 | .on('--help', () => console.log(help)) 23 | .action((signature) => { 24 | if(signature.includes('returns')) throw new Error('The return type of a function is not part of its signature.'); 25 | console.log(abiUtil.getSelector(signature)); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/commands/selectors.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const getArtifacts = require('../utils/getArtifacts'); 3 | const abiUtil = require('../utils/abiUtil'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'selectors '; 7 | const description = 'Lists all selectors of a contract.'; 8 | const help = chalk` 9 | List all the function selectors of the provided contract (compiled or not). 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh selectors test/artifacts/Test.json} 14 | 0x29e99f07: test(uint256) 15 | 0x3fa4f245: value() 16 | 0x5dce0fa6: granparent_value() 17 | 0xa6c56d47: parent2_value() 18 | 0xd8175b14: parent1_value() 19 | 0xf0686273: CONS() 20 | `; 21 | 22 | module.exports = { 23 | signature, 24 | description, 25 | register: (program) => { 26 | program 27 | .command(signature, {noHelp: true}) 28 | .description(description) 29 | .on('--help', () => console.log(help)) 30 | .action(async (contractPath) => { 31 | 32 | // Retrieve contract artifacts and abi. 33 | const { artifacts } = await getArtifacts(contractPath); 34 | 35 | // Retrieve abi. 36 | const abi = artifacts.abi; 37 | 38 | // Scan the abi and identify function signatures. 39 | abi.map((item) => { 40 | if(item.type === 'function') { 41 | const signature = item.signature || abiUtil.getAbiItemSignature(item); 42 | const hash = abiUtil.getAbiItemSigHash(item); 43 | process.stdout.write(`${hash}: ${signature}\n`); 44 | } 45 | }); 46 | }); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/commands/split.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'split '; 6 | const description = 'Splits Solidity files.'; 7 | const help = chalk` 8 | Splits a Solidity file containing multiple contracts into multiple files, each with one contract. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh split test/contracts/Kitties.sol /tmp/} 13 | Split file test/contracts/Kitties.sol into 16 files: 14 | - Ownable.sol 15 | - ERC721.sol 16 | - GeneScienceInterface.sol 17 | - KittyAccessControl.sol 18 | - KittyBase.sol 19 | - ERC721Metadata.sol 20 | - KittyOwnership.sol 21 | - KittyBreeding.sol 22 | - ClockAuctionBase.sol 23 | - Pausable.sol 24 | - ClockAuction.sol 25 | - SiringClockAuction.sol 26 | - SaleClockAuction.sol 27 | - KittyAuction.sol 28 | - KittyMinting.sol 29 | - KittyCore.sol 30 | (New files written to /tmp/) 31 | `; 32 | 33 | module.exports = { 34 | signature, 35 | description, 36 | register: (program) => { 37 | program 38 | .command(signature, {noHelp: true}) 39 | .description(description) 40 | .on('--help', () => console.log(help)) 41 | .action((sourcePath, outputDirectory) => { 42 | 43 | // Validate input. 44 | if(!fs.existsSync(sourcePath)) 45 | throw new Error(`Cannot find source file: ${sourcePath}`); 46 | if(!fs.existsSync(outputDirectory)) 47 | throw new Error(`Cannot find ${outputDirectory}.`); 48 | if(!fs.lstatSync(outputDirectory).isDirectory()) 49 | throw new Error('outputDirectory must be a directory path.'); 50 | 51 | // Read file. 52 | let source = fs.readFileSync(sourcePath, 'utf8'); 53 | 54 | // Identify and remove pragma line. 55 | const pragmaLine = source.match(/pragma.*/gm)[0]; 56 | source = source.replace(pragmaLine, ''); 57 | 58 | // Split contents into lines. 59 | const lines = source.split('\n'); 60 | 61 | // Regroup content into contracts. 62 | // Consume lines and add them to separate contract strings. 63 | // Open new contracts when the number of opening and closing 64 | // curly braces matches. 65 | const contracts = [``]; 66 | let currentContractIdx = 0; 67 | let openCurlyBraceCount = 0; 68 | let closeCurlyBraceCount = 0; 69 | for(let i = 0; i < lines.length; i++) { 70 | let line = lines[i]; 71 | 72 | // Add current line to current contract. 73 | line = line.replace(/\r?\n|\r/, ''); // Remove line breaks from line. 74 | contracts[currentContractIdx] += `${line}\n`; 75 | 76 | // Skip commented curlies 77 | if(['*','/*','//'].some(reg => line.trim().startsWith(reg))) continue 78 | 79 | // Count curly braces. 80 | const openCurlies = line.match(/{/g); 81 | const closeCurlies = line.match(/}/g); 82 | if(openCurlies) openCurlyBraceCount += line.match(/{/g).length; 83 | if(closeCurlies) closeCurlyBraceCount += line.match(/}/g).length; 84 | 85 | // If a new block is starting, open a new file. 86 | if(openCurlyBraceCount > 0 && openCurlyBraceCount === closeCurlyBraceCount) { 87 | contracts.push(``); 88 | currentContractIdx += 1; 89 | openCurlyBraceCount = closeCurlyBraceCount = 0; 90 | } 91 | } 92 | contracts.pop(); 93 | 94 | // Only 1 contract? 95 | if(contracts.length === 1) { 96 | console.log(`Only one contract found in file, nothing to do.`); 97 | return; 98 | } 99 | 100 | // Retrieve all contract names. 101 | const names = []; 102 | for(let i = 0; i < contracts.length; i++) { 103 | const contract = contracts[i]; 104 | 105 | // Get contract name. 106 | const contractDefs = contract.match(/^\s*[contract|library].*/gm); 107 | if(!contractDefs || contractDefs.length === 0) throw new Error(`Unable to find contract definition in ${contract}`); 108 | const name = contractDefs[0].match(/(?<=[contract |library ])\b\w+\b/); 109 | 110 | names.push(name); 111 | } 112 | 113 | // Add import statements. 114 | // Review all contracts, and if any name appears in it, 115 | // add an import statement. 116 | for(let i = 0; i < contracts.length; i++) { 117 | for(let j = 0; j < names.length; j++) { 118 | if(i !== j) { 119 | const otherName = names[j]; 120 | const noComments = contracts[i].replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, ''); 121 | const otherNameMatches = noComments.match(new RegExp(`${otherName}`, 'gm')); 122 | if(otherNameMatches && otherNameMatches.length > 0) { 123 | contracts[i] = `import "./${otherName}.sol";\n${contracts[i]}`; 124 | } 125 | } 126 | } 127 | } 128 | 129 | // Add pragma statements. 130 | for(let i = 0; i < contracts.length; i++) { 131 | contracts[i] = `${pragmaLine}\n\n${contracts[i]}`; 132 | } 133 | 134 | // Write each contract into a separate file. 135 | for(let i = 0; i < contracts.length; i++) { 136 | const contract = contracts[i]; 137 | 138 | // Build target path. 139 | const outPath = path.resolve(outputDirectory, `${names[i]}.sol`); 140 | // console.log(`outPath: ${outPath}`); 141 | 142 | // Write file. 143 | fs.writeFileSync(outPath, contract); 144 | } 145 | 146 | // Report. 147 | console.log(`Split file ${sourcePath} into ${contracts.length} files:`); 148 | names.map(name => { 149 | console.log(` - ${name}.sol`); 150 | }); 151 | console.log(`(New files written to ${outputDirectory})`); 152 | }); 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /src/commands/storage.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const getWeb3 = require('../utils/getWeb3.js'); 4 | const stringUtil = require('../utils/stringUtil.js'); 5 | const BN = require('bn.js'); 6 | const validateUtil = require('../utils/validateUtil'); 7 | const chalk = require('chalk'); 8 | 9 | const signature = 'storage '; 10 | const description = 'Reads the storage of a contract.'; 11 | const help = chalk` 12 | Query the storage of a contract at a given slot. 13 | Relevant Solidity documentation: https://solidity.readthedocs.io/en/v0.5.8/miscellaneous.html?highlight=storage#layout-of-state-variables-in-storage 14 | 15 | The command retrieves the stored value in hex, but also tries to interpret it in decimal and ascii. 16 | 17 | {red Eg:} 18 | 19 | To read simple values (i.e. values that are stored in an entire slot): 20 | {blue > pocketh storage ropsten 0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b 0} 21 | slot: 0 22 | slot (hex): 0x0000000000000000000000000000000000000000000000000000000000000000 23 | value: 0x000000000000000000000000000000000000000000000000000000000000000f 24 | (dec?): 15 25 | (str?): 26 | 27 | To read packed values (i.e. values that are stored in part of a slot) 28 | {blue > pocketh storage localhost address 1 --range 16,32} 29 | ... 30 | 31 | To read mappings: 32 | {blue > pocketh storage ropsten 0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b 5 --key 0xbCcc714d56bc0da0fd33d96d2a87b680dD6D0DF6} 33 | ... 34 | 35 | To read arrays: 36 | {blue > pocketh storage ropsten 0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b 7 --offset 1} 37 | ... 38 | 39 | To read a mapping of structs: 40 | {blue > pocketh storage ropsten 0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b 6 --key 0xbCcc714d56bc0da0fd33d96d2a87b680dD6D0DF6 --offset 1} 41 | ... 42 | `; 43 | 44 | let web3; 45 | 46 | module.exports = { 47 | signature, 48 | description, 49 | register: (program) => { 50 | program 51 | .command(signature, {noHelp: true}) 52 | .description(description) 53 | .on('--help', () => console.log(help)) 54 | .option(`-r, --range `, `Specify a range 'start,len' in bytes to read within the slot.`) 55 | .option(`-k, --key `, `Specify a key to read storage from a mapping.`) 56 | .option(`-o, --offset `, `Specify an offset to read storage from an array.`) 57 | .action(async (networkUrl, contractAddress, storageSlot, options) => { 58 | console.log(`slot: ${storageSlot}`); 59 | 60 | // Input validation. 61 | if(!validateUtil.address(contractAddress)) 62 | throw new Error(`Invalid contract address: ${contractAddress}`); 63 | 64 | // Connect to network. 65 | web3 = await getWeb3(networkUrl); 66 | 67 | // Convert the slot to hex and pad it. 68 | let hexSlot = padWord(web3.utils.numberToHex(storageSlot)); 69 | console.log(`slot (hex): ${hexSlot}`); 70 | 71 | // If a key is specified, combine the key and the storage slot with a hash, 72 | // so that the new slot = keccak256(key . slot) 73 | let slotHasBeenHashed = false; 74 | if(options.key) { 75 | slotHasBeenHashed = true; 76 | hexSlot = web3.utils.soliditySha3(padWord(options.key), hexSlot); 77 | console.log(`slot (key): ${hexSlot}`); 78 | } 79 | 80 | // If an offset is specified, the new slot = keccak(slot) + offset 81 | // NOTE: If the slot is already a hash, i.e. from a mapping, then just apply the offset. 82 | if(options.offset) { 83 | if(!slotHasBeenHashed) hexSlot = web3.utils.soliditySha3(hexSlot); 84 | hexSlot = `0x${bigHexAdd(hexSlot, options.offset)}`; 85 | console.log(`slot (offset): ${hexSlot}`); 86 | } 87 | 88 | // Read storage. 89 | const value = padWord(await web3.eth.getStorageAt(contractAddress, hexSlot)); 90 | console.log(`value: ${value}`); 91 | 92 | // Interpret the value stored at the slot, or part of the slot. 93 | if(options.range) readRange(value, options.range); 94 | else interpretHexValue(value); 95 | }); 96 | } 97 | }; 98 | 99 | function padWord(value) { 100 | let padded = web3.utils.padLeft(stringUtil.remove0x(value), 64, '0'); 101 | if(!padded.includes('0x')) padded = `0x${padded}`; 102 | return padded; 103 | } 104 | 105 | function readRange(value, range) { 106 | const comps = range.split(','); 107 | const start = 2 + parseInt(comps[0], 10) * 2; 108 | const end = start + parseInt(comps[1], 10) * 2; 109 | const subvalue = `0x` + value.substring(start, end); 110 | console.log(`subvalue: ${subvalue}`); 111 | interpretHexValue(subvalue); 112 | } 113 | 114 | function interpretHexValue(hexValue) { 115 | const asDec = new BN(stringUtil.remove0x(hexValue), 16).toString(10); 116 | const asStr = web3.utils.toAscii(hexValue); 117 | console.log(` (dec?): ${asDec}`); 118 | console.log(` (str?): ${asStr}`); 119 | } 120 | 121 | function bigHexAdd(bighex, decIncrement) { 122 | const h = new BN(bighex, 16); 123 | const i = new BN(decIncrement, 10); 124 | const a = h.add(i); 125 | return a.toString(16); 126 | } 127 | -------------------------------------------------------------------------------- /src/commands/str2hex.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const chalk = require('chalk'); 3 | 4 | const signature = 'str2hex '; 5 | const description = 'Converts string to hex.'; 6 | const help = chalk` 7 | Converts an ascii string to its hex representation. 8 | 9 | {red Eg:} 10 | 11 | {blue > pocketh str2hex Hello} 12 | 0x48656c6c6f000000000000000000000000000000000000000000000000000000 13 | `; 14 | 15 | module.exports = { 16 | signature, 17 | description, 18 | register: (program) => { 19 | program 20 | .command(signature, {noHelp: true}) 21 | .description(description) 22 | .on('--help', () => console.log(help)) 23 | .action((asciiString) => { 24 | const web3 = new Web3(); 25 | const hexString = web3.utils.asciiToHex(asciiString); 26 | console.log(`${hexString}`); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/commands/transaction.js: -------------------------------------------------------------------------------- 1 | const getWeb3 = require('../utils/getWeb3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'transaction '; 6 | const description = 'Gets info about a transaction.'; 7 | const help = chalk` 8 | Gets a transaction given its hash. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh transaction mainnet 0x95ecb5317de43d2c682e93350f160c10d3a816002ad43f2b67fb631062c1484b} 13 | 0x95ecb5317de43d2c682e93350f160c10d3a816002ad43f2b67fb631062c1484b => \{ 14 | "blockHash": "0xcabafc45ffe90a54faac651195a5100029d398c08ef81d7b556e412d03f16002", 15 | "blockNumber": 7729790, 16 | "from": "0x992E68379eFC08A1c7B8C1E3bD335E87BF9A4b7B", 17 | "gas": 104068, 18 | "gasPrice": "1100000000", 19 | "hash": "0x95ecb5317de43d2c682e93350f160c10d3a816002ad43f2b67fb631062c1484b", 20 | "input": "0xa9059cbb000000000000000000000000eeff3793df0685d54805b8807d1fd63cc66f9c2f000000000000000000000000000000000000000000000000000000000015011d", 21 | "nonce": 1664, 22 | "r": "0xf924c5771646d973657a7cdf3dd58a41eb6956c8a855fc32362d37490070244a", 23 | "s": "0x6b0d1df736e24e16b52763e95fdd1d0f64571d172b426888202bb6cb09440b20", 24 | "to": "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d", 25 | "transactionIndex": 8, 26 | "v": "0x26", 27 | "value": "0" 28 | \} 29 | `; 30 | 31 | module.exports = { 32 | signature, 33 | description, 34 | register: (program) => { 35 | program 36 | .command(signature, {noHelp: true}) 37 | .description(description) 38 | .on('--help', () => console.log(help)) 39 | .action(async (networkUrl, txHash) => { 40 | 41 | // Input validation. 42 | if(!validateUtil.bytes32(txHash)) 43 | throw new Error(`Invalid txHash: ${txHash}`); 44 | 45 | const web3 = await getWeb3(networkUrl); 46 | const tx = await web3.eth.getTransaction(txHash); 47 | console.log(JSON.stringify(tx, null, 2)); 48 | }); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/commands/txs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const getWeb3 = require('../utils/getWeb3.js'); 3 | const validateUtil = require('../utils/validateUtil'); 4 | const chalk = require('chalk'); 5 | 6 | const signature = 'txs [toBlock] [maxThreads]'; 7 | const description = 'Finds transactions.'; 8 | const help = chalk` 9 | Finds transactions made to a deployed contract, for a specified function selector, in a specified block range, with a maxThreads number of simultaneous queries. 10 | 11 | {red Eg:} 12 | 13 | {blue > pocketh txs mainnet 0x06012c8cf97bead5deae237070f9587f8e7a266d 0xa9059cbb 7729780 7729790} 14 | Scanning 11 blocks... 15 | 👉 match: 0xa911d8af867ec881e007053aac62e9488f07c0d09ec04deb09e3b780494fed83 16 | 👉 match: 0x8864fd2b8a70b3a7e93342813c125f269fbb5dba79177ea48b3e0d55aba3dff0 17 | 18% #7729781, 2 scanned, 9 remaining, 0.82 b/s per thread, 1.25 s, 10 threads 18 | 👉 match: 0x95ecb5317de43d2c682e93350f160c10d3a816002ad43f2b67fb631062c1484b 19 | ... 20 | Matches: 21 | 0xa911d8af867ec881e007053aac62e9488f07c0d09ec04deb09e3b780494fed83 22 | 0x8864fd2b8a70b3a7e93342813c125f269fbb5dba79177ea48b3e0d55aba3dff0 23 | 0x95ecb5317de43d2c682e93350f160c10d3a816002ad43f2b67fb631062c1484b 24 | 0x03d488a5ef270b083ac4f6d29f2fabf5a7962281af56d8a4212d3cc3608751c5 25 | 0x827aaa59821ec53a031d08c04348ffdff3829f75eae11524737394b9dffb846d 26 | 0x43b72ba5dd5e286f9d9785d6a10f08d3c44ad6d546db323b995a1c5608d3bfdb 27 | 0xd9758451aeb0af23c9d7a91bc974ee3df3db9a20570832928caf45ac76593acf 28 | 0x842b49c9fe094bc9c6396cae6259018d39ee469339e83dd0f72d13a80084b001 29 | 0xa90670d617bdf688d047ba5791cd23dc920bb583a755abdd9c0abc8408530ccf 30 | 0x8355606297996feb2c7e6fe7eeb66c12680ace629dcd2585bb77db7f5f962dca 31 | 0xd69eba2de092af2a2d566797293557c755441d39f8108f4a7ffaacedb1252649 32 | 0x4702aa728e2d3e9278c5801319aec432ad26c2d6769e5c99e278e950bfaeba06 33 | 0xcdd8d0f2e63363bd053a445d614382335bbc377db0f3b71cc5c0625d562f3130 34 | 0x0f9a4af0f5c4c5e43f5f87571162570e74d8eee87b96d3245e430ed0e7ef8075 35 | 0x384d49c27ec72ddc560e91fa11a439cb78725f051cfd3ff0ccf30e7ee7ed0790 36 | 0x4488ee46a1a71ef865a314112c6fd981a94af932934e97e715f107006544072f 37 | 0xdcf7d7680bd646c595e6b58c6617bf7bcfa534dc95ae9c629e46233395cdd9c6 38 | 0x985e65d3ecad3a68f785203c0c75c3dc86dc563603c47c5b3546b030fdb20c22 39 | 0xb5d9a837eef195abfba76c231b8a96f59cb12e10d030df2d72b9888cfaff634d 40 | 0xf4d0939a408eb703898cb08f469abdd8b337fed64de8140ddbf83d26cfd89969 41 | 0xb83e36fea78edb4139d830a86bb3a609ee73f091408ddb80f36842990320c5db 42 | 0x8eda05c6126da690077d53b9f9ced3285a5aac9d3eb53d6f81c03ef068a99a71 43 | 0x739f85f4b216de1dcda15933dcacbe8d54d2e1c55bde4e582b0fa30721390e6d 44 | 0x127adada51b1f9097610069ff724f1bc762955214126d18244e2b36ee8655a78 45 | 0xcf5a4a40366f851b5b8bb08a6fbf75ed1a227a6c77633759ab72fa722f45a6fb 46 | 0x1ab519cb7700340078bfca8310ec62076d077ec0a92caf5ed79c6f48ebe3b1ea 47 | 0x950db37d96bf0faea56f67c07a886840a619c25dc906e8226c39956a697df470 48 | 49 | Found 27 transactions in block range [7729780-7729790] that target the address 0x06012c8cf97bead5deae237070f9587f8e7a266d and the selector 0xa9059cbb (listed above) in 1.78 seconds. 50 | `; 51 | 52 | module.exports = { 53 | signature, 54 | description, 55 | register: (program) => { 56 | program 57 | .command(signature, {noHelp: true}) 58 | .description(description) 59 | .on('--help', () => console.log(help)) 60 | .action(async (networkUrl, contractAddress, functionSelector, fromBlock, toBlock, maxThreads) => { 61 | 62 | // Input validation. 63 | if(!validateUtil.address(contractAddress)) 64 | throw new Error(`Invalid contractAddress: ${contractAddress}`); 65 | if(!validateUtil.hex(functionSelector)) 66 | throw new Error(`Invalid functionSelector: ${functionSelector}`); 67 | 68 | // Validate input. 69 | contractAddress = contractAddress.toLowerCase(); 70 | fromBlock = parseInt(fromBlock, 10); 71 | toBlock = toBlock ? toBlock : 'latest'; 72 | maxThreads = maxThreads ? parseInt(maxThreads, 10) : 20; 73 | 74 | // Connect to network. 75 | const web3 = await getWeb3(networkUrl); 76 | 77 | // Init state. 78 | if(toBlock === 'latest') toBlock = await web3.eth.getBlockNumber(); 79 | let triesLeft = 10; 80 | const scannedBlocks = []; 81 | const blocksBeingScanned = []; 82 | const matchingTxs = []; 83 | const absStartTime = (new Date()).getTime() / 1000; 84 | const numBlocks = toBlock - fromBlock + 1; 85 | let blocksPerSecond = 0; 86 | const lastBlocksPerSecondReadings = []; 87 | process.stdout.write(`Scanning ${numBlocks} blocks...\n`); 88 | 89 | // Get a block and examine it's txs for a match with 90 | // the target contract and the target selector. 91 | // When done, either by an error or completion, evaluate 92 | // program completion or scanning the next block. 93 | function scanBlockAndContinue(blockNumber, threadNum) { 94 | 95 | // Record time. 96 | const startTime = new Date(); 97 | 98 | // Register block as being scanned. 99 | blocksBeingScanned.push(blockNumber); 100 | 101 | // Get block (including its transactions). 102 | web3.eth.getBlock( 103 | blockNumber, 104 | true, 105 | (err, block) => { 106 | 107 | // Process errors with a max number of tries. 108 | if(err) { 109 | 110 | // De-register block as being scanned. 111 | blocksBeingScanned.splice(blocksBeingScanned.indexOf(blockNumber), 1); 112 | 113 | // Report error. 114 | process.stderr.write(`\nError: ${err}`); 115 | 116 | // Retry or end. 117 | triesLeft--; 118 | if(triesLeft < 0) evalScanNextBlock(); 119 | else { 120 | process.stderr.write(`\nToo many errors, exiting...`); 121 | endScan(); 122 | } 123 | } 124 | else { 125 | 126 | // Check block transactions. 127 | block.transactions.map((tx) => { 128 | 129 | // Match contract address? 130 | const txToContractAddress = tx.to && tx.to.toLowerCase() === contractAddress; 131 | if(txToContractAddress) { 132 | 133 | // Match contract function? 134 | const txToTargetFunction = tx.input.substring(0, 10) === functionSelector; 135 | if(txToTargetFunction) { 136 | 137 | // Report match immediately. 138 | console.log(`\n👉 match: ${tx.hash}`); 139 | 140 | // Register match. 141 | matchingTxs.push(tx.hash); 142 | } 143 | } 144 | }); 145 | 146 | // Register block as scanned. 147 | scannedBlocks.push(blockNumber); 148 | 149 | // Calculate speed. 150 | const now = (new Date()).getTime() / 1000; 151 | const elapsedTime = now - startTime.getTime() / 1000; 152 | const blocksPerSecond = 1 / elapsedTime; 153 | if(lastBlocksPerSecondReadings.length >= 10) { 154 | lastBlocksPerSecondReadings.splice(0, 1); 155 | } 156 | lastBlocksPerSecondReadings.push(blocksPerSecond); 157 | const avgBlocksPerSecond = lastBlocksPerSecondReadings.reduce( 158 | (sum, value) => sum + value 159 | ) / lastBlocksPerSecondReadings.length; 160 | 161 | // Monitor progress. 162 | const elapsedTotalTime = now - absStartTime; 163 | const blocksRemaining = numBlocks - scannedBlocks.length; 164 | const percentage = Math.floor(100 * scannedBlocks.length / numBlocks, 2); 165 | process.stdout.write(`\r${percentage}% #${blockNumber}, ${scannedBlocks.length} scanned, ${blocksRemaining} remaining, ${avgBlocksPerSecond.toFixed(2)} b/s per thread, ${elapsedTotalTime.toFixed(2)} s, ${blocksBeingScanned.length} threads`); 166 | 167 | // De-register block as being scanned. 168 | blocksBeingScanned.splice(blocksBeingScanned.indexOf(blockNumber), 1); 169 | 170 | // Continue or end scan. 171 | evalEndScan(); 172 | evalScanNextBlock(); 173 | } 174 | } 175 | ); 176 | } 177 | 178 | // Pick a block to scan if a thread is free. 179 | function evalScanNextBlock() { 180 | 181 | // Cap threads. 182 | if(blocksBeingScanned.length >= maxThreads) return; 183 | 184 | // Scan next block. 185 | const blockNumberToScan = findUnscannedBlock(); 186 | if(blockNumberToScan >= 0) scanBlockAndContinue(blockNumberToScan); 187 | } 188 | 189 | // Evaluate if the scan is done. 190 | function evalEndScan(blockNumber) { 191 | const blocksRemaining = numBlocks - scannedBlocks.length; 192 | if(blocksRemaining === 0) endScan(); 193 | } 194 | 195 | // End scan and exit program. 196 | function endScan() { 197 | const elapsedTime = (new Date()).getTime() / 1000 - absStartTime; 198 | console.log(`\n\nMatches:`); 199 | matchingTxs.map(tx => console.log(`${tx}`)); 200 | process.stdout.write(`\nFound ${matchingTxs.length} transactions in block range [${fromBlock}-${toBlock}] that target the address ${contractAddress} and the selector ${functionSelector} (listed above) in ${elapsedTime.toFixed(2)} seconds.`); 201 | process.exit(); 202 | } 203 | 204 | // Find a block that hasn't been scanned or isn't being scanned. 205 | function findUnscannedBlock() { 206 | for(let b = fromBlock; b <= toBlock; b++) { 207 | if(!scannedBlocks.includes(b) && !blocksBeingScanned.includes(b)) return b; 208 | } 209 | return -1; 210 | } 211 | 212 | // Start the first scanning batch. 213 | for(let i = 0; i <= maxThreads; i++) { 214 | evalScanNextBlock(); 215 | } 216 | }); 217 | } 218 | }; 219 | -------------------------------------------------------------------------------- /src/commands/uint2hex.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const validateUtil = require('../utils/validateUtil'); 3 | const chalk = require('chalk'); 4 | 5 | const signature = 'uint2hex '; 6 | const description = 'Converts uint to hex.'; 7 | const help = chalk` 8 | Converts a positive integer in base 10 to its hexadecimal representation. 9 | 10 | {red Eg:} 11 | 12 | {blue > pocketh uint2hex 42} 13 | 0x2a 14 | `; 15 | 16 | module.exports = { 17 | signature, 18 | description, 19 | register: (program) => { 20 | program 21 | .command(signature, {noHelp: true}) 22 | .description(description) 23 | .on('--help', () => console.log(help)) 24 | .action((decNumber) => { 25 | 26 | // Input validation. 27 | if(!validateUtil.positiveInteger(decNumber)) 28 | throw new Error(`Invalid positive integer: ${decNumber}`); 29 | 30 | const web3 = new Web3(); 31 | const hexString = web3.utils.numberToHex(decNumber); 32 | console.log(hexString); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/globals.js: -------------------------------------------------------------------------------- 1 | // Define command paths. 2 | const commandPaths = [ 3 | './commands/getcode.js', 4 | './commands/split.js', 5 | './commands/inheritance.js', 6 | './commands/members.js', 7 | './commands/selectors.js', 8 | './commands/calldata.js', 9 | './commands/blockdate.js', 10 | './commands/txs.js', 11 | './commands/pastevents.js', 12 | './commands/disassemble.js', 13 | './commands/hex2str.js', 14 | './commands/str2hex.js', 15 | './commands/transaction.js', 16 | './commands/block.js', 17 | './commands/info.js', 18 | './commands/hex2uint.js', 19 | './commands/uint2hex.js', 20 | './commands/convert.js', 21 | './commands/pad.js', 22 | './commands/compile/compile.js', 23 | './commands/storage.js', 24 | './commands/liststorage.js', 25 | './commands/checksum.js', 26 | './commands/selector.js', 27 | './commands/docyul.js', 28 | './commands/int2hex.js', 29 | './commands/hex2int.js', 30 | ]; 31 | 32 | module.exports = { 33 | commandPaths 34 | }; 35 | -------------------------------------------------------------------------------- /src/program.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const { version } = require('../package.json'); 5 | const chalk = require('chalk'); 6 | const figlet = require('figlet'); 7 | const globals = require('./globals'); 8 | 9 | // Require all commands. 10 | const commands = globals.commandPaths.map(commandPath => { 11 | return require(commandPath); 12 | }); 13 | 14 | // Register each command in the program. 15 | commands.forEach(command => command.register(program)); 16 | 17 | // Program definition. 18 | program 19 | .name('pocketh') 20 | .usage(' [options]') 21 | .version(version, '-v, --version') 22 | .option('-d, --disable-colors', 'disable colored output') 23 | .on('--help', displayHelp) // Show custon help with the --help option. 24 | // Display an error when an unsupported command is entered. 25 | .on('command:*', function () { 26 | console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); 27 | process.exit(1); 28 | }); 29 | 30 | // Parse program. 31 | program.parse(process.argv); 32 | 33 | // Show custom help if no command is entered. 34 | if(process.argv.length === 2) displayHelp(); 35 | 36 | // Custon main help. 37 | function displayHelp() { 38 | program.help(() => { 39 | 40 | // Title. 41 | const str = figlet.textSync(`pocketh`, {font: 'Slant Relief'}); 42 | console.log(chalk`{redBright ${str}}`); 43 | 44 | // Version. 45 | console.log(chalk`\n {gray version ${version}}`); 46 | 47 | // Commands list with short description. 48 | console.log(chalk`\n{red.bold Commands:}`); 49 | commands.forEach(command => { 50 | if(command.signature) { 51 | console.log(chalk`- {blue.bold ${command.signature}} - {gray.italic ${command.description}}`); 52 | } 53 | }); 54 | console.log(`\n`); 55 | process.exit(0); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/abiUtil.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js'); 2 | const stringUtil = require('./stringUtil.js'); 3 | const Web3 = require('web3'); 4 | 5 | const web3 = new Web3(); 6 | 7 | const abiUtil = { 8 | 9 | getAbiItemSignature: (item) => { 10 | const inputs = []; 11 | if(item.inputs && item.inputs.length > 0) { 12 | item.inputs.map(input => inputs.push(input.type)); 13 | } 14 | return `${item.name}(${inputs.join(',')})`; 15 | }, 16 | 17 | getAbiItemSigHash: (item) => { 18 | const sig = abiUtil.getAbiItemSignature(item); 19 | return abiUtil.getSelector(sig); 20 | }, 21 | 22 | getSelector: (signature) => { 23 | const hash = web3.utils.sha3(signature); 24 | return hash.substring(0, 10); 25 | }, 26 | 27 | parseVariableValue: (type, hex) => { 28 | hex = stringUtil.remove0x(hex); 29 | 30 | if(type.startsWith('struct')) { 31 | return 'composite value'; 32 | } 33 | 34 | if(type.startsWith('contract')) { 35 | return `0x${hex}`; 36 | } 37 | 38 | if(type === 'bytes' || type.includes('[]') || type.startsWith('mapping')) { 39 | return 'dynamic value'; 40 | } 41 | 42 | if(type.startsWith('uint')) { 43 | return (new BN(hex, 16)).toString(10); 44 | } 45 | 46 | if(type.startsWith('int')) { 47 | return (new BN(hex, 16)).fromTwos(256).toString(10); 48 | } 49 | 50 | if(type.startsWith('bytes')) { 51 | return web3.utils.toAscii(`0x${hex}`); 52 | } 53 | 54 | if(type === 'string') { 55 | if(hex === '0'.repeat(62)) return 'dynamic value'; 56 | return web3.utils.toAscii(`0x${hex}`); 57 | } 58 | 59 | if(type === 'bool') { 60 | return hex === '01' ? 'true' : 'false'; 61 | } 62 | 63 | if(type === 'address') { 64 | return `0x${hex}`; 65 | } 66 | 67 | throw new Error(`abiUtil cannot determine the value of variable of type ${type}`); 68 | } 69 | }; 70 | 71 | module.exports = abiUtil; 72 | -------------------------------------------------------------------------------- /src/utils/astUtil.js: -------------------------------------------------------------------------------- 1 | const stringUtil = require('./stringUtil'); 2 | const highlightUtil = require('./highlightUtil'); 3 | const getArtifacts = require('./getArtifacts'); 4 | const chalk = require('chalk'); 5 | 6 | const astUtil = { 7 | 8 | findNodeWithCondition: (ast, conditionFunction) => { 9 | for(let i = 0; i < ast.nodes.length; i++) { 10 | const node = ast.nodes[i]; 11 | if(conditionFunction(node)) return node; 12 | if(node.nodes) { 13 | const match = astUtil.findNodeWithCondition(node, conditionFunction); 14 | if(match) return match; 15 | } 16 | } 17 | }, 18 | 19 | findNodeWithId: (ast, id) => { 20 | return astUtil.findNodeWithCondition(ast, node => node.id === id); 21 | }, 22 | 23 | findNodeWithTypeAndName: (ast, type, name) => { 24 | return astUtil.findNodeWithCondition(ast, node => node.nodeType === type && node.name === name); 25 | }, 26 | 27 | getLinearizedBaseContractNodes: async (ast, contractDefinition, basedir) => { 28 | // A ContractDefinition's linearizedBaseContracts property is an array 29 | // of numeric id's that refer to ContractDefinition's, e.g: 30 | // [13, 35, 47] 31 | // The ContractDefinitions with such id's may or may not be stored in 32 | // the ast and may have to be found on another file's ast. 33 | let nodes = []; 34 | for(let i = 0; i < contractDefinition.linearizedBaseContracts.length; i++) { 35 | const baseContractId = contractDefinition.linearizedBaseContracts[i]; 36 | let baseContractDef = astUtil.findNodeWithId(ast, baseContractId); 37 | if(baseContractDef) nodes.push(baseContractDef); 38 | else { // Base contract definition is stored in a different file. 39 | // Get file name. 40 | let baseContractName; 41 | for(let i = 0; i < contractDefinition.baseContracts.length; i++) { 42 | const baseContract = contractDefinition.baseContracts[i]; 43 | if(baseContract.baseName.referencedDeclaration === baseContractId) { 44 | baseContractName = baseContract.baseName.name; 45 | break; 46 | } 47 | } 48 | if(baseContractName) { 49 | // Load the file. 50 | const baseContractPath = `${basedir}/${baseContractName}.json`; 51 | const { artifacts } = await getArtifacts(baseContractPath); 52 | const baseContractAst = artifacts.ast; 53 | if(!baseContractAst) throw new Error(`AST data not found for ${baseContractPath}`); 54 | // Look for target definition there. 55 | baseContractDef = astUtil.findNodeWithTypeAndName(baseContractAst, 'ContractDefinition', baseContractName); 56 | const baseContractNodes = await astUtil.getLinearizedBaseContractNodes(baseContractAst, baseContractDef, basedir); 57 | // Combine the findings. 58 | nodes = [...nodes, ...baseContractNodes]; 59 | } 60 | } 61 | } 62 | return nodes; 63 | }, 64 | 65 | getVariableDeclarationBytesSize: (contractDefinition, node) => { 66 | if(node.nodeType !== 'VariableDeclaration') throw new Error('Not a VariableDeclaraction node.'); 67 | const type = node.typeDescriptions.typeString; 68 | 69 | if(type.startsWith('struct')) { 70 | 71 | // The type of the struct is actually declared in another node. 72 | const declarationId = node.typeName.baseType.referencedDeclaration; 73 | const declarationNode = astUtil.findNodeWithId(contractDefinition, declarationId); 74 | 75 | // Accumulate the size of the members. 76 | let sum = 0; 77 | for(let i = 0; i < declarationNode.members.length; i++) { 78 | const member = declarationNode.members[i]; 79 | sum += astUtil.getVariableDeclarationBytesSize(contractDefinition, member); 80 | } 81 | return sum; 82 | } 83 | 84 | if(type.startsWith('contract') || type.startsWith('address')) { 85 | return 20; 86 | } 87 | 88 | if(type.startsWith('mapping') || type === 'bytes' || type.includes('[]')) { 89 | return 32; 90 | } 91 | 92 | if(type.startsWith('string')) { 93 | // TODO: It's actually 31 or more. The actual length is stored in the last byte of the first word. 94 | return 32; 95 | } 96 | 97 | if(type.startsWith('bytes')) { 98 | const bytes = stringUtil.getNumericSubstring(type); 99 | return bytes; 100 | } 101 | 102 | if(type.startsWith('uint') || type.startsWith('int')) { 103 | const bits = stringUtil.getNumericSubstring(type); 104 | return bits / 8; 105 | } 106 | 107 | if(type === 'bool') { 108 | return 1; 109 | } 110 | 111 | throw new Error(`astUtil cannot determine the size of variable of type ${type}`); 112 | }, 113 | 114 | sortNodes: (nodes) => { 115 | const nodeOrder = [ 116 | 'UsingForDirective', 117 | 'EnumDefinition', 118 | 'StructDefinition', 119 | 'VariableDeclaration', 120 | 'EventDefinition', 121 | 'ModifierDefinition', 122 | 'FunctionDefinition', 123 | ]; 124 | nodes.sort((node1, node2) => { 125 | return nodeOrder.indexOf(node1.nodeType) > nodeOrder.indexOf(node2.nodeType) ? 1 : -1; 126 | }); 127 | return nodes; 128 | }, 129 | 130 | parseNodeToString: (node, highlightTerm) => { 131 | 132 | function parseParameterList(list) { 133 | if(list.parameters.length === 0) return ''; 134 | const paramStrings = []; 135 | list.parameters.map((parameter) => { 136 | const type = parameter.typeName.name || parameter.typeDescriptions.typeString; 137 | paramStrings.push(`${type}${parameter.name ? ' ' + parameter.name : ''}`); 138 | }); 139 | return paramStrings.join(', '); 140 | } 141 | 142 | function parseEnumMembers(members) { 143 | if(members.length === 0) return ''; 144 | const memberStrings = []; 145 | members.map((member) => { 146 | memberStrings.push(` ${member.name}`); 147 | }); 148 | return memberStrings.join(',\n'); 149 | } 150 | 151 | function parseModifierInvocations(modifiers) { 152 | if(modifiers.length === 0) return ''; 153 | const modifierStrings = []; 154 | modifiers.map((modifier) => { 155 | if(modifier.arguments) { 156 | modifierStrings.push(`${modifier.modifierName.name}(${modifier.arguments.map(arg => arg.value).join(', ')})`); 157 | } else modifierStrings.push(modifier.modifierName.name); 158 | }); 159 | return modifierStrings.join(' '); 160 | } 161 | 162 | let str = ''; 163 | switch(node.nodeType) { 164 | case 'FunctionDefinition': 165 | if(node.kind) { 166 | if(node.kind === 'constructor') str += `constructor`; 167 | else if(node.kind === 'function' || node.kind === 'fallback') str += `function ` + node.name; 168 | } 169 | else str += `function ` + node.name; 170 | str += '('; 171 | str += parseParameterList(node.parameters); 172 | str += ')'; 173 | str += ' '; 174 | str += `${node.visibility}`; 175 | if(node.stateMutability !== 'nonpayable') str += ' ' + `${node.stateMutability}`; 176 | if(node.modifiers && node.modifiers.length > 0) str += ` ${parseModifierInvocations(node.modifiers)}`; 177 | if(node.returnParameters.parameters.length > 0) str += ' returns(' + parseParameterList(node.returnParameters) + ')'; 178 | if(node.implemented) str += ` {...}`; 179 | else str += ';'; 180 | break; 181 | case 'VariableDeclaration': 182 | let varType = node.typeDescriptions.typeString; 183 | varType = varType.replace('contract ', ''); // Hide "'contract '" part of custom types. 184 | str += `${varType}` + ' '; 185 | let varVisibility = node.visibility; 186 | varVisibility = varVisibility.replace('internal', ''); // Hide internal visibility keywords. 187 | if(varVisibility) str += `${varVisibility}` + ' '; 188 | if(node.constant) str += `constant `; 189 | str += node.name; 190 | str += ';'; 191 | break; 192 | case 'EventDefinition': 193 | str += `event ` + node.name; 194 | str += '('; 195 | str += parseParameterList(node.parameters); 196 | str += ')'; 197 | str += ';'; 198 | break; 199 | case 'ModifierDefinition': 200 | str += `modifier `; 201 | str += node.name; 202 | str += '('; 203 | str += parseParameterList(node.parameters); 204 | str += ')'; 205 | str += ` {...}`; 206 | break; 207 | case 'StructDefinition': 208 | str += `struct `; 209 | if(node.visibility) str += node.visibility + ' '; 210 | str += node.name; 211 | str += ' {\n'; 212 | node.members.map((member) => { 213 | str += ' ' + astUtil.parseNodeToString(member) + '\n'; 214 | }); 215 | str += ' }'; 216 | break; 217 | case 'UsingForDirective': 218 | str += `using `; 219 | str += node.libraryName.name; 220 | str += ' for '; 221 | str += node.typeName.name + ';'; 222 | break; 223 | case 'EnumDefinition': 224 | str += 'enum '; 225 | str += node.name; 226 | str += ' {\n'; 227 | str += parseEnumMembers(node.members); 228 | str += '\n }'; 229 | break; 230 | default: 231 | console.log(chalk`{yellow.bold WARNING}: astUtil does not know how to convert node type ${node.nodeType} to string.`); 232 | str += node.name; 233 | } 234 | 235 | return highlightUtil.syntax(str, highlightTerm); 236 | } 237 | }; 238 | 239 | module.exports = astUtil; 240 | -------------------------------------------------------------------------------- /src/utils/cli.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const exec = require('child_process').exec; 3 | 4 | module.exports = function cli(...args) { 5 | const cwd = '.'; 6 | 7 | // Determine executable depending on working directory. 8 | // Use `node src/program` in development and testing. 9 | // Use the installed bnary in path `pocketh` otherwise. 10 | const runningDir = path.basename(process.cwd()); 11 | const executable = runningDir === 'pocketh' ? 'node src/program' : 'pocketh'; 12 | 13 | return new Promise(resolve => { 14 | exec( 15 | `${executable} ${args.join(' ')}`, 16 | { cwd }, 17 | (error, stdout, stderr) => { 18 | const err = error || stderr; 19 | // if(err) console.log(err); 20 | resolve({ 21 | code: err ? 1 : 0, 22 | error: err, 23 | stdout, 24 | stderr 25 | }); 26 | } 27 | ); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/etherscanApi.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | // Docs: https://etherscan.io/apis 4 | const ETHERSCAN_API = `https://api.etherscan.io/api`; 5 | const apikey = `391YIKRFHH8PANTHRX482KKHSUMBA3NPMF`; 6 | 7 | module.exports = { 8 | 9 | getEtherPrice: async () => { 10 | const response = await axios.get(ETHERSCAN_API, { 11 | params: { 12 | module: 'stats', 13 | action: 'ethprice', 14 | apikey 15 | } 16 | }); 17 | return response.data.result.ethusd; 18 | }, 19 | 20 | getSourceCode: async (address) => { 21 | const response = await axios.get(ETHERSCAN_API, { 22 | params: { 23 | module: 'contract', 24 | action: 'getsourcecode', 25 | address, 26 | apikey 27 | } 28 | }); 29 | return response.data.result[0].SourceCode; 30 | }, 31 | 32 | getSourceCodeFull: async (address) => { 33 | const response = await axios.get(ETHERSCAN_API, { 34 | params: { 35 | module: 'contract', 36 | action: 'getsourcecode', 37 | address, 38 | apikey 39 | } 40 | }); 41 | return response.data.result; 42 | }, 43 | 44 | getAbi: async (address) => { 45 | const response = await axios.get(ETHERSCAN_API, { 46 | params: { 47 | module: 'contract', 48 | action: 'getabi', 49 | address, 50 | apikey 51 | } 52 | }); 53 | return JSON.parse(response.data.result) 54 | }, 55 | }; 56 | 57 | function trace(response) { 58 | console.log(response.data.result); 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/getArtifacts.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const tmp = require('tmp'); 4 | const cli = require('../utils/cli'); 5 | const Web3 = require('web3'); 6 | 7 | let web3; 8 | 9 | module.exports = async function(contractPath) { 10 | 11 | // Used for utils only. 12 | web3 = new Web3(); 13 | 14 | // Determine file extension. 15 | let dirname = path.dirname(contractPath); 16 | const filename = path.basename(contractPath); 17 | const comps = filename.split('.'); 18 | const name = comps[0]; 19 | const ext = comps[1]; 20 | 21 | // Validate path. 22 | if(ext !== 'sol' && ext !== 'json') 23 | throw new Error(`Unrecognized extension ${ext}`); 24 | 25 | // Auto compile? 26 | let compiledPath = contractPath; 27 | if(ext === 'sol') { 28 | dirname = await compileSolidityFile(contractPath); 29 | compiledPath = `${dirname}/${name}.json`; 30 | } 31 | 32 | // Return a wrapper with the artifacts 33 | // and a reference directory for where they're stored. 34 | return { 35 | artifacts: retrieveJsonArtifacts(compiledPath), 36 | basedir: dirname 37 | }; 38 | }; 39 | 40 | async function compileSolidityFile(sourcePath) { 41 | 42 | // Create a temporary directory to store the artifacts in. 43 | // If the directory already exists, then its artifacts can be reused. 44 | const tmpdir = `/tmp/${getSourceCodeHash(sourcePath)}`; 45 | if(fs.existsSync(tmpdir)) return tmpdir; 46 | else fs.mkdirSync(tmpdir); 47 | console.log(`Auto compiling ${sourcePath} to ${tmpdir}/`); 48 | 49 | // Compile the file. 50 | // Wrap it in a try/catch to avoid cancelling the auto compilation 51 | // for trivial errors that solcjs sometimes throws. 52 | let result; 53 | try { 54 | result = await cli( 55 | 'compile', 56 | sourcePath, 57 | tmpdir 58 | ); 59 | 60 | return tmpdir; 61 | } 62 | catch(error) { 63 | // If the error has to do with the json file, then something 64 | // really did go wrong. In that case, do report the compilation error. 65 | if(error.message.includes('Cannot find')) throw new Error(`Unable to auto compile ${sourcePath}: ${result.error}`); 66 | } 67 | } 68 | 69 | function getSourceCodeHash(sourcePath) { 70 | const source = fs.readFileSync(sourcePath, 'utf8'); 71 | return web3.utils.sha3(source).substring(2, 14); 72 | } 73 | 74 | function retrieveJsonArtifacts(path) { 75 | if(!fs.existsSync(path)) throw new Error(`Cannot find ${path}`); 76 | return JSON.parse(fs.readFileSync(path, 'utf8')); 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Web3 = require('web3'); 3 | 4 | const defaultNetworks = { 5 | mainnet: "https://mainnet.infura.io/v3/ac987ae2aa3c436c958e050a82a5c8da", 6 | ropsten: "https://ropsten.infura.io/v3/ac987ae2aa3c436c958e050a82a5c8da", 7 | rinkeby: "https://rinkeby.infura.io/v3/ac987ae2aa3c436c958e050a82a5c8da", 8 | localhost: "http://localhost:8545" 9 | }; 10 | 11 | module.exports = async (network) => { 12 | const provider = defaultNetworks[network] ? defaultNetworks[network] : network; 13 | return new Web3(provider); 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/highlightUtil.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | const highlightUtil = { 4 | 5 | syntax: (text, highlightTerm) => { 6 | 7 | const substitutions = { 8 | 'public' : { matcher: /\bpublic\b/g, output: chalk`{yellow.bold $&}` }, 9 | 'external' : { matcher: /\bexternal\b/g, output: chalk`{yellow.bold $&}` }, 10 | 'private' : { matcher: /\bprivate\b/g, output: chalk`{yellow.bold $&}` }, 11 | 'view' : { matcher: /\bview\b/g, output: chalk`{yellow.italic $&}` }, 12 | 'pure' : { matcher: /\bpure\b/g, output: chalk`{yellow.italic $&}` }, 13 | 'function' : { matcher: /\bfunction\b/g, output: chalk`{blue $&}` }, 14 | 'modifier' : { matcher: /\bmodifier\b/g, output: chalk`{blue.italic $&}` }, 15 | 'address' : { matcher: /\baddress\b/g, output: chalk`{green $&}` }, 16 | 'string' : { matcher: /\bstring\b/g, output: chalk`{green $&}` }, 17 | 'mapping' : { matcher: /\bmapping\b/g, output: chalk`{magenta $&}` }, 18 | 'struct' : { matcher: /\bstruct\b/g, output: chalk`{green.bold $&}` }, 19 | 'constant' : { matcher: /\bconstant\b/g, output: chalk`{gray $&}` }, 20 | 'event' : { matcher: /\bevent\b/g, output: chalk`{cyan $&}` }, 21 | 'int-uint' : { matcher: /\bu?int\w*\b/g, output: chalk`{green $&}` }, 22 | 'enum' : { matcher: /\benum\b/g, output: chalk`{green.bold $&}` }, 23 | 'bytes' : { matcher: /\bbytes\w*\b/g, output: chalk`{green $&}` }, 24 | 'bool' : { matcher: /\bbool\b/g, output: chalk`{green $&}` }, 25 | '[]' : { matcher: /\[\]/g, output: chalk`{red $&}` }, 26 | '{...}' : { matcher: /\{\.\.\.\}/g, output: chalk`{gray $&}` }, 27 | 'internal' : { matcher: /\binternal\b/g, output: chalk`{yellow $&}` }, 28 | }; 29 | 30 | // Highlight term? 31 | if(highlightTerm) { 32 | substitutions[highlightTerm] = { 33 | matcher: new RegExp(`\\b${highlightTerm}\\b`, 'g'), 34 | output: chalk`{bgRed $&}` 35 | }; 36 | } 37 | 38 | // Apply all substitutions. 39 | Object.keys(substitutions).map(key => { 40 | const substitution = substitutions[key]; 41 | text = text.replace(substitution.matcher, substitution.output); 42 | }); 43 | 44 | return text; 45 | } 46 | }; 47 | 48 | module.exports = highlightUtil; 49 | -------------------------------------------------------------------------------- /src/utils/log.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = (str) => { 4 | fs.writeFileSync(`log`, str); 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/stringUtil.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getNumericSubstring: (str) => { 3 | const digits = str.match(/\d+/); 4 | if(!digits) throw new Error('No digits found in string.') 5 | return parseInt(digits, 10); 6 | }, 7 | 8 | remove0x: (hex) => { 9 | return hex.startsWith('0x') ? hex.substring(2, hex.length) : hex; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/validateUtil.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | 3 | const web3 = new Web3(); 4 | 5 | const validateUtil = { 6 | 7 | positiveInteger: (value) => { 8 | return value.match(/^\+?[1-9][\d]*$/); 9 | }, 10 | 11 | integer: (value) => { 12 | return value.match(/^[\+|\-]?[1-9][\d]*$/); 13 | }, 14 | 15 | hex: (value) => { 16 | return value.match(/^0x[a-fA-F0-9]+$/); 17 | }, 18 | 19 | bytes32: (value) => { 20 | return validateUtil.hex(value) && value.length === 66; 21 | }, 22 | 23 | address: (value) => { 24 | return validateUtil.hex(value) && value.length === 42; 25 | }, 26 | 27 | checksumAddress: (value) => { 28 | return web3.utils.isAddresss(value); 29 | } 30 | }; 31 | 32 | module.exports = validateUtil; 33 | -------------------------------------------------------------------------------- /test/artifacts/SampleAbstract.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "SampleAbstract", 3 | "abi": [ 4 | { 5 | "constant": true, 6 | "inputs": [], 7 | "name": "test", 8 | "outputs": [ 9 | { 10 | "name": "", 11 | "type": "string" 12 | } 13 | ], 14 | "payable": false, 15 | "stateMutability": "pure", 16 | "type": "function" 17 | }, 18 | { 19 | "anonymous": false, 20 | "inputs": [ 21 | { 22 | "indexed": false, 23 | "name": "addr", 24 | "type": "address" 25 | } 26 | ], 27 | "name": "AnEvent", 28 | "type": "event" 29 | } 30 | ], 31 | "metadata": "{\"compiler\":{\"version\":\"0.5.8+commit.23d335f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"test\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"AnEvent\",\"type\":\"event\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"SampleAbstract.sol\":\"SampleAbstract\"},\"evmVersion\":\"petersburg\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"SampleAbstract.sol\":{\"keccak256\":\"0x5bea0368db7d1e12909ddef38a1df8eb6751f889687fdca1993bde4bc5a531ed\",\"urls\":[\"bzzr://2b5af4acf10f440faf2fe67c31002d9ef1ab752e974bde1b7508cdf7fcc5ce1f\"]}},\"version\":1}", 32 | "bytecode": "", 33 | "deployedBytecode": "", 34 | "ast": { 35 | "absolutePath": "SampleAbstract.sol", 36 | "exportedSymbols": { 37 | "SampleAbstract": [ 38 | 47 39 | ] 40 | }, 41 | "id": 48, 42 | "nodeType": "SourceUnit", 43 | "nodes": [ 44 | { 45 | "id": 37, 46 | "literals": [ 47 | "solidity", 48 | "^", 49 | "0.5", 50 | ".0" 51 | ], 52 | "nodeType": "PragmaDirective", 53 | "src": "0:23:1" 54 | }, 55 | { 56 | "baseContracts": [], 57 | "contractDependencies": [], 58 | "contractKind": "contract", 59 | "documentation": null, 60 | "fullyImplemented": false, 61 | "id": 47, 62 | "linearizedBaseContracts": [ 63 | 47 64 | ], 65 | "name": "SampleAbstract", 66 | "nodeType": "ContractDefinition", 67 | "nodes": [ 68 | { 69 | "anonymous": false, 70 | "documentation": null, 71 | "id": 41, 72 | "name": "AnEvent", 73 | "nodeType": "EventDefinition", 74 | "parameters": { 75 | "id": 40, 76 | "nodeType": "ParameterList", 77 | "parameters": [ 78 | { 79 | "constant": false, 80 | "id": 39, 81 | "indexed": false, 82 | "name": "addr", 83 | "nodeType": "VariableDeclaration", 84 | "scope": 41, 85 | "src": "68:12:1", 86 | "stateVariable": false, 87 | "storageLocation": "default", 88 | "typeDescriptions": { 89 | "typeIdentifier": "t_address", 90 | "typeString": "address" 91 | }, 92 | "typeName": { 93 | "id": 38, 94 | "name": "address", 95 | "nodeType": "ElementaryTypeName", 96 | "src": "68:7:1", 97 | "stateMutability": "nonpayable", 98 | "typeDescriptions": { 99 | "typeIdentifier": "t_address", 100 | "typeString": "address" 101 | } 102 | }, 103 | "value": null, 104 | "visibility": "internal" 105 | } 106 | ], 107 | "src": "67:14:1" 108 | }, 109 | "src": "54:28:1" 110 | }, 111 | { 112 | "body": null, 113 | "documentation": null, 114 | "id": 46, 115 | "implemented": false, 116 | "kind": "function", 117 | "modifiers": [], 118 | "name": "test", 119 | "nodeType": "FunctionDefinition", 120 | "parameters": { 121 | "id": 42, 122 | "nodeType": "ParameterList", 123 | "parameters": [], 124 | "src": "99:2:1" 125 | }, 126 | "returnParameters": { 127 | "id": 45, 128 | "nodeType": "ParameterList", 129 | "parameters": [ 130 | { 131 | "constant": false, 132 | "id": 44, 133 | "name": "", 134 | "nodeType": "VariableDeclaration", 135 | "scope": 46, 136 | "src": "122:13:1", 137 | "stateVariable": false, 138 | "storageLocation": "memory", 139 | "typeDescriptions": { 140 | "typeIdentifier": "t_string_memory_ptr", 141 | "typeString": "string" 142 | }, 143 | "typeName": { 144 | "id": 43, 145 | "name": "string", 146 | "nodeType": "ElementaryTypeName", 147 | "src": "122:6:1", 148 | "typeDescriptions": { 149 | "typeIdentifier": "t_string_storage_ptr", 150 | "typeString": "string" 151 | } 152 | }, 153 | "value": null, 154 | "visibility": "internal" 155 | } 156 | ], 157 | "src": "121:15:1" 158 | }, 159 | "scope": 47, 160 | "src": "86:51:1", 161 | "stateMutability": "pure", 162 | "superFunction": null, 163 | "visibility": "public" 164 | } 165 | ], 166 | "scope": 48, 167 | "src": "25:114:1" 168 | } 169 | ], 170 | "src": "0:140:1" 171 | } 172 | } -------------------------------------------------------------------------------- /test/commands/block.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | 4 | describe('block command', () => { 5 | 6 | let web3; 7 | let network; 8 | let lastTx; 9 | 10 | beforeEach(async () => { 11 | 12 | // Set up web3. 13 | network = 'http://localhost:8545'; 14 | web3 = await getWeb3(network); 15 | 16 | // Send a dummy transaction so that there is at least 1 block. 17 | const accounts = await web3.eth.getAccounts(); 18 | lastTx = await web3.eth.sendTransaction({ 19 | from: accounts[0], 20 | to: accounts[1], 21 | value: 1, 22 | gas: 100000, 23 | gasPrice: 1 24 | }); 25 | }); 26 | 27 | test('Should properly query a known block in localhost', async () => { 28 | 29 | // Get latest block. 30 | const latestBlockNumber = await web3.eth.getBlockNumber(); 31 | 32 | // Trigger command by block number. 33 | let result = await cli( 34 | 'block', 35 | network, 36 | latestBlockNumber 37 | ); 38 | const block = JSON.parse(result.stdout); 39 | 40 | // Verify results. 41 | expect(block.number).toBe(latestBlockNumber); 42 | expect(block.transactions.length).toBe(1); 43 | expect(block.transactions[0]).toBe(lastTx.transactionHash); 44 | 45 | // Trigger command by block hash. 46 | result = await cli( 47 | 'block', 48 | network, 49 | block.hash 50 | ); 51 | const blockA = JSON.parse(result.stdout); 52 | 53 | // Verify results. 54 | expect(blockA.hash).toBe(block.hash); 55 | }); 56 | 57 | test('Should complain when the block identifier is invalid', async () => { 58 | 59 | // Trigger command with an invalid block id. 60 | const blockId = `spongy`; 61 | const result = await cli( 62 | 'block', 63 | network, 64 | blockId 65 | ); 66 | expect(result.code).toBe(1); 67 | expect(result.stderr).toContain(`Invalid blockHashOrNumber: ${blockId}`); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/commands/blockdate.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | 4 | describe('blockdate command', () => { 5 | 6 | let web3; 7 | let network; 8 | let lastTx; 9 | 10 | beforeEach(async () => { 11 | 12 | // Set up web3. 13 | network = 'http://localhost:8545'; 14 | web3 = await getWeb3(network); 15 | 16 | // Send a dummy transaction so that there is at least 1 block. 17 | const accounts = await web3.eth.getAccounts(); 18 | lastTx = await web3.eth.sendTransaction({ 19 | from: accounts[0], 20 | to: accounts[1], 21 | value: 1, 22 | gas: 100000, 23 | gasPrice: 1 24 | }); 25 | }); 26 | 27 | test('Should get the correct date for a block that has just been mined in localhost', async () => { 28 | 29 | // Get latest block. 30 | const latestBlockNumber = await web3.eth.getBlockNumber(); 31 | 32 | // Trigger command by block number. 33 | let result = await cli( 34 | 'blockdate', 35 | network, 36 | latestBlockNumber 37 | ); 38 | 39 | // Convert result to date and compare dates. 40 | const date = new Date(result.stdout).getTime(); 41 | const now = new Date().getTime(); 42 | const deltaMilli = now - date; 43 | expect(deltaMilli).toBeLessThan(7000); 44 | }); 45 | 46 | test('Should complain when the block identifier is invalid', async () => { 47 | 48 | // Trigger command with an invalid block id. 49 | const blockId = `spongy`; 50 | const result = await cli( 51 | 'block', 52 | network, 53 | blockId 54 | ); 55 | expect(result.code).toBe(1); 56 | expect(result.stderr).toContain(`Invalid blockHashOrNumber: ${blockId}`); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/commands/calldata.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('calldata command', () => { 4 | 5 | test('Should properly identify selectors', async () => { 6 | const result = await cli('calldata', '0xffffffff'); 7 | expect(result.stdout).toContain('Selector: 0xffffffff'); 8 | }); 9 | 10 | test('Should properly split parameters', async () => { 11 | 12 | const calldata = [ 13 | '0xffffffff', 14 | '0000000000000000000000000000000000000000000000000000000000000001', 15 | '0000000000000000000000000000000000000000000000000000000000000002', 16 | '0000000000000000000000000000000000000000000000000000000000000003' 17 | ].join(''); 18 | 19 | const result = await cli('calldata', calldata); 20 | 21 | expect(result.stdout).toContain( 22 | '0x0000: 0x0000000000000000000000000000000000000000000000000000000000000001' 23 | ); 24 | expect(result.stdout).toContain( 25 | '0x0020: 0x0000000000000000000000000000000000000000000000000000000000000002' 26 | ); 27 | expect(result.stdout).toContain( 28 | '0x0040: 0x0000000000000000000000000000000000000000000000000000000000000003' 29 | ); 30 | }); 31 | 32 | test('Should complain if an invalid hex value is passed', async () => { 33 | const result = await cli('calldata', 'spongy'); 34 | expect(result.code).toBe(1); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/commands/checksum.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('checksum command', () => { 4 | 5 | test('Should checksum an address', async () => { 6 | const result = await cli('checksum', '0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6'); 7 | expect(result.stdout).toContain('0xbCcc714d56bc0da0fd33d96d2a87b680dD6D0DF6'); 8 | }); 9 | 10 | test('Should complain when an invalid address is provided', async () => { 11 | const result = await cli('checksum', '0xbccc714d56bc0da0fd'); 12 | expect(result.code).toBe(1); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/commands/compile.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('compile command', () => { 4 | 5 | test('Should compile a contract without specifying a solc version', async () => { 6 | const result = await cli('compile', 'test/contracts/Test.sol', '/tmp/'); 7 | expect(result.stdout).toContain('Compiled Test.sol succesfully'); 8 | }); 9 | 10 | test('Should compile a contract when specifying a valid solc version', async () => { 11 | const result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '0.5.7'); 12 | expect(result.stdout).toContain('Compiled Test.sol succesfully'); 13 | }); 14 | 15 | test('Should not compile a contract when specifying an invalid solc version', async () => { 16 | const result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '0.4.24'); 17 | expect(result.code).toBe(1); 18 | expect(result.stderr).toContain('is not compatible with the version specified'); 19 | }); 20 | 21 | test('Should not compile when the specified output directory does not exist', async () => { 22 | const result = await cli('compile', 'test/contracts/Test.sol', '/tmp-spongi/', '0.4.24'); 23 | expect(result.code).toBe(1); 24 | expect(result.stderr).toContain('Cannot find'); 25 | }); 26 | 27 | test('Should not compile when the specified output directory is not a valid directory', async () => { 28 | const result = await cli('compile', 'test/contracts/Test.sol', 'test/setup.js', '0.4.24'); 29 | expect(result.code).toBe(1); 30 | expect(result.stderr).toContain('must be a directory path'); 31 | }); 32 | 33 | test('Should not compile when the specified source file does not have a .sol extension', async () => { 34 | const result = await cli('compile', 'test/contracts/Test.txt', '/spongi', '0.4.24'); 35 | expect(result.code).toBe(1); 36 | expect(result.stderr).toContain('Invalid source file'); 37 | }); 38 | 39 | test('Should not compile when the specified source file does not exist', async () => { 40 | const result = await cli('compile', 'test/contracts/Test1.sol', '/tmp/', '0.4.24'); 41 | expect(result.code).toBe(1); 42 | expect(result.stderr).toContain('Cannot find'); 43 | }); 44 | 45 | test('Should auto-detect semver ranges in source', async () => { 46 | const result = await cli('compile', 'test/contracts/Ranges.sol', '/tmp/'); 47 | expect(result.stdout).toContain('Using compiler 0.4.22+commit.4cb486ee.Emscripten.clang'); 48 | }); 49 | 50 | test('Should understand semver ranges in required version', async () => { 51 | const result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', `'>=0.5.7 <0.5.8'`); 52 | expect(result.stdout).toContain('Using compiler 0.5.7+commit.6da8b019.Emscripten.clang'); 53 | }); 54 | 55 | test('Additional search paths should be specifiable', async () => { 56 | const result = await cli('compile', 'test/contracts/SearchPaths.sol', '/tmp/', `--searchPaths`, 'searchpath/'); 57 | expect(result.code).toBe(0); 58 | }); 59 | 60 | test('Should be able to resolve dependencies in node_modules', async () => { 61 | let result = await cli('compile', 'test/contracts/NodeModules.sol', '/tmp/'); 62 | expect(result.code).toBe(0); 63 | result = await cli('compile', 'test/contracts/subdir/subsubdir/NodeModules.sol', '/tmp/'); 64 | expect(result.code).toBe(0); 65 | }); 66 | 67 | test('Should be able to use a list of already downloaded compilers if one cannot be retrieved from solcjs/bin', async () => { 68 | 69 | // Do a normal compilation to make sure at least one compiler is cached. 70 | let result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '0.5.8'); 71 | expect(result.code).toBe(0); 72 | 73 | // Now, compile again with an invalid url. 74 | result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '--solcbin', 'http://www.google.com'); 75 | expect(result.stdout).toContain('Using one of the already downloaded'); 76 | expect(result.code).toBe(0); 77 | }); 78 | 79 | test('Should cache and reuse downloaded compilers', async () => { 80 | 81 | // Do a normal compilation to make sure at least one compiler is cached. 82 | let result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '0.5.8'); 83 | expect(result.code).toBe(0); 84 | 85 | // Now, compile again with an invalid url. 86 | result = await cli('compile', 'test/contracts/Test.sol', '/tmp/', '0.5.8'); 87 | expect(result.stdout.includes('Downloading')).toBe(false); 88 | expect(result.code).toBe(0); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/commands/convert.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('convert command', () => { 4 | 5 | test('Should properly convert between different denominations', async () => { 6 | expect((await cli( 7 | 'convert', '1', 'wei', 'ether' 8 | )).stdout).toContain('0.000000000000000001'); 9 | expect((await cli( 10 | 'convert', '0.00000000000001', 'finney', 'wei' 11 | )).stdout).toContain('10'); 12 | expect((await cli( 13 | 'convert', '1', 'ether', 'szabo' 14 | )).stdout).toContain('1000000'); 15 | expect((await cli( 16 | 'convert', '0.5', 'tether', 'ether' 17 | )).stdout).toContain('500000000000'); 18 | }); 19 | 20 | test('Should list all denominations if no value is given', async () => { 21 | expect((await cli( 22 | 'convert', 23 | )).stdout).toContain('nano: \'1000000000\''); 24 | }); 25 | 26 | test('Should default to wei to ether if no denominations are given', async () => { 27 | expect((await cli( 28 | 'convert', '1' 29 | )).stdout).toContain('0.000000000000000001'); 30 | }); 31 | 32 | test('Should complain on unknown denominations', async () => { 33 | expect((await cli( 34 | 'convert', '1', 'wei', 'spongy' 35 | )).code).toBe(1); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/commands/disassemble.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('disassemble command', () => { 4 | 5 | test('Should produce expected opcode output from precompiled artifacts', async () => { 6 | const result = await cli('disassemble', 'test/artifacts/Test.json'); 7 | expect(result.stdout).toContain(`75 \{0x63\} [c127, r87] PUSH4 0xf0686273 (dec 4033372787)`); 8 | expect(result.stdout).toContain(`76 \{0x14\} [c132, r92] EQ`); 9 | expect(result.stdout).toContain(`77 \{0x61\} [c133, r93] PUSH2 0x010d (dec 269)`); 10 | expect(result.stdout).toContain(`258 {0x81} [c367, r327] DUP2`); 11 | }); 12 | 13 | test('Should produce expected opcode output from a known solidity file', async () => { 14 | const result = await cli('disassemble', 'test/contracts/Test.sol'); 15 | expect(result.stdout).toContain(`75 \{0x63\} [c127, r87] PUSH4 0xf0686273 (dec 4033372787)`); 16 | expect(result.stdout).toContain(`76 \{0x14\} [c132, r92] EQ`); 17 | expect(result.stdout).toContain(`77 \{0x61\} [c133, r93] PUSH2 0x010d (dec 269)`); 18 | expect(result.stdout).toContain(`258 {0x81} [c367, r327] DUP2`); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/commands/docyul.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('docyul command', () => { 4 | 5 | test('Should produce expected output for certain keywords', async () => { 6 | expect((await cli( 7 | 'docyul', 'delegatecall' 8 | )).stdout).toContain(`delegatecall(g:u256, a:u256, in:u256, insize:u256, out:u256, outsize:u256) ‑> r:u256`); 9 | expect((await cli( 10 | 'docyul', 'sstore' 11 | )).stdout).toContain(`sstore(p:u256, v:u256)`); 12 | }); 13 | 14 | test('Should list all documentation if no keyword is given', async () => { 15 | const output = (await cli( 'docyul')).stdout; 16 | expect(output).toContain(`not(x:bool) ‑> z:bool`); 17 | expect(output).toContain(`gtu256(x:u256, y:u256) ‑> z:bool`); 18 | expect(output).toContain(`mstore8(p:u256, v:u256)`); 19 | expect(output).toContain(`selfdestruct(a:u256)`); 20 | expect(output).toContain(`gasleft() ‑> gas:u256`); 21 | }); 22 | 23 | test('Should display a "no documentation found message" if an invalid keyword is given', async () => { 24 | expect((await cli( 25 | 'docyul', 'spongi' 26 | )).stdout).toContain(`No Yul documentation found for 'spongi'`); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/commands/getcode.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const tmp = require('tmp'); 3 | const fs = require('fs'); 4 | 5 | describe('getcode command', () => { 6 | 7 | test('Should retrieve code from mainnet and print it to stdout if no destination path is specified', async () => { 8 | const result = await cli('getcode', '0x06012c8cf97bead5deae237070f9587f8e7a266d'); 9 | expect(result.stdout).toContain(`contract KittyCore is KittyMinting`); 10 | }); 11 | 12 | test('Should retrieve code from mainnet and store it in a file', async () => { 13 | 14 | // Create a temporary directory to hold the output. 15 | const tmpdir = tmp.dirSync(); 16 | const filepath = `${tmpdir.name}/getcode.test`; 17 | 18 | // Trigger the command. 19 | const result = await cli( 20 | 'getcode', 21 | '0x06012c8cf97bead5deae237070f9587f8e7a266d', 22 | filepath 23 | ); 24 | 25 | // Verify command output. 26 | expect(result.stdout).toContain(`Source code written to ${filepath}`); 27 | 28 | // Verify that the file was produced and that it contains the expected text. 29 | expect(fs.existsSync(filepath)).toBe(true); 30 | const content = fs.readFileSync(filepath, 'utf8'); 31 | expect(content).toContain(`contract KittyCore is KittyMinting`); 32 | }); 33 | 34 | test('Should complain when an invalid address is provided', async () => { 35 | const result = await cli('getcode', '0xbccc714d56bc0da0fd'); 36 | expect(result.code).toBe(1); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/commands/hex2int.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('hex2int command', () => { 4 | 5 | test('Should properly convert large twos complement hex to a negative integer', async () => { 6 | const result = await cli('hex2int', '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6'); 7 | expect(result.stdout).toContain('-42'); 8 | }); 9 | 10 | test('Should properly convert large twos complement hex to a positive integer', async () => { 11 | const result = await cli('hex2int', '0x00000000000000000000000000000000000000000000000000000000000007be'); 12 | expect(result.stdout).toContain('1982'); 13 | }); 14 | 15 | test('Should complain if an invalid hex number is provided', async () => { 16 | expect((await cli('hex2int', 'spongy')).code).toBe(1); 17 | expect((await cli('hex2int', '0.1')).code).toBe(1); 18 | expect((await cli('hex2int', '0xqqq')).code).toBe(1); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/commands/hex2str.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('hex2uint command', () => { 4 | 5 | test('Should properly convert "Hello"', async () => { 6 | const result = await cli('hex2str', '0x48656c6c6f'); 7 | expect(result.stdout).toContain('Hello'); 8 | }); 9 | 10 | test('Should complain if an invalid hex number is provided', async () => { 11 | expect((await cli('hex2uint', 'spongy')).code).toBe(1); 12 | expect((await cli('hex2uint', '0.1')).code).toBe(1); 13 | expect((await cli('hex2uint', '0xqqq')).code).toBe(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/commands/hex2uint.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('hex2uint command', () => { 4 | 5 | test('Should properly convert a small hex to a positive integer', async () => { 6 | const result = await cli('hex2uint', '0x2a'); 7 | expect(result.stdout).toContain('42'); 8 | }); 9 | 10 | test('Should properly convert a large hex to a positive integer', async () => { 11 | const result = await cli('hex2uint', '0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b'); 12 | expect(result.stdout).toContain('1381342429069413442493521589863962471307977493563'); 13 | }); 14 | 15 | test('Should complain if an invalid hex number is provided', async () => { 16 | expect((await cli('hex2uint', 'spongy')).code).toBe(1); 17 | expect((await cli('hex2uint', '0.1')).code).toBe(1); 18 | expect((await cli('hex2uint', '0xqqq')).code).toBe(1); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/commands/info.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | 4 | describe('info command', () => { 5 | 6 | test('Should read the latest block from mainnet', async () => { 7 | 8 | // Set up web3. 9 | const web3 = await getWeb3('mainnet'); 10 | 11 | // Get latest block. 12 | const latestBlockNumber = await web3.eth.getBlockNumber(); 13 | 14 | // Trigger info command. 15 | const result = await cli( 16 | 'info', 17 | 'mainnet' 18 | ); 19 | 20 | // Extract latest block from result. 21 | const blockNumber = parseInt(result.stdout.match(/(?<=latestBlock:).*/g)); 22 | 23 | // Compare the latest block with a delta, to account for race conditions. 24 | const delta = blockNumber - latestBlockNumber; 25 | expect(delta).toBeLessThan(3); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/commands/inheritance.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('inheritance command', () => { 4 | 5 | test('Should properly list the inheritance of Test.json', async () => { 6 | const result = await cli('inheritance', './test/artifacts/Test.json'); 7 | expect(result.stdout).toContain(`└─ Test 8 | ├─ Parent1 9 | │ └─ GrandParent 10 | └─ Parent2`); 11 | }); 12 | 13 | test('Should properly list the inheritance of Test.json with linearized option', async () => { 14 | const result = await cli('inheritance', './test/artifacts/Test.json', '--linearized'); 15 | expect(result.stdout).toContain(`├─ Test 16 | ├─ Parent2 17 | ├─ Parent1 18 | └─ GrandParent`); 19 | }); 20 | 21 | test('Should properly list inheritance of Test.sol', async () => { 22 | const result = await cli('inheritance', './test/contracts/Test.sol'); 23 | expect(result.stdout).toContain(`└─ Test 24 | ├─ Parent1 25 | │ └─ GrandParent 26 | └─ Parent2`); 27 | }); 28 | 29 | test('Should properly list the inheritance of Sample.json', async () => { 30 | const result = await cli('inheritance', './test/artifacts/Sample.json'); 31 | expect(result.stdout).toContain(`└─ Sample 32 | └─ SampleDependency 33 | └─ SampleAbstract`); 34 | }); 35 | 36 | test('Should properly list the inheritance of Sample.sol', async () => { 37 | const result = await cli('inheritance', './test/contracts/Sample.sol'); 38 | expect(result.stdout).toContain(`└─ Sample 39 | └─ SampleDependency 40 | └─ SampleAbstract`); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/commands/int2hex.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('int2hex command', () => { 4 | 5 | test('Should properly convert a small negative integer to hex', async () => { 6 | const result = await cli('int2hex', '-n', '42'); 7 | expect(result.stdout).toContain('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6'); 8 | }); 9 | 10 | test('Should properly convert a small positive integer to hex', async () => { 11 | const result = await cli('int2hex', '42'); 12 | expect(result.stdout).toContain('0x2a'); 13 | }); 14 | 15 | test('Should complain if an invalid integer is provided', async () => { 16 | expect((await cli('int2hex', 'xx')).code).toBe(1); 17 | expect((await cli('int2hex', '0.1')).code).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/commands/liststorage.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('liststorage command', () => { 4 | 5 | test('Should properly read storage from a contract deployed in ropsten', async () => { 6 | const result = await cli( 7 | 'liststorage', 8 | 'ropsten', 9 | 'test/contracts/Storage.sol', 10 | '0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b', 11 | '--disable-colors' 12 | ); 13 | expect(result.stdout).toContain(`value: 15`); 14 | expect(result.stdout).toContain(`value: 17055`); 15 | expect(result.stdout).toContain(`value: 1505308058`); 16 | expect(result.stdout).toContain(`value: test1`); 17 | expect(result.stdout).toContain(`value: test1236`); 18 | expect(result.stdout).toContain(`lets string something`); 19 | }); 20 | 21 | test('Should complain when an invalid contract address is provided', async () => { 22 | const result = await cli('liststorage', 'localhost', 'test/artifacts/Storage.json', '0x123'); 23 | expect(result.code).toBe(1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/commands/members.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('members command', () => { 4 | 5 | test('Should properly identify the members of Test.sol', async () => { 6 | const result = await cli('members', './test/artifacts/Test.json', '--disable-colors'); 7 | expect(result.stdout).toContain(`uint256 public value`); 8 | expect(result.stdout).toContain(`uint256 public constant CONS`); 9 | expect(result.stdout).toContain(`constructor() public {...}`); 10 | expect(result.stdout).toContain(`function test(uint256 newValue) public aModifier anotherModifier(42) {...}`); 11 | }); 12 | 13 | test('Should properly identify the members of Test.sol and its ancestors', async () => { 14 | const result = await cli('members', '--inherited', './test/artifacts/Test.json', '--disable-colors'); 15 | expect(result.stdout).toContain(`uint256 public granparent_value`); 16 | expect(result.stdout).toContain(`uint256 public parent1_value`); 17 | expect(result.stdout).toContain(`uint256 public parent2_value`); 18 | expect(result.stdout).toContain(`uint256 public value`); 19 | expect(result.stdout).toContain(`uint256 public constant CONS`); 20 | expect(result.stdout).toContain(`constructor() public {...}`); 21 | expect(result.stdout).toContain(`function test(uint256 newValue) public aModifier anotherModifier(42) {...}`); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/commands/pad.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('pad command', () => { 4 | 5 | test('Sould properly pad a small hex', async () => { 6 | const result = await cli('pad', '0x2a'); 7 | expect(result.stdout).toContain('0x000000000000000000000000000000000000000000000000000000000000002a'); 8 | }); 9 | 10 | test('Sould properly pad a large hex', async () => { 11 | const result = await cli('pad', '0x66666666666666666666666666666666666666666666666666666666666662a'); 12 | expect(result.stdout).toContain('0x066666666666666666666666666666666666666666666666666666666666662a'); 13 | }); 14 | 15 | test('Sould not pad a hex that fills a word', async () => { 16 | const result = await cli('pad', '0x666666666666666666666666666666666666666666666666666666666666662a'); 17 | expect(result.stdout).toContain('0x666666666666666666666666666666666666666666666666666666666666662a'); 18 | }); 19 | 20 | test('Should complain if an invalid hex number is provided', async () => { 21 | expect((await cli('hex2uint', 'spongy')).code).toBe(1); 22 | expect((await cli('hex2uint', '0.1')).code).toBe(1); 23 | expect((await cli('hex2uint', '0xqqq')).code).toBe(1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/commands/pastevents.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | const fs = require('fs'); 4 | 5 | describe('pastevents command', () => { 6 | 7 | let address; 8 | 9 | test('Should retrieve expected events for a contract deployed in localhost', async () => { 10 | 11 | // Set up web3. 12 | const web3 = await getWeb3('localhost'); 13 | 14 | // Retrieve contract artifacts. 15 | const artifacts = JSON.parse(fs.readFileSync('test/artifacts/EventEmitter.json', 'utf8')); 16 | 17 | // Set up params for upcoming txs. 18 | const accounts = await web3.eth.getAccounts(); 19 | const params = { 20 | from: accounts[0], 21 | gas: 1000000, 22 | gasPrice: 1 23 | }; 24 | 25 | // Deploy contract. 26 | const contract = new web3.eth.Contract(artifacts.abi); 27 | const instance = await contract.deploy({ 28 | data: artifacts.bytecode 29 | }).send(params); 30 | address = instance.options.address; 31 | // console.log('Deployed contract address:', address); 32 | 33 | // Trigger the contract's emitEventWithValue to create some events. 34 | const blockBefore = await web3.eth.getBlockNumber(); 35 | const txReceipt1 = await instance.methods.emitEventWithValue(42).send(params); 36 | const txReceipt2 = await instance.methods.emitEventWithValue(1982).send(params); 37 | const blockAfter = await web3.eth.getBlockNumber(); 38 | 39 | // Trigger the command to search for events. 40 | const result = await cli( 41 | 'pastevents', 42 | 'localhost', 43 | 'test/artifacts/EventEmitter.json', 44 | address, 45 | 'Log', 46 | blockBefore, 47 | blockAfter 48 | ); 49 | 50 | // Verifications. 51 | expect(result.stdout).toContain(`Total "Log" events found: 2`); 52 | expect(result.stdout).toContain(txReceipt1.transactionHash); 53 | expect(result.stdout).toContain(txReceipt2.transactionHash); 54 | expect(result.stdout).toContain('42'); 55 | expect(result.stdout).toContain('1982'); 56 | }); 57 | 58 | test('Should complain when an invalid contract address is provided', async () => { 59 | const result = await cli('pastevents', 'localhost', 'test/artifacts/KittyAuction.json', '0x123', 'f', 'f', 'f'); 60 | expect(result.code).toBe(1); 61 | expect(result.stderr).toContain('Invalid contract address'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/commands/selector.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('selector command', () => { 4 | 5 | test('Should produce an expected selector for a simple signature', async () => { 6 | const result = await cli('selector', `'value()'`); 7 | expect(result.stdout).toContain(`0x3fa4f245`); 8 | }); 9 | 10 | test('Should produce an expected selector for a more elaborate signature', async () => { 11 | const result = await cli('selector', `'createSiringAuction(uint256,uint256,uint256,uint256)'`); 12 | expect(result.stdout).toContain(`0x4ad8c938`); 13 | }); 14 | 15 | test('Should complain if the signature contains "returns"', async () => { 16 | const result = await cli('selector', `'value() returns(bool)'`); 17 | expect(result.code).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/commands/selectors.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('selectors command', () => { 4 | 5 | test('Should produce expected selectors from known contract artifacts', async () => { 6 | const result = await cli('selectors', 'test/artifacts/Test.json'); 7 | expect(result.stdout).toContain(`0x3fa4f245: value()`); 8 | expect(result.stdout).toContain(`0x5dce0fa6: granparent_value()`); 9 | expect(result.stdout).toContain(`0xd8175b14: parent1_value()`); 10 | }); 11 | 12 | test('Should produce expected selectors from a known solidity file', async () => { 13 | const result = await cli('selectors', 'test/contracts/Test.sol'); 14 | expect(result.stdout).toContain(`0x3fa4f245: value()`); 15 | expect(result.stdout).toContain(`0x5dce0fa6: granparent_value()`); 16 | expect(result.stdout).toContain(`0xd8175b14: parent1_value()`); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/commands/split.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const tmp = require('tmp'); 3 | const fs = require('fs'); 4 | 5 | describe('split command', () => { 6 | 7 | test('Should succesfully split the cryptokitties contract, and it should compile', async () => { 8 | 9 | // Set up a temp directory to hold the output. 10 | const tmpdir = tmp.dirSync(); 11 | console.log(`Test directory: ${tmpdir.name}`); 12 | 13 | // Split a test contract. 14 | let result = await cli('split', 'test/contracts/SplitMe.sol', tmpdir.name); 15 | expect(result.code).toBe(0); 16 | expect(result.stdout).toContain(`New files written to ${tmpdir.name}`); 17 | 18 | // Number of files should match. 19 | expect(result.stdout).toContain(`into 4 files`); 20 | 21 | // All expected files should exist. 22 | expect(fs.existsSync(`${tmpdir.name}/SplitTheLibrary.sol`)); 23 | expect(fs.existsSync(`${tmpdir.name}/SplitMe1.sol`)); 24 | expect(fs.existsSync(`${tmpdir.name}/SplitMe2.sol`)); 25 | expect(fs.existsSync(`${tmpdir.name}/SplitMe3.sol`)); 26 | 27 | // Files should have the expected contract definitions. 28 | const content0 = fs.readFileSync(`${tmpdir.name}/SplitTheLibrary.sol`, 'utf8'); 29 | const content1 = fs.readFileSync(`${tmpdir.name}/SplitMe1.sol`, 'utf8'); 30 | const content2 = fs.readFileSync(`${tmpdir.name}/SplitMe2.sol`, 'utf8'); 31 | const content3 = fs.readFileSync(`${tmpdir.name}/SplitMe3.sol`, 'utf8'); 32 | expect(content0).toContain('library SplitTheLibrary'); 33 | expect(content1).toContain('contract SplitMe1'); 34 | expect(content2).toContain('contract SplitMe2'); 35 | expect(content3).toContain('contract SplitMe3'); 36 | 37 | // Files should have the expected imports. 38 | expect(content0.includes('import')).toBe(false); 39 | expect(content1.includes('import')).toBe(false); 40 | expect(content2.includes('import "./SplitMe1.sol";')).toBe(true); 41 | expect(content2.includes('import "./SplitMe2.sol";')).toBe(false); 42 | expect(content2.includes('import "./SplitMe3.sol";')).toBe(false); 43 | expect(content3.includes('import "./SplitMe1.sol";')).toBe(true); 44 | expect(content3.includes('import "./SplitMe2.sol";')).toBe(true); 45 | expect(content3.includes('import "./SplitMe3.sol";')).toBe(false); 46 | 47 | // Additional expected content. 48 | expect(content1.includes('This comment should be part of SplitMe1.')).toBe(true); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/commands/storage.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | const fs = require('fs'); 4 | 5 | describe('storage command', () => { 6 | 7 | test('Should complain when an invalid contract address is provided', async () => { 8 | const result = await cli('storage', 'localhost', '0x123'); 9 | expect(result.code).toBe(1); 10 | }); 11 | 12 | describe('When reading storage', () => { 13 | 14 | let address; 15 | 16 | beforeAll(async () => { 17 | 18 | // Set up web3. 19 | const web3 = await getWeb3('localhost'); 20 | 21 | // Retrieve contract artifacts. 22 | const artifacts = JSON.parse(fs.readFileSync('test/artifacts/Storage.json', 'utf8')); 23 | 24 | // Set up params for upcoming txs. 25 | const accounts = await web3.eth.getAccounts(); 26 | const params = { 27 | from: accounts[0], 28 | gas: 1000000, 29 | gasPrice: 1 30 | }; 31 | 32 | // Deploy contract. 33 | const contract = new web3.eth.Contract(artifacts.abi); 34 | const instance = await contract.deploy({ 35 | data: artifacts.bytecode 36 | }).send(params); 37 | address = instance.options.address; 38 | // console.log('Deployed contract address:', address); 39 | 40 | // Trigger the contract's testFunction to set storage of some variables. 41 | const tx = await instance.methods.testStorage().send(params); 42 | expect(tx.transactionHash.length).toBe(66); 43 | expect(tx.gasUsed).toBeGreaterThan(400000); 44 | 45 | // Verify a public storage value set by testStorage(); 46 | const secret = await instance.methods.secret().call(); 47 | expect(secret).toBe('42'); 48 | }); 49 | 50 | test('Can read uints that occupy an entire slot', async () => { 51 | const result = await cli('storage', 'localhost', address, '0'); 52 | expect(result.stdout).toContain(`0x000000000000000000000000000000000000000000000000000000000000000f`); 53 | }); 54 | 55 | test('Can read uints that partially occupy a slot', async () => { 56 | const result = await cli('storage', 'localhost', address, '1', '--range', '16,32'); 57 | expect(result.stdout).toContain(`0x0000000000000000000000000000429f`); 58 | }); 59 | 60 | test('Can read strings', async () => { 61 | let result; 62 | result = await cli('storage', 'localhost', address, '2'); 63 | expect(result.stdout).toContain(`test1`); 64 | result = await cli('storage', 'localhost', address, '3'); 65 | expect(result.stdout).toContain(`test1236`); 66 | result = await cli('storage', 'localhost', address, '4'); 67 | expect(result.stdout).toContain(`lets string something`); 68 | }); 69 | 70 | test('Can read mappings that store uints', async () => { 71 | let result; 72 | result = await cli('storage', 'localhost', address, '5'); 73 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000000`); 74 | result = await cli('storage', 'localhost', address, '5', '--key', '0xbCcc714d56bc0da0fd33d96d2a87b680dD6D0DF6'); 75 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000058`); 76 | result = await cli('storage', 'localhost', address, '5', '--key', '0xaee905FdD3ED851e48d22059575b9F4245A82B04'); 77 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000063`); 78 | }); 79 | 80 | test('Can read mappings that store structs', async () => { 81 | let result; 82 | result = await cli('storage', 'localhost', address, '6'); 83 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000000`); 84 | result = await cli('storage', 'localhost', address, '6', '--key', '0xaee905FdD3ED851e48d22059575b9F4245A82B04', '--offset', '0'); 85 | expect(result.stdout).toContain(`deviceBrand2`); 86 | result = await cli('storage', 'localhost', address, '6', '--key', '0xaee905FdD3ED851e48d22059575b9F4245A82B04', '--offset', '1'); 87 | expect(result.stdout).toContain(`deviceYear2`); 88 | result = await cli('storage', 'localhost', address, '6', '--key', '0xaee905FdD3ED851e48d22059575b9F4245A82B04', '--offset', '2'); 89 | expect(result.stdout).toContain(`wearLevel2`); 90 | }); 91 | 92 | test('Can read arrays that store uints', async () => { 93 | let result = await cli('storage', 'localhost', address, '7'); 94 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000002`); 95 | result = await cli('storage', 'localhost', address, '7', '--offset', '0'); 96 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000001f40`); 97 | result = await cli('storage', 'localhost', address, '7', '--offset', '1'); 98 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000002328`); 99 | }); 100 | 101 | test('Can read arrays that store structs', async () => { 102 | let result; 103 | result = await cli('storage', 'localhost', address, '8'); 104 | expect(result.stdout).toContain(`0x0000000000000000000000000000000000000000000000000000000000000002`); 105 | result = await cli('storage', 'localhost', address, '8', '--offset', '3'); 106 | expect(result.stdout).toContain(`deviceBrand2`); 107 | result = await cli('storage', 'localhost', address, '8', '--offset', '4'); 108 | expect(result.stdout).toContain(`deviceYear2`); 109 | result = await cli('storage', 'localhost', address, '8', '--offset', '5'); 110 | expect(result.stdout).toContain(`wearLevel2`); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/commands/str2hex.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('str2hex command', () => { 4 | 5 | test('Should properly translate "Hello" to hex', async () => { 6 | const result = await cli('str2hex', 'Hello'); 7 | expect(result.stdout).toContain('0x48656c6c6f'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/commands/transaction.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | 4 | describe('transaction command', () => { 5 | 6 | test('Should complain when an invalid tx hash is provided', async () => { 7 | const result = await cli( 8 | 'transaction', 9 | 'localhost', 10 | '0x95ecb5317de43d2c682e93' 11 | ); 12 | expect(result.code).toBe(1); 13 | }); 14 | 15 | test('Should retrieve a known transaction from localhost', async () => { 16 | 17 | // Set up web3. 18 | const web3 = await getWeb3('localhost'); 19 | 20 | // Send a dummy transaction so that there is at least 1 block. 21 | const accounts = await web3.eth.getAccounts(); 22 | const txReceipt = await web3.eth.sendTransaction({ 23 | from: accounts[0], 24 | to: accounts[1], 25 | value: 1, 26 | gas: 100000, 27 | gasPrice: 1 28 | }); 29 | 30 | // Trigger command by block number. 31 | const result = await cli( 32 | 'transaction', 33 | 'localhost', 34 | txReceipt.transactionHash 35 | ); 36 | const tx = JSON.parse(result.stdout); 37 | 38 | // Verify results. 39 | expect(txReceipt.transactionHash).toBe(tx.hash); 40 | expect(txReceipt.blockHash).toBe(tx.blockHash); 41 | expect(txReceipt.from).toBe(tx.from.toLowerCase()); 42 | expect(txReceipt.to).toBe(tx.to.toLowerCase()); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/commands/txs.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const getWeb3 = require('../../src/utils/getWeb3.js'); 3 | const fs = require('fs'); 4 | 5 | describe('txs command', () => { 6 | 7 | let address; 8 | 9 | test('Should find a known number of transactions for a contract deployed in localhost', async () => { 10 | 11 | // Set up web3. 12 | const web3 = await getWeb3('localhost'); 13 | 14 | // Retrieve contract artifacts. 15 | const artifacts = JSON.parse(fs.readFileSync('test/artifacts/EventEmitter.json', 'utf8')); 16 | 17 | // Set up params for upcoming txs. 18 | const accounts = await web3.eth.getAccounts(); 19 | const params = { 20 | from: accounts[0], 21 | gas: 1000000, 22 | gasPrice: 1 23 | }; 24 | 25 | // Deploy contract. 26 | const contract = new web3.eth.Contract(artifacts.abi); 27 | const instance = await contract.deploy({ 28 | data: artifacts.bytecode 29 | }).send(params); 30 | address = instance.options.address; 31 | // console.log('Deployed contract address:', address); 32 | 33 | // Trigger the contract's emitEventWithValue to create some events. 34 | const blockBefore = await web3.eth.getBlockNumber(); 35 | const txReceipt1 = await instance.methods.emitEventWithValue(42).send(params); 36 | const txReceipt2 = await instance.methods.emitEventWithValue(1982).send(params); 37 | await instance.methods.anotherMethod().send(params); 38 | await instance.methods.anotherMethod().send(params); 39 | await instance.methods.anotherMethod().send(params); 40 | const blockAfter = await web3.eth.getBlockNumber(); 41 | 42 | // Trigger the command to search for events. 43 | const result = await cli( 44 | 'txs', 45 | 'localhost', 46 | address, 47 | '0xc8d8936c', 48 | blockBefore, 49 | blockAfter 50 | ); 51 | 52 | // Verifications. 53 | expect(result.stdout).toContain(`Found 2 transactions`); 54 | expect(result.stdout).toContain(txReceipt1.transactionHash); 55 | expect(result.stdout).toContain(txReceipt2.transactionHash); 56 | }); 57 | 58 | test('Should find a known number of transactions from a known contract deployed in mainnet', async () => { 59 | const result = await cli( 60 | 'txs', 61 | 'mainnet', 62 | '0x06012c8cf97bead5deae237070f9587f8e7a266d', 63 | '0xa9059cbb', 64 | '7729780', 65 | '7729790', 66 | '10' 67 | ); 68 | expect(result.stdout).toContain(`Found 27 transactions`); 69 | }); 70 | 71 | test('Should complain when an invalid contract address is provided', async () => { 72 | const result = await cli('txs', 'localhost', '0x123', 'f', 'f', 'f'); 73 | expect(result.code).toBe(1); 74 | expect(result.stderr).toContain('Invalid contractAddress'); 75 | }); 76 | 77 | test('Should complain when an invalid function selector is provided', async () => { 78 | const result = await cli('txs', 'localhost', '0x06012c8cf97bead5deae237070f9587f8e7a266d', '0xcqf', 'f', 'f'); 79 | expect(result.code).toBe(1); 80 | expect(result.stderr).toContain('Invalid functionSelector'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/commands/uint2hex.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | 3 | describe('uint2hex command', () => { 4 | 5 | test('Should properly convert a small positive integer to hex', async () => { 6 | const result = await cli('uint2hex', '42'); 7 | expect(result.stdout).toContain('0x2a'); 8 | }); 9 | 10 | test('Should complain if an invalid integer is provided', async () => { 11 | expect((await cli('int2hex', 'xx')).code).toBe(1); 12 | expect((await cli('int2hex', '0.1')).code).toBe(1); 13 | expect((await cli('int2hex', '\-1')).code).toBe(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/contracts/EventEmitter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract EventEmitter { 4 | 5 | uint256 value; 6 | 7 | event Log(uint256 value); 8 | 9 | function emitEventWithValue(uint256 _value) public { 10 | value = _value; 11 | emit Log(_value); 12 | } 13 | 14 | function anotherMethod() public { 15 | value = 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/contracts/NodeModules.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "some-dependency/contracts/ownership/Ownable.sol"; 4 | 5 | contract NodeModules is Ownable {} 6 | -------------------------------------------------------------------------------- /test/contracts/Ranges.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.0 <0.4.23; 2 | 3 | contract Ranges { 4 | function test() public pure returns(uint) { return 42; } 5 | } 6 | -------------------------------------------------------------------------------- /test/contracts/Sample.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./SampleDependency.sol"; 4 | 5 | contract Sample is SampleDependency { 6 | function testSample() public pure returns (string memory) { 7 | return "Sample"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/contracts/SampleAbstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract SampleAbstract { 4 | 5 | event AnEvent(address addr); 6 | 7 | function test() public pure returns(string memory); 8 | } 9 | -------------------------------------------------------------------------------- /test/contracts/SampleDependency.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./SampleAbstract.sol"; 4 | 5 | contract SampleDependency is SampleAbstract { 6 | 7 | function testSampleDependency() public pure returns (string memory) { 8 | return "SampleDependency"; 9 | } 10 | 11 | function test() public pure returns(string memory) { 12 | return "Implemented"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/contracts/SearchPaths.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Intentionally not specifying "./searchpath/Dependency.sol" 4 | import "Dependency.sol"; 5 | 6 | contract ContractName is Dependency {} 7 | -------------------------------------------------------------------------------- /test/contracts/SplitMe.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | library SplitTheLibrary {} 4 | 5 | // This comment should be part of SplitMe1. 6 | contract SplitMe1 {/* This should not cause SplitMe1 to contract SplitMe3 */} 7 | 8 | // This comment about SplitMe3, should not cause an import of SplitMe3 in SplitMe2. 9 | /* 10 | * This comment about SplitMe3, should not cause an import of SplitMe3 in SplitMe2. 11 | This comment about SplitMe3, should not cause an import of SplitMe3 in SplitMe2. 12 | */ 13 | contract SplitMe2 is SplitMe1 { 14 | function test() public pure { 15 | {{{{{{{{{{{}}}}}}}}}}} 16 | } 17 | function anotherTest() { 18 | { 19 | { 20 | { 21 | { 22 | { 23 | { 24 | { 25 | { 26 | { 27 | { 28 | {} 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | contract SplitMe3 is SplitMe2 { 43 | using SplitTheLibrary for uint256; 44 | SplitMe1 splitMe1; // Should cause an import. 45 | } 46 | -------------------------------------------------------------------------------- /test/contracts/Storage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Based on: https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925 4 | 5 | contract Storage { 6 | 7 | uint storeduint1 = 15; 8 | uint constant CONSTUINT = 16; 9 | uint128 investmentsLimit = 17055; 10 | uint32 investmentsDeadlineTimeStamp = uint32(now); 11 | 12 | bytes16 string1 = "test1"; 13 | bytes32 string2 = "test1236"; 14 | string string3 = "lets string something"; 15 | 16 | mapping(address => uint) uints1; 17 | mapping(address => DeviceData) structs1; 18 | 19 | uint[] uintarray; 20 | DeviceData[] deviceDataArray; 21 | 22 | uint public secret; 23 | 24 | struct DeviceData { 25 | string deviceBrand; 26 | string deviceYear; 27 | string batteryWearLevel; 28 | } 29 | 30 | function testStorage() public { 31 | 32 | secret = 42; 33 | 34 | address address1 = 0xbCcc714d56bc0da0fd33d96d2a87b680dD6D0DF6; 35 | address address2 = 0xaee905FdD3ED851e48d22059575b9F4245A82B04; 36 | 37 | uints1[address1] = 88; 38 | uints1[address2] = 99; 39 | 40 | DeviceData memory dev1 = DeviceData("deviceBrand1", "deviceYear1", "wearLevel1"); 41 | DeviceData memory dev2 = DeviceData("deviceBrand2", "deviceYear2", "wearLevel2"); 42 | 43 | structs1[address1] = dev1; 44 | structs1[address2] = dev2; 45 | 46 | uintarray.push(8000); 47 | uintarray.push(9000); 48 | 49 | deviceDataArray.push(dev1); 50 | deviceDataArray.push(dev2); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/contracts/Test.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | library Lib { 4 | function ret(uint256 value) internal pure returns(uint256) { 5 | return value; 6 | } 7 | } 8 | 9 | contract GrandParent { 10 | uint256 public granparent_value; 11 | } 12 | 13 | contract Parent1 is GrandParent { 14 | uint256 public parent1_value; 15 | } 16 | 17 | contract Parent2 { 18 | uint256 public parent2_value; 19 | } 20 | 21 | contract Test is Parent1, Parent2 { 22 | using Lib for uint256; 23 | 24 | uint256 public value; 25 | uint256 public constant CONS = 42; 26 | 27 | modifier aModifier { 28 | _; 29 | } 30 | 31 | modifier anotherModifier(uint256) { 32 | _; 33 | } 34 | 35 | constructor() public { 36 | value = 5; 37 | } 38 | 39 | function test(uint256 newValue) public aModifier anotherModifier(42) { 40 | value = newValue; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/contracts/searchpath/Dependency.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract Dependency {} 4 | -------------------------------------------------------------------------------- /test/contracts/subdir/subsubdir/NodeModules.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "some-dependency/contracts/ownership/Ownable.sol"; 4 | 5 | contract NodeModules is Ownable {} 6 | -------------------------------------------------------------------------------- /test/flows/ant.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const tmp = require('tmp'); 3 | const fs = require('fs'); 4 | 5 | const ANT_MAINNET = ' 0x960b236A07cf122663c4303350609A66A7B288C0'; 6 | const CONTRACT_NAME = 'ANT'; 7 | const NUM_FILES = 12; 8 | 9 | describe('ANT flow', () => { 10 | test('Retrieves code from Etherscan, splits it, compiles it, and runs a series of commands on the code', async () => { 11 | 12 | // Set up a temp directory to hold the output. 13 | const tmpdir = tmp.dirSync(); 14 | const filepath = `${tmpdir.name}/${CONTRACT_NAME}.sol`; 15 | console.log(`Test directory: ${tmpdir.name}`); 16 | 17 | // Retrieve the code. 18 | let result = await cli( 19 | 'getcode', 20 | ANT_MAINNET, 21 | filepath 22 | ); 23 | expect(result.stdout).toContain(`Source code written to ${filepath}`); 24 | 25 | // Split the contract. 26 | result = await cli('split', filepath, tmpdir.name); 27 | expect(result.code).toBe(0); 28 | expect(result.stdout).toContain(`New files written to ${tmpdir.name}`); 29 | 30 | // Number of files should match. 31 | expect(result.stdout).toContain(`into ${NUM_FILES} files`); 32 | 33 | // Expected files should exist. 34 | expect(fs.existsSync(`${tmpdir.name}/AbstractSale.sol`)); 35 | expect(fs.existsSync(`${tmpdir.name}/MiniMeToken.sol`)); 36 | expect(fs.existsSync(`${tmpdir.name}/ANT.sol`)); 37 | 38 | // Files should have expected content. 39 | expect(fs.readFileSync(`${tmpdir.name}/SaleWallet.sol`, 'utf8')).toContain('address public multisig'); 40 | 41 | // Files should compile. 42 | result = await cli('compile', `${tmpdir.name}/${CONTRACT_NAME}.sol`, `${tmpdir.name}/`); 43 | expect(result.code).toBe(0); 44 | 45 | // Compilation output should exist and have content. 46 | expect(fs.existsSync(`${tmpdir.name}/${CONTRACT_NAME}.json`)); 47 | expect(fs.readFileSync(`${tmpdir.name}/${CONTRACT_NAME}.json`, 'utf8').length).toBeGreaterThan(0); 48 | 49 | // Should have the right inheritance tree. 50 | result = await cli('inheritance', `${tmpdir.name}/${CONTRACT_NAME}.json`); 51 | expect(result.stdout).toContain(`└─ ANT 52 | └─ MiniMeIrrevocableVestedToken 53 | ├─ MiniMeToken 54 | │ ├─ ERC20 55 | │ └─ Controlled 56 | └─ SafeMath`); 57 | 58 | // Should have the right members. 59 | result = await cli('members', `${tmpdir.name}/${CONTRACT_NAME}.json`, '--inherited', '--disable-colors'); 60 | let expectedStdout = fs.readFileSync('test/output/ant.members.output', 'utf8'); 61 | expect(result.stdout).toBe(expectedStdout); 62 | 63 | // Should read the right storage. 64 | result = await cli('liststorage', 'mainnet', `${tmpdir.name}/${CONTRACT_NAME}.json`, ANT_MAINNET, '--disable-colors'); 65 | expect(result.stdout).toContain(`0xd39902f046b5885d70e9e66594b65f84d4d1c952`); 66 | expect(result.stdout).toContain(`Aragon Network Token`); 67 | expect(result.stdout).toContain(`18`); 68 | expect(result.stdout).toContain(`ANT`); 69 | expect(result.stdout).toContain(`MMT_0.1`); 70 | expect(result.stdout).toContain(`3711733`); 71 | expect(result.stdout).toContain(`0x175b5d76b0eaa2c66bee02c393f96d6cc05e7ff9`); 72 | expect(result.stdout).toContain(`0xcafe1a77e84698c83ca8931f54a755176ef75f2c`); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/flows/cryptokitties.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const tmp = require('tmp'); 3 | const fs = require('fs'); 4 | 5 | const CRYPTOKITTIES_MAINNET = '0x06012c8cf97bead5deae237070f9587f8e7a266d'; 6 | const CONTRACT_NAME = 'KittyCore'; 7 | const NUM_FILES = 16; 8 | 9 | describe('Cryptokitties flow', () => { 10 | test('Retrieves Cryptokitties\' code from Etherscan, splits it, compiles it, and runs a series of commands on the code', async () => { 11 | 12 | // Set up a temp directory to hold the output. 13 | const tmpdir = tmp.dirSync(); 14 | const filepath = `${tmpdir.name}/${CONTRACT_NAME}.sol`; 15 | console.log(`Test directory: ${tmpdir.name}`); 16 | 17 | // Retrieve the code. 18 | let result = await cli( 19 | 'getcode', 20 | CRYPTOKITTIES_MAINNET, 21 | filepath 22 | ); 23 | expect(result.stdout).toContain(`Source code written to ${filepath}`); 24 | 25 | // Split the contract. 26 | result = await cli('split', filepath, tmpdir.name); 27 | expect(result.code).toBe(0); 28 | expect(result.stdout).toContain(`New files written to ${tmpdir.name}`); 29 | 30 | // Number of files should match. 31 | expect(result.stdout).toContain(`into ${NUM_FILES} files`); 32 | 33 | // Expected files should exist. 34 | expect(fs.existsSync(`${tmpdir.name}/KittyAccessControl.sol`)); 35 | expect(fs.existsSync(`${tmpdir.name}/KittyMinting.sol`)); 36 | expect(fs.existsSync(`${tmpdir.name}/KittyBreeding.sol`)); 37 | 38 | // Files should have expected content. 39 | expect(fs.readFileSync(`${tmpdir.name}/KittyBreeding.sol`, 'utf8')).toContain('contract KittyBreeding is KittyOwnership'); 40 | 41 | // Files should compile. 42 | result = await cli('compile', `${tmpdir.name}/${CONTRACT_NAME}.sol`, `${tmpdir.name}/`); 43 | expect(result.code).toBe(0); 44 | 45 | // Compilation output should exist and have content. 46 | expect(fs.existsSync(`${tmpdir.name}/${CONTRACT_NAME}.json`)); 47 | expect(fs.readFileSync(`${tmpdir.name}/${CONTRACT_NAME}.json`, 'utf8').length).toBeGreaterThan(0); 48 | 49 | // Should have the right inheritance tree. 50 | result = await cli('inheritance', `${tmpdir.name}/${CONTRACT_NAME}.json`); 51 | expect(result.stdout).toContain(`└─ KittyCore 52 | └─ KittyMinting 53 | └─ KittyAuction 54 | └─ KittyBreeding 55 | └─ KittyOwnership 56 | ├─ KittyBase 57 | │ └─ KittyAccessControl 58 | └─ ERC721`); 59 | 60 | // Should have the right members. 61 | result = await cli('members', `${tmpdir.name}/${CONTRACT_NAME}.json`, '--inherited', '--disable-colors'); 62 | let expectedStdout = fs.readFileSync('test/output/cryptokitties.members.output', 'utf8'); 63 | expect(result.stdout).toBe(expectedStdout); 64 | 65 | // Should read the right storage. 66 | result = await cli('liststorage', 'mainnet', `${tmpdir.name}/${CONTRACT_NAME}.json`, CRYPTOKITTIES_MAINNET, '--disable-colors'); 67 | expect(result.stdout).toContain(`0xaf1e54b359b0897133f437fc961dd16f20c045e1`); 68 | expect(result.stdout).toContain(`0xa874aa3e03842a84e4b252315488d27837d89544`); 69 | expect(result.stdout).toContain(`0x09191d18729da57a83a9afc8ace0c8d7d104e118`); 70 | expect(result.stdout).toContain(`0xb1690c08e213a35ed9bab7b318de14420fb57d8c`); 71 | expect(result.stdout).toContain(`0xc7af99fe5513eb6710e6d5f44f9989da40f27f26`); 72 | expect(result.stdout).toContain(`3087`); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/flows/rep.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const tmp = require('tmp'); 3 | const fs = require('fs'); 4 | 5 | const REP_MAINNET = '0xe94327d07fc17907b4db788e5adf2ed424addff6'; 6 | const CONTRACT_NAME = 'RepToken'; 7 | const NUM_FILES = 10; 8 | 9 | describe('REP flow', () => { 10 | test('Retrieves code from Etherscan, splits it, compiles it, and runs a series of commands on the code', async () => { 11 | 12 | // Set up a temp directory to hold the output. 13 | const tmpdir = tmp.dirSync(); 14 | const filepath = `${tmpdir.name}/${CONTRACT_NAME}.sol`; 15 | console.log(`Test directory: ${tmpdir.name}`); 16 | 17 | // Retrieve the code. 18 | let result = await cli( 19 | 'getcode', 20 | REP_MAINNET, 21 | filepath 22 | ); 23 | expect(result.stdout).toContain(`Source code written to ${filepath}`); 24 | 25 | // Split the contract. 26 | result = await cli('split', filepath, tmpdir.name); 27 | expect(result.code).toBe(0); 28 | expect(result.stdout).toContain(`New files written to ${tmpdir.name}`); 29 | 30 | // Number of files should match. 31 | expect(result.stdout).toContain(`into ${NUM_FILES} files`); 32 | 33 | // Expected files should exist. 34 | expect(fs.existsSync(`${tmpdir.name}/ERC20.sol`)); 35 | expect(fs.existsSync(`${tmpdir.name}/RepToken.sol`)); 36 | expect(fs.existsSync(`${tmpdir.name}/SafeMath.sol`)); 37 | 38 | // Files should have expected content. 39 | expect(fs.readFileSync(`${tmpdir.name}/RepToken.sol`, 'utf8')).toContain('contract RepToken is Initializable, PausableToken {'); 40 | 41 | // Files should compile. 42 | result = await cli('compile', `${tmpdir.name}/${CONTRACT_NAME}.sol`, `${tmpdir.name}/`); 43 | expect(result.code).toBe(0); 44 | 45 | // Compilation output should exist and have content. 46 | expect(fs.existsSync(`${tmpdir.name}/${CONTRACT_NAME}.json`)); 47 | expect(fs.readFileSync(`${tmpdir.name}/${CONTRACT_NAME}.json`, 'utf8').length).toBeGreaterThan(0); 48 | 49 | // Should have the right inheritance tree. 50 | result = await cli('inheritance', `${tmpdir.name}/${CONTRACT_NAME}.json`); 51 | expect(result.stdout).toContain(`─ RepToken 52 | ├─ Initializable 53 | └─ PausableToken 54 | ├─ StandardToken 55 | │ ├─ ERC20 56 | │ │ └─ ERC20Basic 57 | │ └─ BasicToken 58 | │ └─ ERC20Basic 59 | └─ Pausable 60 | └─ Ownable`); 61 | 62 | // Should have the right members. 63 | result = await cli('members', `${tmpdir.name}/${CONTRACT_NAME}.json`, '--inherited', '--disable-colors'); 64 | let expectedStdout = fs.readFileSync('test/output/rep.members.output', 'utf8'); 65 | expect(result.stdout).toBe(expectedStdout); 66 | 67 | // Should read the right storage. 68 | result = await cli('liststorage', 'mainnet', `${tmpdir.name}/${CONTRACT_NAME}.json`, REP_MAINNET, '--disable-colors'); 69 | expect(result.stdout).toContain(`11000000000000000000000000`); 70 | expect(result.stdout).toContain(`0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5`); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/misc/getArtifacts.test.js: -------------------------------------------------------------------------------- 1 | const cli = require('../../src/utils/cli.js'); 2 | const Web3 = require('web3'); 3 | const fs = require('fs'); 4 | 5 | describe('compile command', () => { 6 | 7 | test('Should cache autocompilation output', async () => { 8 | 9 | // Used for utils only. 10 | const web3 = new Web3(); 11 | 12 | // Make sure cacheed files are deleted. 13 | // TODO: Consider deleting it instead of removing it. 14 | const source = fs.readFileSync('test/contracts/Test.sol', 'utf8'); 15 | const dir = `/tmp/${web3.utils.sha3(source).substring(2, 14)}`; 16 | console.log(`Temporary directory: `, dir); 17 | fs.renameSync(dir, `${dir}_bkp`); 18 | 19 | // Call inheritance on a .sol file. 20 | let result = await cli('inheritance', 'test/contracts/Test.sol'); 21 | expect(result.code).toBe(0); 22 | expect(result.stdout).toContain('Auto compiling'); 23 | 24 | // Call inheritance again on the same .sol file. 25 | result = await cli('inheritance', 'test/contracts/Test.sol'); 26 | expect(result.code).toBe(0); 27 | expect(result.stdout.includes('Auto compiling')).toBe(false); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/misc/help.test.js: -------------------------------------------------------------------------------- 1 | const globals = require('../../src/globals.js'); 2 | const cli = require('../../src/utils/cli.js'); 3 | const path = require('path'); 4 | 5 | describe('help', () => { 6 | 7 | test('Main program should output help, and the help should be custom', async () => { 8 | const result = await cli('--help'); 9 | expect(result.code).toBe(0); 10 | expect(result.stdout.length).toBeGreaterThan(0); 11 | expect(result.stdout).toContain('_/\\\\\\_'); // Part of the figlet output. 12 | }); 13 | 14 | test('Main program should output help when no command is specified', async () => { 15 | const result = await cli(); 16 | expect(result.code).toBe(0); 17 | expect(result.stdout.length).toBeGreaterThan(0); 18 | }); 19 | 20 | test('All commands should output help', async () => { 21 | 22 | // Get all command files. 23 | const commands = globals.commandPaths; 24 | 25 | // Trigger help on all commands. 26 | const promises = commands.map((command) => { 27 | const commandName = path.basename(command).split('.')[0]; 28 | return cli(commandName, '--help'); 29 | }); 30 | 31 | // Verify all promises. 32 | const results = await Promise.all(promises); 33 | results.map(result => { 34 | expect(result.code).toBe(0); 35 | expect(result.stdout.length).toBeGreaterThan(0); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/misc/program.test.js: -------------------------------------------------------------------------------- 1 | const globals = require('../../src/globals.js'); 2 | const cli = require('../../src/utils/cli.js'); 3 | const path = require('path'); 4 | 5 | describe('program', () => { 6 | 7 | test('Program should throw an error when an unknown command is received', async () => { 8 | const invalidCommnad = `spongi`; 9 | const result = await cli(invalidCommnad); 10 | expect(result.code).toBe(1); 11 | expect(result.stderr).toContain(`Invalid command: ${invalidCommnad}`); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/output/ant.members.output: -------------------------------------------------------------------------------- 1 | 2 | ¬ ANT 3 | function ANT(address _tokenFactory) public MiniMeIrrevocableVestedToken(, 0x0, 0, Aragon Network Token, 18, ANT, true) {...} 4 | 5 | ¬ MiniMeIrrevocableVestedToken 6 | struct public TokenGrant { 7 | address granter; 8 | uint256 value; 9 | uint64 cliff; 10 | uint64 vesting; 11 | uint64 start; 12 | } 13 | event NewTokenGrant(address from, address to, uint256 value, uint64 start, uint64 cliff, uint64 vesting); 14 | mapping(address => struct MiniMeIrrevocableVestedToken.TokenGrant[]) public grants; 15 | mapping(address => bool) canCreateGrants; 16 | address vestingWhitelister; 17 | modifier canTransfer(address _sender, uint _value) {...} 18 | modifier onlyVestingWhitelister() {...} 19 | function MiniMeIrrevocableVestedToken(address _tokenFactory, address _parentToken, uint _parentSnapShotBlock, string _tokenName, uint8 _decimalUnits, string _tokenSymbol, bool _transfersEnabled) public MiniMeToken(, , , , , , ) {...} 20 | function transfer(address _to, uint _value) public canTransfer(, ) returns(bool success) {...} 21 | function transferFrom(address _from, address _to, uint _value) public canTransfer(, ) returns(bool success) {...} 22 | function spendableBalanceOf(address _holder) public view returns(uint) {...} 23 | function grantVestedTokens(address _to, uint256 _value, uint64 _start, uint64 _cliff, uint64 _vesting) public {...} 24 | function setCanCreateGrants(address _addr, bool _allowed) public onlyVestingWhitelister {...} 25 | function doSetCanCreateGrants(address _addr, bool _allowed) internal {...} 26 | function changeVestingWhitelister(address _newWhitelister) public onlyVestingWhitelister {...} 27 | function revokeTokenGrant(address _holder, uint _grantId) public {...} 28 | function tokenGrantsCount(address _holder) public view returns(uint index) {...} 29 | function tokenGrant(address _holder, uint _grantId) public view returns(address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting) {...} 30 | function vestedTokens(TokenGrant grant, uint64 time) internal view returns(uint256) {...} 31 | function calculateVestedTokens(uint256 tokens, uint256 time, uint256 start, uint256 cliff, uint256 vesting) internal view returns(uint256) {...} 32 | function nonVestedTokens(TokenGrant grant, uint64 time) internal view returns(uint256) {...} 33 | function lastTokenIsTransferableDate(address holder) public view returns(uint64 date) {...} 34 | function transferableTokens(address holder, uint64 time) public view returns(uint256) {...} 35 | 36 | ¬ SafeMath 37 | function safeMul(uint a, uint b) internal returns(uint) {...} 38 | function safeDiv(uint a, uint b) internal returns(uint) {...} 39 | function safeSub(uint a, uint b) internal returns(uint) {...} 40 | function safeAdd(uint a, uint b) internal returns(uint) {...} 41 | function max64(uint64 a, uint64 b) internal view returns(uint64) {...} 42 | function min64(uint64 a, uint64 b) internal view returns(uint64) {...} 43 | function max256(uint256 a, uint256 b) internal view returns(uint256) {...} 44 | function min256(uint256 a, uint256 b) internal view returns(uint256) {...} 45 | function assert(bool assertion) internal {...} 46 | 47 | ¬ MiniMeToken 48 | string public name; 49 | uint8 public decimals; 50 | string public symbol; 51 | string public version; 52 | struct public Checkpoint { 53 | uint128 fromBlock; 54 | uint128 value; 55 | } 56 | MiniMeToken public parentToken; 57 | uint256 public parentSnapShotBlock; 58 | uint256 public creationBlock; 59 | mapping(address => struct MiniMeToken.Checkpoint[]) balances; 60 | mapping(address => mapping(address => uint256)) allowed; 61 | struct MiniMeToken.Checkpoint[] totalSupplyHistory; 62 | bool public transfersEnabled; 63 | MiniMeTokenFactory public tokenFactory; 64 | function MiniMeToken(address _tokenFactory, address _parentToken, uint _parentSnapShotBlock, string _tokenName, uint8 _decimalUnits, string _tokenSymbol, bool _transfersEnabled) public {...} 65 | function transfer(address _to, uint256 _amount) public returns(bool success) {...} 66 | function transferFrom(address _from, address _to, uint256 _amount) public returns(bool success) {...} 67 | function doTransfer(address _from, address _to, uint _amount) internal returns(bool) {...} 68 | function balanceOf(address _owner) public view returns(uint256 balance) {...} 69 | function approve(address _spender, uint256 _amount) public returns(bool success) {...} 70 | function allowance(address _owner, address _spender) public view returns(uint256 remaining) {...} 71 | function approveAndCall(address _spender, uint256 _amount, bytes _extraData) public returns(bool success) {...} 72 | function totalSupply() public view returns(uint) {...} 73 | function balanceOfAt(address _owner, uint _blockNumber) public view returns(uint) {...} 74 | function totalSupplyAt(uint _blockNumber) public view returns(uint) {...} 75 | function min(uint a, uint b) internal returns(uint) {...} 76 | function createCloneToken(string _cloneTokenName, uint8 _cloneDecimalUnits, string _cloneTokenSymbol, uint _snapshotBlock, bool _transfersEnabled) public returns(address) {...} 77 | function generateTokens(address _owner, uint _amount) public onlyController returns(bool) {...} 78 | function destroyTokens(address _owner, uint _amount) public onlyController returns(bool) {...} 79 | function enableTransfers(bool _transfersEnabled) public onlyController {...} 80 | function getValueAt(struct MiniMeToken.Checkpoint[] checkpoints, uint _block) internal view returns(uint) {...} 81 | function updateValueAtNow(struct MiniMeToken.Checkpoint[] checkpoints, uint _value) internal {...} 82 | function isContract(address _addr) internal view returns(bool) {...} 83 | function () public payable {...} 84 | event NewCloneToken(address _cloneToken, uint _snapshotBlock); 85 | 86 | ¬ Controlled 87 | modifier onlyController() {...} 88 | address public controller; 89 | function Controlled() public {...} 90 | function changeController(address _newController) public onlyController {...} 91 | 92 | ¬ ERC20 93 | function totalSupply() public view returns(uint); 94 | function balanceOf(address who) public view returns(uint); 95 | function allowance(address owner, address spender) public view returns(uint); 96 | function transfer(address to, uint value) public returns(bool ok); 97 | function transferFrom(address from, address to, uint value) public returns(bool ok); 98 | function approve(address spender, uint value) public returns(bool ok); 99 | event Transfer(address from, address to, uint value); 100 | event Approval(address owner, address spender, uint value); 101 | -------------------------------------------------------------------------------- /test/output/cryptokitties.members.output: -------------------------------------------------------------------------------- 1 | 2 | ¬ KittyCore 3 | address public newContractAddress; 4 | function KittyCore() public {...} 5 | function setNewAddress(address _v2Address) external onlyCEO whenPaused {...} 6 | function () external payable {...} 7 | function getKitty(uint256 _id) external view returns(bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes) {...} 8 | function unpause() public onlyCEO whenPaused {...} 9 | function withdrawBalance() external onlyCFO {...} 10 | 11 | ¬ KittyMinting 12 | uint256 public constant PROMO_CREATION_LIMIT; 13 | uint256 public constant GEN0_CREATION_LIMIT; 14 | uint256 public constant GEN0_STARTING_PRICE; 15 | uint256 public constant GEN0_AUCTION_DURATION; 16 | uint256 public promoCreatedCount; 17 | uint256 public gen0CreatedCount; 18 | function createPromoKitty(uint256 _genes, address _owner) external onlyCOO {...} 19 | function createGen0Auction(uint256 _genes) external onlyCOO {...} 20 | function _computeNextGen0Price() internal view returns(uint256) {...} 21 | 22 | ¬ KittyAuction 23 | function setSaleAuctionAddress(address _address) external onlyCEO {...} 24 | function setSiringAuctionAddress(address _address) external onlyCEO {...} 25 | function createSaleAuction(uint256 _kittyId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration) external whenNotPaused {...} 26 | function createSiringAuction(uint256 _kittyId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration) external whenNotPaused {...} 27 | function bidOnSiringAuction(uint256 _sireId, uint256 _matronId) external payable whenNotPaused {...} 28 | function withdrawAuctionBalances() external onlyCLevel {...} 29 | 30 | ¬ KittyBreeding 31 | event Pregnant(address owner, uint256 matronId, uint256 sireId, uint256 cooldownEndBlock); 32 | uint256 public autoBirthFee; 33 | uint256 public pregnantKitties; 34 | GeneScienceInterface public geneScience; 35 | function setGeneScienceAddress(address _address) external onlyCEO {...} 36 | function _isReadyToBreed(Kitty _kit) internal view returns(bool) {...} 37 | function _isSiringPermitted(uint256 _sireId, uint256 _matronId) internal view returns(bool) {...} 38 | function _triggerCooldown(Kitty _kitten) internal {...} 39 | function approveSiring(address _addr, uint256 _sireId) external whenNotPaused {...} 40 | function setAutoBirthFee(uint256 val) external onlyCOO {...} 41 | function _isReadyToGiveBirth(Kitty _matron) private view returns(bool) {...} 42 | function isReadyToBreed(uint256 _kittyId) public view returns(bool) {...} 43 | function isPregnant(uint256 _kittyId) public view returns(bool) {...} 44 | function _isValidMatingPair(Kitty _matron, uint256 _matronId, Kitty _sire, uint256 _sireId) private view returns(bool) {...} 45 | function _canBreedWithViaAuction(uint256 _matronId, uint256 _sireId) internal view returns(bool) {...} 46 | function canBreedWith(uint256 _matronId, uint256 _sireId) external view returns(bool) {...} 47 | function _breedWith(uint256 _matronId, uint256 _sireId) internal {...} 48 | function breedWithAuto(uint256 _matronId, uint256 _sireId) external payable whenNotPaused {...} 49 | function giveBirth(uint256 _matronId) external whenNotPaused returns(uint256) {...} 50 | 51 | ¬ KittyOwnership 52 | string public constant name; 53 | string public constant symbol; 54 | ERC721Metadata public erc721Metadata; 55 | bytes4 constant InterfaceSignature_ERC165; 56 | bytes4 constant InterfaceSignature_ERC721; 57 | function supportsInterface(bytes4 _interfaceID) external view returns(bool) {...} 58 | function setMetadataAddress(address _contractAddress) public onlyCEO {...} 59 | function _owns(address _claimant, uint256 _tokenId) internal view returns(bool) {...} 60 | function _approvedFor(address _claimant, uint256 _tokenId) internal view returns(bool) {...} 61 | function _approve(uint256 _tokenId, address _approved) internal {...} 62 | function balanceOf(address _owner) public view returns(uint256 count) {...} 63 | function transfer(address _to, uint256 _tokenId) external whenNotPaused {...} 64 | function approve(address _to, uint256 _tokenId) external whenNotPaused {...} 65 | function transferFrom(address _from, address _to, uint256 _tokenId) external whenNotPaused {...} 66 | function totalSupply() public view returns(uint) {...} 67 | function ownerOf(uint256 _tokenId) external view returns(address owner) {...} 68 | function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) {...} 69 | function _memcpy(uint _dest, uint _src, uint _len) private view {...} 70 | function _toString(bytes32[4] _rawBytes, uint256 _stringLength) private view returns(string) {...} 71 | function tokenMetadata(uint256 _tokenId, string _preferredTransport) external view returns(string infoUrl) {...} 72 | 73 | ¬ ERC721 74 | function totalSupply() public view returns(uint256 total); 75 | function balanceOf(address _owner) public view returns(uint256 balance); 76 | function ownerOf(uint256 _tokenId) external view returns(address owner); 77 | function approve(address _to, uint256 _tokenId) external; 78 | function transfer(address _to, uint256 _tokenId) external; 79 | function transferFrom(address _from, address _to, uint256 _tokenId) external; 80 | event Transfer(address from, address to, uint256 tokenId); 81 | event Approval(address owner, address approved, uint256 tokenId); 82 | function supportsInterface(bytes4 _interfaceID) external view returns(bool); 83 | 84 | ¬ KittyBase 85 | event Birth(address owner, uint256 kittyId, uint256 matronId, uint256 sireId, uint256 genes); 86 | event Transfer(address from, address to, uint256 tokenId); 87 | struct public Kitty { 88 | uint256 genes; 89 | uint64 birthTime; 90 | uint64 cooldownEndBlock; 91 | uint32 matronId; 92 | uint32 sireId; 93 | uint32 siringWithId; 94 | uint16 cooldownIndex; 95 | uint16 generation; 96 | } 97 | uint32[14] public cooldowns; 98 | uint256 public secondsPerBlock; 99 | struct KittyBase.Kitty[] kitties; 100 | mapping(uint256 => address) public kittyIndexToOwner; 101 | mapping(address => uint256) ownershipTokenCount; 102 | mapping(uint256 => address) public kittyIndexToApproved; 103 | mapping(uint256 => address) public sireAllowedToAddress; 104 | SaleClockAuction public saleAuction; 105 | SiringClockAuction public siringAuction; 106 | function _transfer(address _from, address _to, uint256 _tokenId) internal {...} 107 | function _createKitty(uint256 _matronId, uint256 _sireId, uint256 _generation, uint256 _genes, address _owner) internal returns(uint) {...} 108 | function setSecondsPerBlock(uint256 secs) external onlyCLevel {...} 109 | 110 | ¬ KittyAccessControl 111 | event ContractUpgrade(address newContract); 112 | address public ceoAddress; 113 | address public cfoAddress; 114 | address public cooAddress; 115 | bool public paused; 116 | modifier onlyCEO() {...} 117 | modifier onlyCFO() {...} 118 | modifier onlyCOO() {...} 119 | modifier onlyCLevel() {...} 120 | function setCEO(address _newCEO) external onlyCEO {...} 121 | function setCFO(address _newCFO) external onlyCEO {...} 122 | function setCOO(address _newCOO) external onlyCEO {...} 123 | modifier whenNotPaused() {...} 124 | modifier whenPaused() {...} 125 | function pause() external onlyCLevel whenNotPaused {...} 126 | function unpause() public onlyCEO whenPaused {...} 127 | -------------------------------------------------------------------------------- /test/output/rep.members.output: -------------------------------------------------------------------------------- 1 | 2 | ¬ RepToken 3 | ERC20Basic public legacyRepContract; 4 | uint256 public targetSupply; 5 | string public constant name; 6 | string public constant symbol; 7 | uint256 public constant decimals; 8 | event Migrated(address holder, uint256 amount); 9 | function RepToken(address _legacyRepContract, uint256 _amountUsedToFreeze, address _accountToSendFrozenRepTo) public {...} 10 | function migrateBalances(address[] _holders) public onlyOwner beforeInitialized returns(bool) {...} 11 | function migrateBalance(address _holder) public onlyOwner beforeInitialized returns(bool) {...} 12 | function unpause() public onlyOwner whenPaused afterInitialized returns(bool) {...} 13 | 14 | ¬ PausableToken 15 | function transfer(address _to, uint _value) public whenNotPaused returns(bool) {...} 16 | function transferFrom(address _from, address _to, uint _value) public whenNotPaused returns(bool) {...} 17 | 18 | ¬ Pausable 19 | event Pause(); 20 | event Unpause(); 21 | bool public paused; 22 | modifier whenNotPaused() {...} 23 | modifier whenPaused() {...} 24 | function pause() public onlyOwner whenNotPaused returns(bool) {...} 25 | function unpause() public onlyOwner whenPaused returns(bool) {...} 26 | 27 | ¬ Ownable 28 | address public owner; 29 | function Ownable() public {...} 30 | modifier onlyOwner() {...} 31 | function transferOwnership(address newOwner) public onlyOwner {...} 32 | 33 | ¬ StandardToken 34 | mapping(address => mapping(address => uint256)) allowed; 35 | function transferFrom(address _from, address _to, uint256 _value) public returns(bool) {...} 36 | function approve(address _spender, uint256 _value) public returns(bool) {...} 37 | function allowance(address _owner, address _spender) public view returns(uint256 remaining) {...} 38 | 39 | ¬ BasicToken 40 | using SafeMath for uint256; 41 | mapping(address => uint256) balances; 42 | function transfer(address _to, uint256 _value) public returns(bool) {...} 43 | function balanceOf(address _owner) public view returns(uint256 balance) {...} 44 | 45 | ¬ ERC20Basic 46 | uint256 public totalSupply; 47 | function balanceOf(address who) public view returns(uint256); 48 | function transfer(address to, uint256 value) public returns(bool); 49 | event Transfer(address from, address to, uint256 value); 50 | 51 | ¬ ERC20 52 | function allowance(address owner, address spender) public view returns(uint256); 53 | function transferFrom(address from, address to, uint256 value) public returns(bool); 54 | function approve(address spender, uint256 value) public returns(bool); 55 | event Approval(address owner, address spender, uint256 value); 56 | 57 | ¬ ERC20Basic 58 | uint256 public totalSupply; 59 | function balanceOf(address who) public view returns(uint256); 60 | function transfer(address to, uint256 value) public returns(bool); 61 | event Transfer(address from, address to, uint256 value); 62 | 63 | ¬ Initializable 64 | bool public initialized; 65 | modifier afterInitialized() {...} 66 | modifier beforeInitialized() {...} 67 | function endInitialization() internal beforeInitialized returns(bool) {...} 68 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(60000); 2 | --------------------------------------------------------------------------------