Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Predictor #440

Open
wants to merge 19 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,194 changes: 3,077 additions & 4,117 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"ethereumjs-abi": "^0.6.8",
"ethers": "^5.0.19",
"ganache-core": "^2.10.2",
"glob":"^8.0.2",
"hardhat": "^2.9.3",
"hardhat-abi-exporter": "^2.2.1",
"hardhat-contract-sizer": "^2.0.2",
Expand Down
59 changes: 59 additions & 0 deletions scripts/predictor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# WHAT IS THIS SCRIPT SET FOR?

This set of scripts is intended to help devs to find out if the contracts of a given commit / branch in a repo will reproduce exactly the contracts deployed in the blockchain.
Copy link
Contributor

@tjcloa tjcloa Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This set of scripts is intended to help devs to find out if the contracts ABI of a given commit / branch in a repo correspond to the contracts deployed in the blockchain.

These scripts are categorized into four .js files for convenient separate execution.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls add a high level schematic description of how it works as a sequence of steps like

  1. provide/generate ABIs and bytecodes of the contracts you want to verify
  2. provide contracts addresses
  3. run comparator script on a blockchain (provide as parameter) to get the list of contracts which ABIs correspond to the deployed contracts meaning that they will be successfully verified in the blockchain explorers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw can it be extended with automatic verifier? :)

This can be used as a tool to predict if the contract code in certain repo / branch / commit will verify in a block explorer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This can be used as a tool to predict if the contract code in certain repo / branch / commit will verify in a block explorer.
This can be used as a tool to identify if the contract code in certain repo / branch / commit will verify in a block explorer.


## Scripts and JSON Files

The folder ./scripts/predictor have four .js files and may have several .json files, but one them is invariant: contract_config.json

These scripts will work only provided that there exist a folder in the repo named ./scripts/contractInteraction contentive of the .json files listing all the Sovryn's deployed contracts.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
These scripts will work only provided that there exist a folder in the repo named ./scripts/contractInteraction contentive of the .json files listing all the Sovryn's deployed contracts.
These scripts will work only provided that there exists a folder in the repo named ./scripts/contractInteraction contentive of the .json files listing all the Sovryn's deployed contracts.

pls consider putting all settings - paths etc. in e.g. .deployment-verifier.json or .ts/.js


The script files are:

- createJSON.js --> to initialize the dump .json files with the expected format
- findFiles.js --> to complete information in the files created by the former script
- fillJSON.js --> to help to fill dump files when data is provided by hand
- comparator.js --> generate a report based on the info placed in dump files, and predict if the contracts held in the repo will generate a bytecode congruent with the deployed bytecode.

This scripts are intended to be universal and independent on how old the solidity compiler was the chosen to compile and deploy.

The contract_config.json file will content precise information about the .json files that must be present in the folder ./scripts/contractInteraction, about the network providers and the dump files. Anyone can extend the initial content of the file, but the info must be right or the scripts will fail.

## How to Use These Scripts

1. Clone locally the smart contracts git repo.
2. Install the dependencies as instructed in the RAEADME.md file of that repo.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. Install the dependencies as instructed in the RAEADME.md file of that repo.
2. Install the dependencies as instructed in the README.md file of that repo.

