Skip to content

Commit

Permalink
CodeRecon script (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-fruitful authored Jul 25, 2023
1 parent 894a77c commit 65c2be9
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,15 @@ update-e: ## update
-vvvv \
--broadcast

coderecon: ## code recon
@forge script CodeRecon \
-s "run(string[] memory)" ${contractNames} \
-f ${ETH_MAINNET_RPC_URL} \
--chain-id 1 \
--etherscan-api-key ${ETHERSCAN_API_KEY} \
-vv \
; node cli-tools/parse-json.js

compb: ## Compare bytecode
@forge script CheckBytecode \
-s "run(uint8)" ${checkBytecodeAction} \
Expand Down
40 changes: 40 additions & 0 deletions cli-tools/code-recon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");

function getFileNames(directory) {
return fs.readdirSync(directory).map((file) => {
const baseName = path.basename(file, ".sol");
return path.parse(baseName).name;
});
}

let fileNames = [];
fileNames = fileNames.concat(getFileNames("src/diamonds/nayms/facets/"));
fileNames = fileNames.concat(getFileNames("src/diamonds/shared/facets/"));

const namesToRemove = ["DiamondCutFacet", "OwnershipFacet"];

fileNames = fileNames.filter((name) => !namesToRemove.includes(name));

fileNames.push("Nayms");

fileNames.forEach((name, index) => {
console.log(`contractName[${index}] = "${name}";`);
});

const formattedFileNames = fileNames.map((name) => `'${name}'`);

const arrayString = `[${formattedFileNames.join(",")}]`;

console.log(formattedFileNames);

let cmd = `forge script CodeRecon -s "run(string[] memory)" ${arrayString} -f ${process.env.ETH_MAINNET_RPC_URL} --chain-id 1 --etherscan-api-key ${process.env.ETHERSCAN_API_KEY} -vv`;

console.log(`Running command forge script CodeRecon`);

let output = execSync(cmd);
console.log(`Output: ${output}`);

console.log("Running parse-json.js");
execSync("node cli-tools/parse-json.js");
32 changes: 32 additions & 0 deletions cli-tools/get-filenames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const fs = require("fs");
const path = require("path");

function getFileNames(directory) {
return fs.readdirSync(directory).map((file) => {
const baseName = path.basename(file, ".sol");
return path.parse(baseName).name;
});
}

let fileNames = [];

for (let i = 2; i < process.argv.length; i++) {
const directoryPath = process.argv[i];
fileNames = fileNames.concat(getFileNames(directoryPath));
}

const namesToRemove = ["DiamondCutFacet", "OwnershipFacet"];

fileNames = fileNames.filter((name) => !namesToRemove.includes(name));

fileNames.push("Nayms");

fileNames.forEach((name, index) => {
console.log(`contractName[${index}] = "${name}";`);
});

const formattedFileNames = fileNames.map((name) => `"${name}"`);

const arrayString = `'[${formattedFileNames.join(",")}]'`;

console.log(arrayString);
16 changes: 16 additions & 0 deletions cli-tools/parse-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const fs = require("fs");

let data = fs.readFileSync("codeReconReport.json", "utf8");
data = JSON.parse(data);

let cleanedString = data.reconResult
.replace(/\\"/g, '"')
.replace(/"\{/g, "{")
.replace(/\}"/g, "}")
.slice(1, -1);
cleanedString = "[" + cleanedString + "]";
let cleanedJSON = JSON.parse(cleanedString);

data.reconResult = cleanedJSON;

fs.writeFileSync("codeReconReport.json", JSON.stringify(data, null, 2));
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ fs_permissions = [
{ access = "read-write", path = "./facetsdeployed.txt" },
{ access = "read-write", path = "./deployedAddresses.json" },
{ access = "read-write", path = "./deployedAddressesTest.json" },
{ access = "read-write", path = "./codeReconReport.json" },
{ access = "read", path = "./forge-artifacts/" },
{ access = "read", path = "./broadcast/" },
{ access = "read", path = "./nayms_mnemonic.txt" },
Expand Down
165 changes: 165 additions & 0 deletions script/CodeRecon.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import { strings } from "lib/solidity-stringutils/src/strings.sol";
import { IDiamondLoupe } from "src/diamonds/shared/interfaces/IDiamondLoupe.sol";
import "script/utils/DeploymentHelpers.sol";

struct ReconResult {
string contractName;
address contractAddress;
bytes artifactCode;
bytes onChainCode;
bool isMatchingWithMetadata;
bool isMatchingWithoutMetadata;
}
struct ReconInfo {
uint256 chainId;
uint256 blockNumber;
uint256 timestamp;
ReconResult[] reconResult;
address[] addressesNotMatching;
string[] namesNotMatching;
}

library LibSearch {
function arrayIntersection(
mapping(string => bool) storage matchResultMapping,
mapping(bytes32 => bool) storage tMap,
mapping(bytes32 => string) storage codeToName,
bytes32[] memory arr1,
bytes32[] memory arr2
) internal {
// Loop through the second array and map all elements to true
for (uint256 i; i < arr2.length; i++) {
tMap[arr2[i]] = true;
}

// Loop through the first array and check if any element exists in the map
for (uint256 i; i < arr1.length; i++) {
if (tMap[arr1[i]]) {
matchResultMapping[codeToName[arr1[i]]] = true;
}
}
}
}

contract CodeRecon is DeploymentHelpers {
using strings for *;
using LibSearch for mapping(string => bool);

mapping(bytes32 => bool) tMap;
address[] public contractAddresses;
string[] public contractNames;
bytes32[] public onChainCode;
bytes32[] public onChainCodeWithoutMetadata;
bytes32[] public artifactCode;
bytes32[] public artifactCodeWithoutMetadata;
mapping(bytes32 => string) public artifactCodeToName;
mapping(bytes32 => string) public artifactCodeWithoutMetadataToName;
mapping(string => bool) public matching;
mapping(string => bool) public matchingWithoutMetadata;
address[] addressesNotMatching;
string[] namesNotMatching;

mapping(bytes32 => address) onChainCodeToAddress;
mapping(address => bytes32) addressToOnChainCode;
mapping(bytes32 => address) onChainCodeWithoutMetadataToAddress;
mapping(address => bytes32) addressToOnChainCodeWithoutMetadata;

function getOnchainCode(address proxyDiamondAddress) public {
strings.slice memory needle1 = "a264".toSlice();

IDiamondLoupe diamond = IDiamondLoupe(proxyDiamondAddress);
contractAddresses = diamond.facetAddresses();
contractAddresses.push(proxyDiamondAddress);

for (uint256 i; i < contractAddresses.length; ++i) {
onChainCode.push(keccak256(contractAddresses[i].code));
onChainCodeWithoutMetadata.push(keccak256(bytes(vm.toString(contractAddresses[i].code).toSlice().rfind(needle1).toString())));

onChainCodeToAddress[keccak256(contractAddresses[i].code)] = contractAddresses[i];
addressToOnChainCode[contractAddresses[i]] = keccak256(contractAddresses[i].code);
onChainCodeWithoutMetadataToAddress[keccak256(bytes(vm.toString(contractAddresses[i].code).toSlice().rfind(needle1).toString()))] = contractAddresses[i];
addressToOnChainCodeWithoutMetadata[contractAddresses[i]] = keccak256(bytes(vm.toString(contractAddresses[i].code).toSlice().rfind(needle1).toString()));
}
}

function getArtifactCode(string[] memory contractName) public {
contractNames = contractName;

strings.slice memory needle1 = "a264".toSlice();
for (uint256 i; i < contractName.length; ++i) {
bytes memory artifactCode_ = vm.getDeployedCode(string.concat(contractName[i], ".sol:", contractName[i]));
bytes32 keccakArtifactCode = keccak256(artifactCode_);
artifactCode.push(keccak256(artifactCode_));
artifactCodeToName[keccakArtifactCode] = contractName[i];

bytes32 artifactCodeWithoutMetadata_ = keccak256(bytes(vm.toString(artifactCode_).toSlice().rfind(needle1).toString()));
artifactCodeWithoutMetadata.push(artifactCodeWithoutMetadata_);
artifactCodeWithoutMetadataToName[artifactCodeWithoutMetadata_] = contractName[i];
}
}

function compareCode(string[] memory contractName) public {
getArtifactCode(contractName);
getOnchainCode(getDiamondAddressFromFile());

matching.arrayIntersection(tMap, artifactCodeToName, onChainCode, artifactCode);
matchingWithoutMetadata.arrayIntersection(tMap, artifactCodeWithoutMetadataToName, onChainCodeWithoutMetadata, artifactCodeWithoutMetadata);
for (uint256 i; i < contractAddresses.length; ++i) {
if (matchingWithoutMetadata[artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]]]) {
console2.log("Bytecode WITHOUT metadata matches for facet", artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]], vm.toString(contractAddresses[i]));
}
}

for (uint256 i; i < contractAddresses.length; ++i) {
if (!matchingWithoutMetadata[artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]]]) {
console2.log(
"Bytecode WITHOUT metadata DOES NOT MATCH for facet",
artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]],
vm.toString(contractAddresses[i])
);
addressesNotMatching.push(contractAddresses[i]);
}
}
}

