diff --git a/package-lock.json b/package-lock.json index 73f8ecf2c..cbe02b178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26949,12 +26949,27 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", - "dev": true, "requires": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0" } }, + "keccak256": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.3.tgz", + "integrity": "sha512-EkF/4twuPm1V/gn75nejOUrKfDUJn87RMLzDWosXF3pXuOvesiSgX35GcmbqzdImCASEkE/WaklWGWSa+Ha5bQ==", + "requires": { + "bn.js": "^4.11.8", + "keccak": "^3.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, "kew": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", @@ -29128,8 +29143,7 @@ "node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, "node-emoji": { "version": "1.10.0", @@ -29174,8 +29188,7 @@ "node-gyp-build": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "dev": true + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" }, "node-int64": { "version": "0.4.0", diff --git a/package.json b/package.json index 8acf22389..b85a527dc 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,8 @@ } }, "dependencies": { + "keccak": "^3.0.1", + "keccak256": "^1.0.3", "phantomjs-prebuilt": "^2.1.16", "yarn": "^1.22.10" } diff --git a/scripts/functionSignatures.sh b/scripts/functionSignatures.sh new file mode 100755 index 000000000..6934c05c7 --- /dev/null +++ b/scripts/functionSignatures.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# This script extracts all function signatures from contracts on a repo. +# It's meant to run on Linux from the repo base folder like this: +# cd Sovryn-smart-contracts/ +# ./scripts/functionSignatures.sh + +find . -type f -name '*.sol' ! -path '*node_modules/*' -print0 | xargs -0 sed -z "s/\/\/[^\n]*\n//g" | awk '/function/,/{/' | awk '/function/,/;/' | sed "s/[[:space:]]+function/function/" | sed -z "s/\n[\t ]*//g" | sed -z "s/{/\n/g" | sed -z "s/;/\n/g" | egrep --text "^function " | egrep --text " (public|external) " | sed -E "s/(address|u*int[0-9]+|bytes[0-9]*|bool|string)(\[[0-9]*\])* [^,\)]+/\1/g" | sed "s/function //" | sed -r "s/ (public|external).*$//" | sed -r "s/\s+//g" + + diff --git a/scripts/getSelectors.js b/scripts/getSelectors.js new file mode 100644 index 000000000..364300e48 --- /dev/null +++ b/scripts/getSelectors.js @@ -0,0 +1,187 @@ +// This is a nodejs script to compute the selectors of Solidity events or functions +// (four bytes of the Keccak-256 or SHA-3 hash of the signature of the function) +// Knowledge: In Solidiy selector = bytes4(keccak256(signature)) +// Install: npm i keccak256 +// Run: node ./scripts/getSelectors.js ./contracts functions > functionSignatures.txt +// Run: node ./scripts/getSelectors.js ./contracts events > eventTopics.txt + +// TODO: Pending to fix 3 issues: +// 1.- On function signatures, interfaces should be typed as addresses. +// 2.- On function signatures, structs should be exploded into their components, around brackets () +// 3.- According to the list swapExternal(address,address,address,address,uint256,uint256,bytes) has selector: 0x11058a8a +// According to vscode: e321b540 => swapExternal(address,address,address,address,uint256,uint256,uint256,bytes) +// It has an additional parameter. This case should be debugged. + +// Get the path where contracts are located +var args = process.argv.slice(2); +let path = String(args[0]); + +// Get the type of items to search on contracts +let searchType = String(args[1]); + +const keccak256 = require("keccak256"); + +// Get keccak256 in 0x string format for a given content +var keccak256_0x = function (content) { + return "0x" + keccak256(content).toString("hex"); +}; + +const fs = require("fs"); + +// Parse a contract searching for function and event declarations +var parseContract = function (fileContent) { + fileContent = fileContent + .replace(/\/\/[^\n]*\n/g, "\n") // remove comments like // + .replace(/\/\*[\s\S]*?\*\//g, ""); // remove comments like /* */ + + var signatureList = {}; + if (searchType == "functions") { + searchRegExp = new RegExp(/function [^\)]*\)/g); // focus on function declarations + } else if (searchType == "events") { + searchRegExp = new RegExp(/event [^\)]*\)/g); // focus on event declarations + } else { + console.log("Error: Unknown searchType", searchType); + process.exit(1); + } + + while (null != (f = searchRegExp.exec(fileContent))) { + // For every function or event found + signature = f[0] + .replace(/(function|event) /g, "") // remove "function " or "event " on every match + .replace(/([\(,])\s+/g, "$1") // remove whitespaces and newlines inmediatly after ( or , + .replace(/\s+\)/g, ")") // remove whitespaces and newlines inmediatly before ) + .replace(/\s.*?([,\)])/g, "$1") // remove var names and extra modifiers + .replace(/^(u?int[0-9]*|address|bool|string|bytes(32|4)*)/g, "address") // every unknown type found is considered to be an address + ; + if (!!signature) { + signatureList[signature] = keccak256_0x(signature); + } + } + + return signatureList; +}; + +// Parse a contract searching for interface declarations +var parseInterfacesFromContract = function (fileContent) { + fileContent = fileContent + .replace(/\/\/[^\n]*\n/g, "\n") // remove comments like // + .replace(/\/\*[\s\S]*?\*\//g, ""); // remove comments like /* */ + + var interfaceList = []; + searchRegExp = new RegExp(/interface [^\}]*\}/g); // focus on interface declarations + + // Get all interfaces from repo + while (null != (f = searchRegExp.exec(fileContent))) { + // For every interface found + interfaceName = f[0] + .replace(/[\n\r\t\s]+/g, " ") // remove newlines and tabs + .replace(/^interface ([^\s]+).*$/g, "$1") // leave only interface name + ; + if (!!interfaceName) { + interfaceList.push(interfaceName); + } + } + + return interfaceList; +}; + +// Parse a contract searching for struct declarations +var parseStructsFromContract = function (fileContent) { + fileContent = fileContent + .replace(/\/\/[^\n]*\n/g, "\n") // remove comments like // + .replace(/\/\*[\s\S]*?\*\//g, ""); // remove comments like /* */ + + var structList = {}; + searchRegExp = new RegExp(/struct [^\}]*\}/g); // focus on struct declarations + + // Get all structs from repo + while (null != (f = searchRegExp.exec(fileContent))) { + // For every struct found + structName = f[0] + .replace(/[\n\r\t\s]+/g, " ") // remove newlines and tabs + .replace(/^struct ([^\s]+).*$/g, "$1") // leave only struct name + ; + structDeclaration = f[0] + .replace(/[\n\r\t\s]+/g, " ") // remove newlines and tabs + .replace(/^struct [^\s]+ \{(.*)\}/g, "$1") // leave only struct declaration + ; + if (!!structName) { + structList[structName] = structDeclaration; + } + } + + return structList; +}; + +// Loop through fileList and extract all interfaces from repo +var getAllInterfaces = function (fileList) { + var interfaces = []; + for (let file of fileList) { + // console.log("\nFile: ", file); + let content = fs.readFileSync(file, { encoding: "utf8" }); + var interfacesToAdd = parseInterfacesFromContract(content); + interfaces.push(...interfacesToAdd); + } + + return interfaces; +}; + +// Loop through fileList and extract all structs from repo +var getAllStructs = function (fileList) { + var structs = {}; + for (let file of fileList) { + // console.log("\nFile: ", file); + let content = fs.readFileSync(file, { encoding: "utf8" }); + var structsToAdd = parseStructsFromContract(content); + for (var key in structsToAdd) { + structs[key] = structsToAdd[key]; + } + } + + return structs; +}; + +// Loop through fileList and call parser on each one +var parseContractList = function (fileList) { + var contractSignatures = {}; + for (let file of fileList) { + // console.log("\nFile: ", file); + let content = fs.readFileSync(file, { encoding: "utf8" }); + contractSignatures[file] = parseContract(content); + } + + return contractSignatures; +}; + +// Open files recursively +const glob = require("glob"); + +var getDirectories = function (src, ext, callback) { + glob(src + "/**/*" + ext, callback); +}; + +getDirectories(path, ".sol", function (err, res) { + if (err) { + console.log("Error", err); + } else { +/* + interfaces = getAllInterfaces(res); + +console.log("Interfaces found: ", interfaces); +process.exit(1); + structs = getAllStructs(res); + +console.log("Structs found: ", structs); +process.exit(1); +*/ + contractSignatures = parseContractList(res); + // console.log("contractSignatures: ", contractSignatures); + // Loop through results and apply tabulated format to copy/past on Google Docs + for (const [contract, functions] of Object.entries(contractSignatures)) { + console.log(contract); + for (const [signature, selector] of Object.entries(functions)) { + console.log("\t" + signature + "\t" + selector.slice(0, 10) + "\t" + selector); + } + } + } +}); diff --git a/scripts/keccak256.js b/scripts/keccak256.js new file mode 100644 index 000000000..7316b84de --- /dev/null +++ b/scripts/keccak256.js @@ -0,0 +1,27 @@ +// This is a nodejs script to compute the selectors of Solidity events or functions +// (four bytes of the Keccak-256 or SHA-3 hash of the signature of the function) +// Install: npm i keccak256 + +var myArgs = process.argv.slice(2); +// console.log('arg: ', String(myArgs[0]).split(/\r?\n/)); + +let signatures = String(myArgs[0]) + .replace(/;/g, "") // remove final ; + .split(/\r?\n/) // split lines, every line is a signature + .sort(function (a, b) { + // sorted alphabetically + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }) + .filter((item, i, ar) => ar.indexOf(item) === i); // get unique +// console.log('signatures: ', signatures); + +const keccak256 = require("keccak256"); +for (let s of signatures) { + if (s) console.log(s + "\t" + "0x" + keccak256(s).toString("hex")); +}