3. Execute the compiling script.
This will generate the needed files and folders with all the info on compilation needed for this script. More info about this can be found [here](https://wiki.sovryn.app/en/technical-documents/API/ApiDoc#h-3-compiling-contracts) for amm repo, and [here](https://wiki.sovryn.app/en/technical-documents/API/ApiDoc#h-3-compile-all-the-contracts-with-hard-hat) for the Sovryn protocol.
4. `./scripts/predictor/contract_config.json` can be edited to extend it. Be ware that the info must be precise or the script may fail with unexpected errors.
5. Check the info in folder `./scripts/contractInteraction` and make sure that at least that the files `mainnet_contracts.json` and `testnet_contracts.json` are present with the relevant info. These files are expected to contain as accurate and complete information as possible, about contract deployments on chain.
6. Go to the folder `./scripts/predictor` and execute:
Comment on lines +30 to +32
Copy link
Contributor

@tjcloa tjcloa Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls consider adding paths to a config like .deployment-verifier.json


```
$ node createJSON
```

After this, files like `m_deployed_compiled.json` mut be present. Be ware that if there was a previous .json file with that name, this script will overwrite it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls consider adding it as parameter to cover cases when you can add an external ABI manually and those won't be overwritten

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
After this, files like `m_deployed_compiled.json` mut be present. Be ware that if there was a previous .json file with that name, this script will overwrite it.
After this, files like `m_deployed_compiled.json` mut be present. Beware that if there was a previous .json file with that name, this script will overwrite it.


7. Now execute:

```
$ node findFiles
```
This will fill the dump files with the paths of the files with the conpiled bytecodes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will fill the dump files with the paths of the files with the conpiled bytecodes.
This will fill the dump files with the paths of the files with the compiled bytecodes.


8. We can now inspect the modified dump files. It is not expected that all the field have been filled. If there are still missing information we can copy/paste these dump files in other smart contract local repos in which we have performed the compilation scripts. Then we can repeat the step N° 7 in that other repository, and the dump files will be filled with additional information that can only be located in that other repo.
9. We can inspect the modified dump files. If there is still missing information that can be found in the repositories, we can fill such data by hand. We can use the script `fillJSON.js` to automatically copy/paste the new info from the file of one network to its couple. E.g.: if we filled by hand data in `m_deployed_compiled.json`, we can use `fillJSON.js` to copy such data to `t_deployed_compiled.json`. The way to do this is by executing:

```
$ node fillJSON <file_path_FROM_file> <file_path_TO_file>
```

10. Once we have dump files with the enough information or the expected amount of data, we can now execute:

```
$ node comparator
```
This script will generate a report in the file: `scripts/predictor/report` which will be re-written if present.
3 changes: 3 additions & 0 deletions scripts/predictor/bsc_testnet_deployed_compiled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"multisig": ["0xCc653b64e8f4f2aDEa87490f11d090472E08838A", "./"]
}
291 changes: 291 additions & 0 deletions scripts/predictor/comparator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
const { ethers } = require("ethers");
const fs = require("fs");
const revision = require("child_process");
var commHash = revision.execSync("git rev-parse HEAD");
commHash = commHash.toString().substring(0, commHash.length - 1);
var origin = revision.execSync("git config --get remote.origin.url");
origin = origin.toString().substring(0, origin.length - 1);
var branch = revision.execSync("git branch");
branch = branch
.toString()
.substring(0, branch.length - 1)
.slice(2);

const FIX_ID = 32 * 2;

console.log("------------------comparator script-----------------------", "\n", "\n", "\n");

const configContract = "./contract_config.json";

// this is the main function; it takes the config JSON to guide the runs
// this config is still in the form of the path of a file
async function iterator(CfgC) {
// we take the JSON object, from the path og the file
// it is assumed that CfgC exists
var F = require(CfgC);
// we measure the length of the JSON object
var L = Object.keys(F).length;

// we start a stream of data to feed the report file
var STR = initializeReport("./report");

// the headers for both the report and the console
STR.write(
"Prediction Report: to know if the code on this repo will produce bytecode that will verify in a block explorer" +
"\n" +
"\n" +
"\n"
);
STR.write(
"Repository info: " +
"\n" +
"URL: " +
origin +
"\n" +
"Branch: " +
branch +
"\n" +
"Commit: " +
commHash +
"\n" +
"\n" +
"\n"
);
console.log(
"Prediction Report: to know if the code on this repo will produce bytecode that will verify in a block explorer",
"\n",
"\n",
"\n"
);
console.log(
"Repository info: ",
"\n",
"URL: ",
origin,
"\n",
"Branch: ",
branch,
"\n",
"Commit: ",
commHash,
"\n",
"\n",
"\n"
);

// the loop to cycle through all the lists of contracts for all the networks described in CfgC
for (let i = 0; i < L; ++i) {
let ki = Object.keys(F)[i]; // this is the network id
STR.write("\n contracts dpeloyed in the network N°: " + ki + "\n" + "\n");
console.log("\n contracts dpeloyed in the network N°: ", ki + "\n" + "\n");
let vi = F[ki][2]; // this is the path to the JSON file with the list of contracts for that network id
let Provi = F[ki][1]; // this is Sovryn's provider endpoint for that network id
STR.write("\n using provider: " + Provi + "\n" + "\n");
console.log("\n using provider: ", Provi, "\n", "\n");
let pi = new ethers.providers.JsonRpcProvider(Provi); // ethers provider

// we assume that the string vi is rightfully written
if (fs.existsSync(vi)) {
let C = require(vi); // this is the JSON object with the data of contracts
let Lc = Object.keys(C).length; // this is how many contract are listed in that JSON
STR.write("checking " + Lc + " contracts." + "\n" + "\n");
console.log("checking ", Lc, " contracts.", "\n", "\n");

// the loop to cycle through all the contracts of a network's list
for (let j = 0; j < Lc; ++j) {
kj = Object.keys(C)[j]; // this is the name of the contract
STR.write(
"checking contract N°: " +
(j + 1) +
" from " +
Lc +
", named: " +
kj +
"\n"
);
console.log(
"checking contract N°: ",
j + 1,
" from ",
Lc,
", named: ",
kj,
"\n"
);
let Ad = C[kj][0]; // this is the address of the deployment of that contract in that network id
Ad = Ad.toLowerCase();
let Pt = C[kj][1]; // this is the path of the JSON file produced by the compilation, with the predicted bytecode

// we need assure that Pt is rightfully written
if (Pt != null && Pt != undefined && Pt != "./") {
// we need make sure that the file in the path Pt exists
if (fs.existsSync(Pt)) {
let Bytc = require(Pt); // this is the JSON object holding the compilation's bytecode
let B0 = Bytc["deployedBytecode"]; // This is the compilation's bytecode
// preventing missing URL response error
let B1 = ethers.utils.isAddress(Ad) ? await getCode(pi, Ad, 1) : "0x"; // This is the bytecode from the deployed contract in blockchain
let veredict =
(B0 != undefined && B1 != undefined && B0 != null && B1 != null)
? compare(B0, B1)
: false; // true: it should verify; false: it won't verify
if (veredict) {
let success =
"the contract " +
kj +
", deployed in the network N° " +
ki +
", with the address " +
Ad +
" will successfully verify \n";
STR.write(success);
console.log(success);
} else {
let fail =
"the contract " +
kj +
", deployed in the network N° " +
ki +
", with the address " +
Ad +
" will NOT verify \n";
STR.write(fail);
console.log(fail);
}
} else {
STR.write(
"the bytecode for the contract " + kj + " may not be in this repo" + "\n"
);
console.log(
"the bytecode for the contract ",
kj,
" may not be in this repo",
"\n"
);
}
} else {
STR.write(
"the compilation for the contract " + kj + " has not been done yet" + "\n"
);
console.log(
"the compilation for the contract ",
kj,
" has not been done yet",
"\n"
);
}
}
} else {
STR.write(
"make sure createJSON was run first, " +
vi +
" file not initialized" +
"\n"
);
console.log(
"make sure createJSON was run first, ",
vi,
" file not initialized",
"\n"
);
}
}
// bug: iterator do not verify if a given file exist or not in a path
STR.end();
}