function genOutput() public {
string memory path = "codeReconReport.json";

vm.serializeUint("ReconInfo", "chainId", uint256(block.chainid));
vm.serializeUint("ReconInfo", "blockNumber", uint256(block.number));
vm.serializeUint("ReconInfo", "timestamp", uint256(block.timestamp));

string[] memory reconResults = new string[](contractAddresses.length);

for (uint256 i; i < contractAddresses.length; ++i) {
if (!matchingWithoutMetadata[contractNames[i]]) {
namesNotMatching.push(contractNames[i]);
}

vm.serializeString(string.concat("reconResult", vm.toString(i)), "contractName", artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]]);
vm.serializeAddress(string.concat("reconResult", vm.toString(i)), "contractAddress", onChainCodeWithoutMetadataToAddress[onChainCodeWithoutMetadata[i]]);
// vm.serializeBytes(string.concat("reconResult", vm.toString(i)), "artifactCode", reconResult.artifactCode);
// vm.serializeBytes(string.concat("reconResult", vm.toString(i)), "onChainCode", reconResult.onChainCode);
vm.serializeBool(string.concat("reconResult", vm.toString(i)), "isMatchingWithMetadata", matching[artifactCodeToName[onChainCode[i]]]);
reconResults[i] = vm.serializeBool(
string.concat("reconResult", vm.toString(i)),
"isMatchingWithoutMetadata",
matchingWithoutMetadata[artifactCodeWithoutMetadataToName[onChainCodeWithoutMetadata[i]]]
);
}

vm.serializeAddress("ReconInfo", "addressesNotMatching", addressesNotMatching);
vm.serializeString("ReconInfo", "namesNotMatching", namesNotMatching);

string memory finalJson = vm.serializeString("ReconInfo", "reconResult", reconResults);
vm.writeJson(finalJson, path);
}

function run(string[] memory contractName) public {
compareCode(contractName);
genOutput();
}
}

0 comments on commit 65c2be9

Please sign in to comment.