diff --git a/.circleci/config.yml b/.circleci/config.yml index 8bd70f869..42c27b22c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,6 +54,21 @@ jobs: - node_modules - store_artifacts: path: ./coverage/lcov.info + clash-check: + docker: + - image: maxsam4/solidity-kit + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package.json" }} + - run: yarn install + - run: node --version + - run: truffle version + - run: npm run clash-check + - save_cache: + key: dependency-cache-{{ checksum "package.json" }} + paths: + - node_modules docs: docker: - image: maxsam4/solidity-kit @@ -74,6 +89,7 @@ workflows: commit: jobs: - coverage + - clash-check daily-builds: triggers: - schedule: diff --git a/contracts/mocks/FunctionSigClash1.sol b/contracts/mocks/FunctionSigClash1.sol new file mode 100644 index 000000000..51f505ade --- /dev/null +++ b/contracts/mocks/FunctionSigClash1.sol @@ -0,0 +1,4 @@ +contract functionSigClash1 { + // function clash550254402() public { + // } +} diff --git a/contracts/mocks/FunctionSigClash2.sol b/contracts/mocks/FunctionSigClash2.sol new file mode 100644 index 000000000..0cd0e9f9e --- /dev/null +++ b/contracts/mocks/FunctionSigClash2.sol @@ -0,0 +1,4 @@ +contract functionSigClash2 { + // function proxyOwner() public { + // } +} diff --git a/package.json b/package.json index a33a07af8..3adad2e2c 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "test": "scripts/test.sh 2> /dev/null", + "clash-check": "node scripts/clashCheck.js", "gas": "scripts/gasUsage.sh", "wintest": "scripts\\wintest.cmd", "wincov": "scripts\\wincov.cmd", diff --git a/scripts/clashCheck.js b/scripts/clashCheck.js new file mode 100644 index 000000000..09f36d31c --- /dev/null +++ b/scripts/clashCheck.js @@ -0,0 +1,55 @@ +const fs = require('fs'); +const exec = require('child_process').execSync; +const chalk = require('chalk'); +const Web3 = require('web3'); + +async function readFiles() { + if (fs.existsSync("./build/contracts/")) { + return fs.readdirSync("./build/contracts/"); + } else { + console.log(chalk.yellow('Compiling contracts. This may take a while, please wait.')); + exec('truffle compile'); + return fs.readdirSync("./build/contracts/"); + } +} + +async function checkClashes() { + console.log(chalk.green("Starting function selector clash check")); + let files = await readFiles(); + let contractFunctions = new Set(); + let functionSelectors = new Map(); + files.forEach(item => { + let ABI = JSON.parse(fs.readFileSync(`./build/contracts/${item}`).toString()).abi; + ABI.forEach(element => { + if (element['type'] == 'function') { + let functionSig = element['name'] + '('; + element['inputs'].forEach(input => { + functionSig = functionSig + input['type'] + ','; + }); + if(functionSig[functionSig.length - 1] == ',') + functionSig = functionSig.slice(0, -1) + ')'; + else + functionSig = functionSig + ')'; + contractFunctions.add(functionSig); + } + }); + }); + let clashesFound = false; + contractFunctions.forEach(functionSig => { + let fnSelector = Web3.utils.sha3(functionSig).slice(0, 10); + if(functionSelectors.has(fnSelector)) { + clashesFound = true; + console.log(chalk.red('Function selector clash found!', functionSelectors.get(fnSelector), 'and', functionSig, 'have the same function selector:', fnSelector)); + functionSelectors.set(fnSelector, functionSelectors.get(fnSelector) + ', ' + functionSig); + } else { + functionSelectors.set(fnSelector, functionSig); + } + }); + if (clashesFound) { + console.log(chalk.yellow("The clash(es) might be in two different contracts and hence not be am Issue.\nThis script can not detect this (yet) because of proxy contracts")); + throw("Clash(es) found! Please fix."); + } + console.log(chalk.green("Clash check finished. No Clashes found.")); +} + +checkClashes(); \ No newline at end of file