// preventing missing URL response error
async function getCode(provider, Addrss, tries) {
console.log('\n number of tries: ', tries, '\n');
let byCd = '0x';
try {
byCd = await provider.getCode(Addrss);
return byCd;
} catch (error) {
++tries;
console.log('\n we got this error: ', error, '\n');
if (tries < 11) {
byCd = await getCode(provider, Addrss, tries);
return byCd;
}
}
if (tries = 11 &&
byCd == '0x') {
console.log('\n DO NOT TRUST NEXT VEREDICT \n');
return byCd;
}
}

// this function guides the generation of stream of data for the report
function initializeReport(report_path) {
// writtable streams with fs:
// https://stackoverflow.com/questions/3459476/how-to-append-to-a-file-in-node/43370201#43370201
// according to: https://nodejs.org/api/fs.html#file-system-flags
// File system flag 'a': Open file for appending. The file is created if it does not exist.
var stream = fs.createWriteStream(report_path, { flags: "a" });
return stream;
}

function compare(A, B) {

var bytes = select(A, B);
// fixing TypeError
A = bytes[0];
B = bytes[1];
bytes = (A != B ) ? reduce(A, B) : [A, B];

x = bytes[0] == bytes[1];

// console.log(bytes[0], bytes[1]);
return x;

}

function sizes(A, B) {
return A.length == B.length && isPair(A.length);
}

function select(A, B) {
if (!sizes(A, B)) {
console.log("bytecode sizes not equal or invalid");
return ["wrong", "arguments"];
}
// fixing returning an undefined object
let flag = false;
for (let i = A.length; i > 2; i--) {

if (A[i - 1] != B[i - 1]) {
flag = true;
if (isPair(i)) {
A = A.slice(0, i);
B = B.slice(0, i);
} else {
A = A.slice(0, i + 1);
B = B.slice(0, i + 1);
}

return [A, B];
}

}
if (!flag) return [A, B];
}

function reduce(A, B) {
if (!sizes(A, B) || A.length <= FIX_ID + 2) {
console.log("bytecode sizes not equal, invalid or too short");
return ["wrong", "arguments"];
}

x = A.length - FIX_ID;

A = A.slice(0, x);
B = B.slice(0, x);

return [A, B];
}

function isPair(X) {
return X % 2 == 0;
}

iterator(configContract);
17 changes: 17 additions & 0 deletions scripts/predictor/contract_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"30": [
"../contractInteraction/mainnet_contracts.json",
"https://mainnet.sovryn.app/rpc",
"./m_deployed_compiled.json"
],
"31": [
"../contractInteraction/testnet_contracts.json",
"https://testnet.sovryn.app/rpc",
"./t_deployed_compiled.json"
],
"97": [
"../contractInteraction/bsc_testnet_contracts.json",
"https://bsc.sovryn.app/testnet",
"./bscT_deployed_compiled.json"
]
}
Loading