workflow update 2

This commit is contained in:
Aleksandr S 2022-03-03 18:41:31 +03:00
parent 779561f328
commit e07c3221ba
9 changed files with 547 additions and 2 deletions

View File

@ -6,8 +6,8 @@
"scripts": {
"test": "hardhat run scripts/tests/global-test.ts",
"coverage": "./node_modules/.bin/solidity-coverage",
"check": "ts-node status-checks/huskyCheck.ts",
"check-husky": "ts-node status-checks/huskyCheck.ts",
"check": "node status-checks/huskyCheck.js",
"check-husky": "node status-checks/huskyCheck.js",
"deploy": "node scripts/deployConnectorsFromCmd.js",
"test:runner": "hardhat run scripts/tests/run-tests.ts",
"typechain": "hardhat typechain",

436
status-checks/check.js Normal file
View File

@ -0,0 +1,436 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const forbiddenStrings = ["selfdestruct"];
const getConnectorsList = async (connectorsRootsDirs) => {
try {
const connectors = [];
for (let index = 0; index < connectorsRootsDirs.length; index++) {
const dirs = [connectorsRootsDirs[index]];
while (dirs.length) {
const currentDir = dirs.pop();
const subs = fs.readdirSync(currentDir, { withFileTypes: true });
for (let index = 0; index < subs.length; index++) {
const sub = subs[index];
if (sub.isFile() && sub.name === "main.sol") {
connectors.push(currentDir);
}
else if (sub.isDirectory()) {
dirs.push(`${currentDir}/${sub.name}`);
}
}
}
}
return connectors.map((dir) => ({ path: dir }));
}
catch (error) {
return Promise.reject(error);
}
};
const checkCodeForbidden = async (code, codePath) => {
try {
const forbidden = [];
for (let i1 = 0; i1 < forbiddenStrings.length; i1++) {
const forbiddenStr = forbiddenStrings[i1];
const strs = code.split("\n");
for (let i2 = 0; i2 < strs.length; i2++) {
if (strs[i2].includes(forbiddenStr)) {
forbidden.push(`found '${forbiddenStr}' in ${codePath}:${i2 + 1}`);
}
}
}
return forbidden;
}
catch (error) {
return Promise.reject(error);
}
};
const checkForbidden = async (parentPath, codePath = "./main.sol") => {
try {
if (codePath.startsWith("@")) {
codePath = path.resolve("node_modules", `./${codePath}`);
}
else {
codePath = path.resolve(parentPath, codePath);
}
const code = fs.readFileSync(codePath, { encoding: "utf8" });
const forbidden = await checkCodeForbidden(code, codePath);
if (code.includes("import")) {
const importsPathes = code
.split("\n")
.filter((str) => str.includes("import") &&
str.includes("from") &&
str.includes(".sol"))
.map((str) => str.split("from")[1].replace(/["; ]/gi, ""));
for (let index = 0; index < importsPathes.length; index++) {
const forbiddenErrors = await checkForbidden(path.parse(codePath).dir, importsPathes[index]);
forbidden.push(...forbiddenErrors);
}
}
return codePath.endsWith("main.sol")
? { forbiddenErrors: forbidden, code }
: forbidden;
}
catch (error) {
return Promise.reject(error);
}
};
const checkEvents = async (connector) => {
try {
const errors = [];
const warnings = [];
const eventsPath = `${connector.path}/events.sol`;
const mainPath = `${connector.path}/main.sol`;
if (connector.events.length) {
const eventNames = [];
for (let i1 = 0; i1 < connector.mainEvents.length; i1++) {
const mainEvent = connector.mainEvents[i1];
const name = mainEvent.split("(")[0];
eventNames.push(name);
const event = connector.events.find((e) => e.split("(")[0].split(" ")[1] === name);
if (event) {
const mainEventArgs = mainEvent
.split("(")[1]
.split(")")[0]
.split(",")
.map((a) => a.trim());
const eventArgs = event
.split("(")[1]
.split(")")[0]
.split(",")
.map((a) => a.trim());
if (mainEventArgs.length !== eventArgs.length) {
errors.push(`arguments amount don't match for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`);
continue;
}
for (let i2 = 0; i2 < mainEventArgs.length; i2++) {
if (!mainEventArgs[i2].startsWith(eventArgs[i2].split(" ")[0])) {
errors.push(`invalid argument #${i2 + 1} for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`);
}
}
}
else {
errors.push(`event ${name} missing at ${eventsPath}`);
}
}
if (connector.mainEvents.length < connector.events.length) {
const deprecatedEvents = connector.events.filter((e) => {
let used = false;
for (let index = 0; index < eventNames.length; index++) {
if (e.split("(")[0].split(" ")[1] === eventNames[index])
used = true;
}
return !used;
});
warnings.push(`${deprecatedEvents
.map((e) => e.split("(")[0].split(" ")[1])
.join(", ")} event(s) not used at ${connector.path}/main.sol`);
}
}
else {
warnings.push(`missing events file for ${connector.path}/main.sol`);
}
return { eventsErrors: errors, eventsWarnings: warnings };
}
catch (error) {
return Promise.reject(error);
}
};
const getCommments = async (strs) => {
try {
const comments = [];
let type = '';
for (let index = strs.length - 1; index >= 0; index--) {
const str = strs[index];
if (!type) {
if (str.trim().startsWith("//")) {
type = "single";
}
else if (str.trim().startsWith("*/")) {
type = "multiple";
}
}
if (type === "single" && str.trim().startsWith("//")) {
comments.push(str.replace(/[/]/gi, "").trim());
}
else if (type === "multiple" &&
!str.trim().startsWith("/**") &&
!str.trim().startsWith("*/")) {
comments.push(str.replace(/[*]/gi, "").trim());
}
else if (type === "single" && !str.trim().startsWith("//")) {
break;
}
else if (type === "multiple" && str.trim().startsWith("/**")) {
break;
}
}
return comments;
}
catch (error) {
return Promise.reject(error);
}
};
const parseCode = async (connector) => {
try {
const strs = connector.code.split("\n");
const events = [];
const eventsFirstLines = [];
let func = [];
let funcs = [];
let event = [];
let mainEvents = [];
let firstLine = -1;
let mainEventsLines = [];
for (let index = 0; index < strs.length; index++) {
const str = strs[index];
if (str.includes("function") && !str.trim().startsWith("//")) {
func = [str];
firstLine = index + 1;
}
else if (func.length && !str.trim().startsWith("//")) {
func.push(str);
}
if (func.length && str.startsWith(`${func[0].split("function")[0]}}`)) {
funcs.push({
raw: func.map((str) => str.trim()).join(" "),
comments: await getCommments(strs.slice(0, firstLine)),
firstLine,
});
func = [];
}
}
const allPublicFuncs = funcs
.filter(({ raw }) => {
return raw.includes("external") || raw.includes("public");
})
.map((f) => {
const name = f.raw
.split("(")[0]
.split("function")[1]
.trim();
return {
...f,
name,
};
});
funcs = allPublicFuncs
.filter(({ raw }) => {
if (raw.includes("returns")) {
const returns = raw
.split("returns")[1]
.split("(")[1]
.split(")")[0];
return returns.includes("string") && returns.includes("bytes");
}
return false;
})
.map((f) => {
const args = f.raw
.split("(")[1]
.split(")")[0]
.split(",")
.map((arg) => arg.trim())
.filter((arg) => arg !== "");
return {
...f,
args,
};
});
const eventsPath = `${connector.path}/events.sol`;
if (fs.existsSync(eventsPath)) {
mainEvents = funcs
.map(({ raw }) => raw
.split("_eventName")[2]
.trim()
.split('"')[1])
.filter((raw) => !!raw);
mainEventsLines = mainEvents.map((me) => strs.findIndex((str) => str.includes(me)) + 1);
const eventsCode = fs.readFileSync(eventsPath, { encoding: "utf8" });
const eventsStrs = eventsCode.split("\n");
for (let index = 0; index < eventsStrs.length; index++) {
const str = eventsStrs[index];
if (str.includes("event")) {
event = [str];
firstLine = index + 1;
}
else if (event.length && !str.trim().startsWith("//")) {
event.push(str);
}
if (event.length && str.includes(")")) {
events.push(event.map((str) => str.trim()).join(" "));
eventsFirstLines.push(firstLine);
event = [];
}
}
}
return {
...connector,
events,
eventsFirstLines,
mainEvents,
mainEventsLines,
funcs,
allPublicFuncs,
};
}
catch (error) {
return Promise.reject(error);
}
};
const checkComments = async (connector) => {
try {
const errors = [];
for (let i1 = 0; i1 < connector.funcs.length; i1++) {
const func = connector.funcs[i1];
for (let i2 = 0; i2 < func.args.length; i2++) {
const argName = func.args[i2].split(" ").pop();
if (!func.comments.some((comment) => comment.startsWith("@param") && comment.split(" ")[1] === argName)) {
errors.push(`argument ${argName} has no @param for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`);
}
}
const reqs = ["@dev", "@notice"];
for (let i3 = 0; i3 < reqs.length; i3++) {
if (!func.comments.some((comment) => comment.startsWith(reqs[i3]))) {
errors.push(`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`);
}
}
}
return errors;
}
catch (error) {
return Promise.reject(error);
}
};
const checkPublicFuncs = async (connector) => {
try {
const errors = [];
for (let i1 = 0; i1 < connector.allPublicFuncs.length; i1++) {
const { raw, firstLine, name } = connector.allPublicFuncs[i1];
if (!raw.includes("payable")) {
errors.push(`public function ${name} is not payable at ${connector.path}/main.sol:${firstLine}`);
}
}
return errors;
}
catch (error) {
return Promise.reject(error);
}
};
const checkName = async (connector) => {
try {
const strs = connector.code.split("\n");
let haveName = false;
for (let index = strs.length - 1; index > 0; index--) {
const str = strs[index];
if (str.includes("string") &&
str.includes("public") &&
str.includes("name = ")) {
haveName = true;
}
}
return haveName
? []
: [`name variable missing in ${connector.path}/main.sol`];
}
catch (error) {
return Promise.reject(error);
}
};
const checkHeadComments = async (connector) => {
try {
const errors = [];
const strs = connector.code.split("\n");
let haveTitle = false;
let haveDev = false;
for (let index = 0; index < strs.length; index++) {
if (!strs[index].includes("{")) {
if (strs[index].includes("@title"))
haveTitle = true;
if (strs[index].includes("@dev"))
haveDev = true;
}
else {
break;
}
}
if (!haveTitle)
errors.push(`@title missing in ${connector.path}/main.sol`);
if (!haveDev)
errors.push(`@dev missing in ${connector.path}/main.sol`);
return errors;
}
catch (error) {
return Promise.reject(error);
}
};
async function checkMain() {
try {
const connectorsRootsDirsDefault = ["mainnet", "polygon"].map((v) => `contracts/${v}/connectors`);
const customPathArg = process.argv.find((a) => a.startsWith("connector="));
const connectorsRootsDirs = customPathArg
? [customPathArg.slice(10)]
: connectorsRootsDirsDefault;
const errors = [];
const warnings = [];
const connectors = await getConnectorsList(connectorsRootsDirs);
for (let index = 0; index < connectors.length; index++) {
const { forbiddenErrors, code } = await checkForbidden(connectors[index].path);
connectors[index].code = code;
connectors[index] = await parseCode(connectors[index]);
const { eventsErrors, eventsWarnings } = await checkEvents(connectors[index]);
const commentsErrors = await checkComments(connectors[index]);
const nameErrors = await checkName(connectors[index]);
const headCommentsErrors = await checkHeadComments(connectors[index]);
const publicFuncsErrors = await checkPublicFuncs(connectors[index]);
errors.push(...forbiddenErrors);
errors.push(...eventsErrors);
errors.push(...commentsErrors);
errors.push(...nameErrors);
errors.push(...headCommentsErrors);
errors.push(...publicFuncsErrors);
warnings.push(...eventsWarnings);
}
if (errors.length) {
console.log("\x1b[31m%s\x1b[0m", `Total errors: ${errors.length}`);
errors.forEach((error) => console.log("\x1b[31m%s\x1b[0m", error));
}
else {
console.log("\x1b[32m%s\x1b[0m", "No Errors Found");
}
if (warnings.length) {
console.log("\x1b[33m%s\x1b[0m", `Total warnings: ${warnings.length}`);
warnings.forEach((warning) => console.log("\x1b[33m%s\x1b[0m", warning));
}
else {
console.log("\x1b[32m%s\x1b[0m", "No Warnings Found");
}
if (errors.length)
return Promise.reject(errors.join("\n"));
}
catch (error) {
console.error("check execution error:", error);
}
}
exports.default = checkMain;
//# sourceMappingURL=check.js.map

File diff suppressed because one or more lines are too long

21
status-checks/checks.js Normal file
View File

@ -0,0 +1,21 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const check_1 = __importDefault(require("./check"));
exports.default = [
{
name: "Solidity check",
callback: async () => {
try {
await (0, check_1.default)();
return "Check passed!";
}
catch (error) {
throw new Error("Check failed!");
}
},
},
];
//# sourceMappingURL=checks.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"checks.js","sourceRoot":"","sources":["checks.ts"],"names":[],"mappings":";;;;;AAAA,oDAAgC;AAEhC,kBAAe;IACb;QACE,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,KAAK,IAAI,EAAE;YACnB,IAAI;gBACF,MAAM,IAAA,eAAS,GAAE,CAAC;gBAClB,OAAO,eAAe,CAAC;aACxB;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;aAClC;QACH,CAAC;KACF;CACF,CAAC"}

View File

@ -0,0 +1,15 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const check_1 = __importDefault(require("./check"));
(async function runHusky() {
try {
await (0, check_1.default)();
}
catch (error) {
process.exit(1);
}
})();
//# sourceMappingURL=huskyCheck.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"huskyCheck.js","sourceRoot":"","sources":["huskyCheck.ts"],"names":[],"mappings":";;;;;AAAA,oDAAgC;AAEhC,CAAC,KAAK,UAAU,QAAQ;IACtB,IAAI;QACF,MAAM,IAAA,eAAS,GAAE,CAAC;KACnB;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC,CAAC,EAAE,CAAC"}

69
status-checks/index.js Normal file
View File

@ -0,0 +1,69 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cp = __importStar(require("child_process"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const checks_1 = __importDefault(require("./checks"));
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
function getCurrentCommitSha() {
return cp
.execSync("git rev-parse HEAD")
.toString()
.trim();
}
// The SHA provied by GITHUB_SHA is the merge (PR) commit.
// We need to get the current commit sha ourself.
const sha = getCurrentCommitSha();
async function setStatus(context, state, description) {
return (0, node_fetch_1.default)(`https://api.github.com/repos/${owner}/${repo}/statuses/${sha}`, {
method: "POST",
body: JSON.stringify({
state,
description,
context,
}),
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
});
}
(async () => {
console.log(`Starting status checks for commit ${sha}`);
// Run in parallel
await Promise.all(checks_1.default.map(async (check) => {
const { name, callback } = check;
await setStatus(name, "pending", "Running check..");
try {
const response = await callback();
await setStatus(name, "success", response);
}
catch (err) {
const message = err ? err.message : "Something went wrong";
await setStatus(name, "failure", message);
}
}));
console.log("Finished status checks");
})();
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAAoC;AACpC,4DAA+B;AAE/B,sDAA8B;AAE9B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAE/D,SAAS,mBAAmB;IAC1B,OAAO,EAAE;SACN,QAAQ,CAAC,oBAAoB,CAAC;SAC9B,QAAQ,EAAE;SACV,IAAI,EAAE,CAAC;AACZ,CAAC;AACD,0DAA0D;AAC1D,iDAAiD;AACjD,MAAM,GAAG,GAAG,mBAAmB,EAAE,CAAC;AAElC,KAAK,UAAU,SAAS,CAAC,OAAY,EAAE,KAAa,EAAE,WAAmB;IACvE,OAAO,IAAA,oBAAK,EACV,gCAAgC,KAAK,IAAI,IAAI,aAAa,GAAG,EAAE,EAC/D;QACE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK;YACL,WAAW;YACX,OAAO;SACR,CAAC;QACF,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE;YACnD,cAAc,EAAE,kBAAkB;SACnC;KACF,CACF,CAAC;AACJ,CAAC;AAED,CAAC,KAAK,IAAI,EAAE;IACV,OAAO,CAAC,GAAG,CAAC,qCAAqC,GAAG,EAAE,CAAC,CAAC;IACxD,kBAAkB;IAClB,MAAM,OAAO,CAAC,GAAG,CACf,gBAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAmC,EAAE,EAAE;QACvD,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;QAEjC,MAAM,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAEpD,IAAI;YACF,MAAM,QAAQ,GAAG,MAAM,QAAQ,EAAE,CAAC;YAClC,MAAM,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;SAC5C;QAAC,OAAO,GAAG,EAAE;YACZ,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;YAC3D,MAAM,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;SAC3C;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC,CAAC,EAAE,CAAC"}