mirror of
				https://github.com/Instadapp/dsa-connectors.git
				synced 2024-07-29 22:37:00 +00:00 
			
		
		
		
	 4800889442
			
		
	
	
		4800889442
		
			
		
	
	
	
	
		
			
			* fixed checks * updated sushi incentive comments * fixed index.ts errors * removed yarn.lock since it is not used anywhere, updated gh actions config and package-lock.json Co-authored-by: Ishan Jain <contact@ishanjain.me> Co-authored-by: Prayuman Verma <pradyumnverma27@gmail.com>
		
			
				
	
	
		
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import * as fs from "fs";
 | |
| import * as path from "path";
 | |
| 
 | |
| const forbiddenStrings: any = ["selfdestruct"];
 | |
| 
 | |
| const getConnectorsList= async (connectorsRootsDirs: string | any[]): Promise<Record<string, any>>  => {
 | |
|   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: string, codePath: string) => {
 | |
|   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: string, 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: any = 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: {
 | |
|   path: any;
 | |
|   events?: any;
 | |
|   mainEvents?: any;
 | |
|   mainEventsLines?: any;
 | |
| }) => {
 | |
|   try {
 | |
|     const errors = [];
 | |
|     const warnings = [];
 | |
|     const eventsPath = `${connector.path}/events.sol`;
 | |
|     const mainPath = `${connector.path}/main.sol`;
 | |
|     if (connector.events.length) {
 | |
|       const eventNames:string[] = [];
 | |
|       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: string) => e.split("(")[0].split(" ")[1] === name
 | |
|         );
 | |
|         if (event) {
 | |
|           const mainEventArgs = mainEvent
 | |
|             .split("(")[1]
 | |
|             .split(")")[0]
 | |
|             .split(",")
 | |
|             .map((a: string) => a.trim());
 | |
|           const eventArgs = event
 | |
|             .split("(")[1]
 | |
|             .split(")")[0]
 | |
|             .split(",")
 | |
|             .map((a: string) => 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: string) => {
 | |
|           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: string) => 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: string | any[]) => {
 | |
|   try {
 | |
|     const comments = [];
 | |
|     let type: string = '';
 | |
|     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: { path: any; code?: any }) => {
 | |
|   try {
 | |
|     const strs = connector.code.split("\n");
 | |
|     const events = [];
 | |
|     const eventsFirstLines = [];
 | |
|     let func = [];
 | |
|     let funcs = [];
 | |
|     let event: string[] = [];
 | |
|     let mainEvents: string[] = [];
 | |
|     let firstLine: number = -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: string | any[]) => 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:any) => {
 | |
|   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: string) =>
 | |
|               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: string) => 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: {
 | |
|   path: any;
 | |
|   allPublicFuncs?: any;
 | |
| }) => {
 | |
|   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: { path: any; code?: any }) => {
 | |
|   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: { path: any; code?: any }) => {
 | |
|   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);
 | |
|   }
 | |
| }
 | |
| export default checkMain;
 |