From 27abadd8403bb968e69cbe05a08fe85a24acc1d5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 22 Feb 2024 11:57:42 +0900 Subject: [PATCH 1/6] feat: show unclaimed permits --- static/scripts/audit-report/audit.ts | 3 ++- static/scripts/audit-report/helpers.ts | 10 ++++++---- static/styles/audit-report/audit.css | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index ed9f862a..67a259fc 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -15,6 +15,7 @@ import { primaryRateLimitHandler, RateLimitOptions, secondaryRateLimitHandler, + TX_EMPTY_VALUE, } from "./helpers"; import { ChainScanResult, @@ -306,7 +307,7 @@ class SmartQueue { if (git?.issue_number) { elemList.push({ id: git.issue_number, - tx: ether?.txHash || "N/A", // @TODO - handle this better + tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better amount: ethers.utils.formatEther(amount), title: git.issue_title, bounty_hunter: git.bounty_hunter, diff --git a/static/scripts/audit-report/helpers.ts b/static/scripts/audit-report/helpers.ts index 38c620b9..6cb099a5 100644 --- a/static/scripts/audit-report/helpers.ts +++ b/static/scripts/audit-report/helpers.ts @@ -31,8 +31,10 @@ const RPC_HEADER = { "Content-Type": "application/json", }; +export const TX_EMPTY_VALUE = "N/A"; + export function shortenTransactionHash(hash: string | undefined, length = 8): string { - if (!hash) return ""; + if (!hash || hash === TX_EMPTY_VALUE) return "UNCLAIMED"; const prefixLength = Math.floor(length / 2); const suffixLength = length - prefixLength; @@ -52,18 +54,18 @@ export function populateTable( amount: string, bountyHunter: BountyHunter ) { - if (!txHash) return; // permit not claimed const issueUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`; const txUrl = `https://${getChainScan(network)}/tx/${txHash}`; + const disableLinkStyle = txHash === TX_EMPTY_VALUE ? 'disabled tabIndex="-1"' : ""; const rows = ` ${owner}/${repo} #${issueNumber} - ${issueTitle} ${bountyHunter?.name} - ${ethers.BigNumber.isBigNumber(amount) ? ethers.utils.formatEther(amount) : amount} ${ + ${ethers.BigNumber.isBigNumber(amount) ? ethers.utils.formatEther(amount) : amount} ${ network === Chain.Ethereum ? "DAI" : "WXDAI" } - ${shortenTransactionHash(txHash)} + ${shortenTransactionHash(txHash)} `; resultTableTbodyElem.insertAdjacentHTML("beforeend", rows); diff --git a/static/styles/audit-report/audit.css b/static/styles/audit-report/audit.css index ef039e13..e1088cef 100644 --- a/static/styles/audit-report/audit.css +++ b/static/styles/audit-report/audit.css @@ -95,6 +95,10 @@ a:hover { color: black; } +a[disabled] { + pointer-events: none; +} + #searchInput { margin-bottom: 20px; width: 100%; From 788b8a0d00974a74464fc827cbceba4dda0876ec Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 22 Feb 2024 22:01:11 +0900 Subject: [PATCH 2/6] chore: changed logic to add the items after all operations are complete --- static/scripts/audit-report/audit.ts | 66 +++++++++++++++----------- static/scripts/audit-report/helpers.ts | 2 +- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index 67a259fc..4f576cc6 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -298,35 +298,18 @@ class SmartQueue { if (this._queue.has(key)) { const queueValue = this._queue.get(key) as StandardInterface; queueValue.s[value.t] = value.s[value.t] as object extends GitInterface ? GitInterface : object extends EtherInterface ? EtherInterface : never; - const { - s: { ether, git, network }, - c: { amount }, - } = queueValue; - - // check for undefined - if (git?.issue_number) { - elemList.push({ - id: git.issue_number, - tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better - amount: ethers.utils.formatEther(amount), - title: git.issue_title, - bounty_hunter: git.bounty_hunter, - owner: git.owner, - repo: git.repo, - network, - }); - if (elemList.length > 0) { - resultTableTbodyElem.innerHTML = ""; - for (const data of elemList) { - populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); - } - } - } - this._queue.delete(key); } else { this._queue.set(key, value); } } + + get() { + return this._queue.values() as Readonly>; + } + + clear() { + this._queue.clear(); + } } type QueueItem = ChainScanResult; type Queue = QueueItem extends string ? string[] : QueueItem[]; @@ -636,6 +619,7 @@ async function resetInit() { lastEtherHash = false; repoArray.splice(0, repoArray.length); finishedQueue.clearQueue(); + updateQueue.clear(); } async function asyncInit() { @@ -644,9 +628,35 @@ async function asyncInit() { } function tabInit(repoUrls: GitHubUrlParts[]) { - etherFetcher().catch((error) => console.error(error)); - gitFetcher(repoUrls).catch((error) => console.error(error)); - rpcFetcher().catch((error) => console.error(error)); + Promise.all([etherFetcher(), gitFetcher(repoUrls), rpcFetcher()]) + .then(() => { + for (const item of updateQueue.get()) { + const { + s: { ether, git, network }, + c: { amount }, + } = item; + // check for undefined + if (git?.issue_number) { + elemList.push({ + id: git.issue_number, + tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better + amount: ethers.utils.formatEther(amount), + title: git.issue_title, + bounty_hunter: git.bounty_hunter, + owner: git.owner, + repo: git.repo, + network, + }); + } + } + if (elemList.length > 0) { + resultTableTbodyElem.innerHTML = ""; + for (const data of elemList) { + populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); + } + } + }) + .catch((error) => console.error(error)); } function auditInit() { diff --git a/static/scripts/audit-report/helpers.ts b/static/scripts/audit-report/helpers.ts index 6cb099a5..a5d9c07d 100644 --- a/static/scripts/audit-report/helpers.ts +++ b/static/scripts/audit-report/helpers.ts @@ -34,7 +34,7 @@ const RPC_HEADER = { export const TX_EMPTY_VALUE = "N/A"; export function shortenTransactionHash(hash: string | undefined, length = 8): string { - if (!hash || hash === TX_EMPTY_VALUE) return "UNCLAIMED"; + if (!hash || hash === TX_EMPTY_VALUE) return ""; const prefixLength = Math.floor(length / 2); const suffixLength = length - prefixLength; From 0d83cf0fe34b2f5752ce932b3c87247221b1349b Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 23 Feb 2024 15:19:20 +0900 Subject: [PATCH 3/6] chore: moved the push to ui logic to the same spot as the loading button --- static/scripts/audit-report/audit.ts | 57 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index 4f576cc6..87880fe8 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -259,6 +259,31 @@ class QueueObserver { private _callback() { toggleLoader("none"); + for (const item of updateQueue.get()) { + const { + s: { ether, git, network }, + c: { amount }, + } = item; + // check for undefined + if (git?.issue_number) { + elemList.push({ + id: git.issue_number, + tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better + amount: ethers.utils.formatEther(amount), + title: git.issue_title, + bounty_hunter: git.bounty_hunter, + owner: git.owner, + repo: git.repo, + network, + }); + } + } + if (elemList.length > 0) { + resultTableTbodyElem.innerHTML = ""; + for (const data of elemList) { + populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); + } + } if (!this._isException) { this._databaseCallback(); } @@ -628,35 +653,9 @@ async function asyncInit() { } function tabInit(repoUrls: GitHubUrlParts[]) { - Promise.all([etherFetcher(), gitFetcher(repoUrls), rpcFetcher()]) - .then(() => { - for (const item of updateQueue.get()) { - const { - s: { ether, git, network }, - c: { amount }, - } = item; - // check for undefined - if (git?.issue_number) { - elemList.push({ - id: git.issue_number, - tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better - amount: ethers.utils.formatEther(amount), - title: git.issue_title, - bounty_hunter: git.bounty_hunter, - owner: git.owner, - repo: git.repo, - network, - }); - } - } - if (elemList.length > 0) { - resultTableTbodyElem.innerHTML = ""; - for (const data of elemList) { - populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); - } - } - }) - .catch((error) => console.error(error)); + etherFetcher().catch((error) => console.error(error)); + gitFetcher(repoUrls).catch((error) => console.error(error)); + rpcFetcher().catch((error) => console.error(error)); } function auditInit() { From ec6c3a6be21165778b4e9b89913f39e9a60b5662 Mon Sep 17 00:00:00 2001 From: Fernando Date: Sun, 25 Feb 2024 14:05:48 +0900 Subject: [PATCH 4/6] feat!: removed calls to blockchains and made the db sole source of truth --- static/audit.html | 10 - static/scripts/audit-report/audit.ts | 562 ++++----------------------- 2 files changed, 74 insertions(+), 498 deletions(-) diff --git a/static/audit.html b/static/audit.html index bf505dc1..95f25ef9 100644 --- a/static/audit.html +++ b/static/audit.html @@ -11,10 +11,6 @@

Audit report

-
- - -
@@ -31,7 +27,6 @@

Audit report

id="quickName" placeholder=' { - "WALLET": "", "REPO": "", "PAT": "" }' @@ -42,11 +37,6 @@

Audit report

Get report -
- - -
⚡Cache
-
diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index 87880fe8..fea0cb38 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -1,36 +1,18 @@ import { throttling } from "@octokit/plugin-throttling"; import { Octokit } from "@octokit/rest"; import { createClient } from "@supabase/supabase-js"; -import axios from "axios"; import { ethers } from "ethers"; -import GoDB from "godb"; -import { permit2Abi } from "../rewards/abis"; -import { Chain, ChainScan, DATABASE_NAME, NULL_HASH, NULL_ID } from "./constants"; +import { Chain } from "./constants"; import { getCurrency, getGitHubUrlPartsArray, - getOptimalRPC, - getRandomAPIKey, populateTable, primaryRateLimitHandler, RateLimitOptions, secondaryRateLimitHandler, TX_EMPTY_VALUE, } from "./helpers"; -import { - ChainScanResult, - ElemInterface, - EtherInterface, - GitHubUrlParts, - GitInterface, - GoDBSchema, - ObserverKeys, - QuickImport, - SavedData, - StandardInterface, - TxData, -} from "./types"; -import { getTxInfo } from "./utils/getTransaction"; +import { ElemInterface, EtherInterface, GitHubUrlParts, GitInterface, QuickImport, SavedData, StandardInterface } from "./types"; declare const SUPABASE_URL: string; declare const SUPABASE_ANON_KEY: string; @@ -41,7 +23,6 @@ const rateOctokit = Octokit.plugin(throttling); let octokit: Octokit; -let BOT_WALLET_ADDRESS = ""; let REPOSITORY_URL = ""; let GITHUB_PAT = ""; @@ -51,9 +32,6 @@ const resultTableElem = document.querySelector("#resultTable") as HTMLElement; const resultTableTbodyElem = document.querySelector("#resultTable tbody") as HTMLTableCellElement; const getReportElem = document.querySelector("#getReport") as HTMLButtonElement; const reportLoader = document.querySelector("#report-loader") as HTMLElement; -const tgBtnInput = document.querySelector("#cb4") as HTMLInputElement; - -let isCache = true; // TODO: should be generated directly from the Supabase db schema interface Permit { @@ -111,207 +89,18 @@ interface Permit { const permitList: Permit[] = []; -let isGit = true; -const offset = 100; - -let isEther = true; -const ETHER_INTERVAL = 250; -let etherPageNumber = 1; - -let isRPC = true; -const RPC_INTERVAL = 50; -const permit2Interface = new ethers.utils.Interface(permit2Abi); -const permitTransferFromSelector = "0x30f28b7a"; -const permitFunctionName = "permitTransferFrom"; const elemList: ElemInterface[] = []; -let gitID = NULL_ID; -let etherHash = NULL_HASH; - -let lastGitID: number | boolean = false; -let lastEtherHash: string | boolean = false; - -function getDataSchema(storeHash: string) { - const schema: GoDBSchema = { - [NULL_HASH]: { - id: { - type: String, - unique: true, - }, - hash: { - type: String, - unique: false, - }, - issue: { - type: Number, - unique: false, - }, - }, - [storeHash]: { - id: { - type: Number, - unique: true, - }, - tx: { - type: String, - unique: false, - }, - amount: { - type: String, - unique: false, - }, - title: { - type: String, - unique: false, - }, - }, - }; - - return schema; -} - function parseAndAddUrls(input: string): void { const urls = input.split(",").map((url) => url.trim()); repoArray.push(...urls); } -async function updateDB(storeHash: string) { - const schema = getDataSchema(storeHash); - const cacheDB = new GoDB(DATABASE_NAME, schema); - const metaTable = cacheDB.table(NULL_HASH); - const storeTable = cacheDB.table(storeHash); - - const metaData = { - // unknown as number because the only time it receives a string is initiating the db - // and it is always a number after that according to the schema definition - // [NULL_HASH]: id: storeHash - // [STORE_HASH]: id: storeHash - id: storeHash as unknown as number, - hash: lastEtherHash !== etherHash ? (lastEtherHash as string) : (etherHash as string), - issue: lastGitID !== gitID ? (lastGitID as number) : (gitID as number), - }; - - await metaTable.put(metaData); - if (elemList.length > 0) { - for (const elem of elemList) { - const { id, tx, amount, title, bounty_hunter, network, owner, repo } = elem; - await storeTable.put({ - id, - tx, - amount, - title, - bounty_hunter, - network, - owner, - repo, - }); - } - } - return cacheDB.close(); -} - -async function readDB(storeHash: string) { - const schema = getDataSchema(storeHash); - const cacheDB = new GoDB(DATABASE_NAME, schema); - const storeTable = cacheDB.table(storeHash); - const tableData = await storeTable.getAll(); - cacheDB.close(); - return tableData; -} - -async function readMeta(storeHash: string) { - const schema = getDataSchema(storeHash); - const cacheDB = new GoDB(DATABASE_NAME, schema); - const metaTable = cacheDB.table(NULL_HASH); - const metaData = await metaTable.get({ id: storeHash }); - cacheDB.close(); - return metaData; -} - function toggleLoader(type: "none" | "block") { - getReportElem.disabled = type === "block" ? true : false; + getReportElem.disabled = type === "block"; reportLoader.style.display = type; } -class QueueObserver { - private readonly _queueObject: { - isRPC: boolean; - isComment: boolean; - isGit: boolean; - isEther: boolean; - }; - private _isException; - - constructor() { - this._queueObject = { - isRPC: false, - isComment: false, - isGit: false, - isEther: false, - }; - this._isException = false; - } - - private _databaseCallback() { - const storeHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${REPOSITORY_URL}_${BOT_WALLET_ADDRESS}`)); - updateDB(storeHash).catch((error) => console.error(error)); - } - - private _callback() { - toggleLoader("none"); - for (const item of updateQueue.get()) { - const { - s: { ether, git, network }, - c: { amount }, - } = item; - // check for undefined - if (git?.issue_number) { - elemList.push({ - id: git.issue_number, - tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better - amount: ethers.utils.formatEther(amount), - title: git.issue_title, - bounty_hunter: git.bounty_hunter, - owner: git.owner, - repo: git.repo, - network, - }); - } - } - if (elemList.length > 0) { - resultTableTbodyElem.innerHTML = ""; - for (const data of elemList) { - populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); - } - } - if (!this._isException) { - this._databaseCallback(); - } - } - - mutate(key: ObserverKeys, value: boolean) { - this._queueObject[key] = value; - const { isRPC, isComment, isGit, isEther } = this._queueObject; - const isUpdateFinished = isRPC && isComment && isGit && isEther; - if (isUpdateFinished) { - this._callback(); - } - } - - clearQueue() { - this._queueObject.isComment = false; - this._queueObject.isEther = false; - this._queueObject.isGit = false; - this._queueObject.isRPC = false; - } - - raise() { - this._isException = true; - } -} - -const finishedQueue = new QueueObserver(); - class SmartQueue { private readonly _queue: Map; @@ -326,6 +115,30 @@ class SmartQueue { } else { this._queue.set(key, value); } + const { + s: { ether, git, network }, + c: { amount }, + } = this._queue.get(key) as StandardInterface; + // check for undefined + if (git?.issue_number) { + elemList.push({ + id: git.issue_number, + tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better + amount: ethers.utils.formatEther(amount), + title: git.issue_title, + bounty_hunter: git.bounty_hunter, + owner: git.owner, + repo: git.repo, + network, + }); + } + + if (elemList.length > 0) { + resultTableTbodyElem.innerHTML = ""; + for (const data of elemList) { + populateTable(data?.owner, data?.repo, data?.id, data?.network, data?.tx, data?.title, data?.amount, data?.bounty_hunter); + } + } } get() { @@ -336,41 +149,8 @@ class SmartQueue { this._queue.clear(); } } -type QueueItem = ChainScanResult; -type Queue = QueueItem extends string ? string[] : QueueItem[]; - -class QueueSet { - private readonly _queue: Queue; - private readonly _set: Set>; - - constructor() { - this._queue = []; - this._set = new Set(); - } - - add(item: NonNullable) { - if (!this._set.has(item)) { - this._set.add(item); - this._queue.push(item as QueueItem); - } - } - - remove() { - const v = this._queue.shift(); - if (v) this._set.delete(v); - } - - read(): ChainScanResult { - return this._queue[0]; - } - - isEmpty() { - return this._queue.length === 0; - } -} const updateQueue = new SmartQueue(); -const rpcQueue = new QueueSet(); async function getPermitsForRepo(owner: string, repo: string) { const permitList: Permit[] = []; @@ -395,285 +175,91 @@ async function getPermitsForRepo(owner: string, repo: string) { return permitList; } -async function gitFetcher(repoUrls: GitHubUrlParts[]) { - if (isGit) { - try { - const permitsPromises = repoUrls.map((repoUrl) => getPermitsForRepo(repoUrl.owner, repoUrl.repo)); - const allPermits = await Promise.all(permitsPromises); - - for (let i = 0; i < allPermits.length; i++) { - const issues = allPermits[i]; - permitList.push(...issues); - console.log(`Fetched ${issues.length} permits for repository ${repoUrls[i].owner}/${repoUrls[i].repo}`); - } - isGit = false; - finishedQueue.mutate("isGit", true); - for (const permit of permitList) { - const { data: userData } = await octokit.request("GET /user/:id", { id: permit.locations.user_id }); - const { data } = await supabase.from("locations").select("*").eq("issue_id", permit.locations.issue_id).single(); - const lastSlashIndex = data.node_url.lastIndexOf("/"); - const hashIndex = data.node_url.lastIndexOf("#") || data.node_url.length; - const issueNumber = Number(data.node_url.substring(lastSlashIndex + 1, hashIndex)); - const { data: issueData } = await octokit.rest.issues.get({ - issue_number: issueNumber, - owner: permit.owner, - repo: permit.repo, - }); - updateQueue.add(permit.signature, { - c: { - amount: permit.amount, - deadline: permit.deadline, - nonce: permit.nonce, - owner: permit.owner, - signature: permit.signature, - to: permit.users.wallets.address, - token: permit.tokens.address, - }, - k: permit.signature, - s: { - ether: undefined, - git: { - bounty_hunter: { - name: userData.login, - url: userData.html_url, - }, - issue_number: issueData.number, - issue_title: issueData.title, - owner: permit.owner, - repo: permit.repo, - }, - network: getCurrency(permit.network_id) || Chain.Ethereum, - }, - t: "git", - }); - } - finishedQueue.mutate("isComment", true); - } catch (error) { - console.error(`Error fetching issues: ${error}`); - finishedQueue.mutate("isComment", true); - } - - return permitList; - } -} - -async function fetchDataFromChainScanAPI(url: string, chain: string) { +async function fetchPermits(repoUrls: GitHubUrlParts[]) { try { - const { data } = await axios.get(url); - return data.result.map((item: NonNullable) => ({ ...item, chain })); - } catch (error: unknown) { - console.error(error); - throw error; - } -} - -async function etherFetcher() { - const ethereumURL = `https://api.${ChainScan.Ethereum}/api?module=account&action=tokentx&address=${BOT_WALLET_ADDRESS}&apikey=${getRandomAPIKey( - Chain.Ethereum - )}&page=${etherPageNumber}&offset=${offset}&sort=desc`; - - const gnosisURL = `https://api.${ChainScan.Gnosis}/api?module=account&action=tokentx&address=${BOT_WALLET_ADDRESS}&apikey=${getRandomAPIKey( - Chain.Gnosis - )}&page=${etherPageNumber}&offset=${offset}&sort=desc`; - - if (isEther) { - const etherIntervalID = setInterval(async () => { - clearInterval(etherIntervalID); - try { - const [ethereumData, gnosisData] = await Promise.all([ - fetchDataFromChainScanAPI(ethereumURL, Chain.Ethereum), - fetchDataFromChainScanAPI(gnosisURL, Chain.Gnosis), - ]); - - const combinedData: ChainScanResult[] = [...ethereumData, ...gnosisData]; - await handleCombinedData(combinedData); - } catch (error: unknown) { - console.error(error); - finishedQueue.raise(); - isEther = false; - finishedQueue.mutate("isEther", true); - } - }, ETHER_INTERVAL); - } -} + const permitsPromises = repoUrls.map((repoUrl) => getPermitsForRepo(repoUrl.owner, repoUrl.repo)); + const allPermits = await Promise.all(permitsPromises); -async function handleCombinedData(combinedData: ChainScanResult[]) { - if (combinedData.length > 0) { - if (!lastEtherHash) { - lastEtherHash = combinedData[0].hash; - } - let isIEF = true; - for (const e of combinedData) { - if (e.hash !== etherHash) { - rpcQueue.add({ hash: e.hash, chain: e.chain }); - } else { - isIEF = false; - break; - } + for (let i = 0; i < allPermits.length; i++) { + const issues = allPermits[i]; + permitList.push(...issues); + console.log(`Fetched ${issues.length} permits for repository ${repoUrls[i].owner}/${repoUrls[i].repo}`); } - - if (isIEF) { - etherPageNumber++; - await etherFetcher(); - } else { - isEther = false; - finishedQueue.mutate("isEther", true); - } - } else { - isEther = false; - finishedQueue.mutate("isEther", true); - } -} - -async function rpcFetcher() { - if (isRPC) { - const rpcIntervalID = setInterval(async () => { - clearInterval(rpcIntervalID); - try { - const data = rpcQueue.read(); - await handleRPCData(data); - rpcQueue.remove(); - if (isEther || !rpcQueue.isEmpty()) { - await rpcFetcher(); - } else { - isRPC = false; - finishedQueue.mutate("isRPC", true); - } - } catch (error: unknown) { - console.error(error); - finishedQueue.raise(); - rpcQueue.remove(); - if (isEther || !rpcQueue.isEmpty()) { - await rpcFetcher(); - } else { - isRPC = false; - finishedQueue.mutate("isRPC", true); - } - } - }, RPC_INTERVAL); - } -} - -async function handleRPCData(data: ChainScanResult) { - if (data) { - const { hash, chain } = data as { hash: string; chain: string }; - const providerUrl = await getOptimalRPC(chain as Chain); - const txInfo = await getTxInfo(hash, providerUrl, chain as Chain); - - if (txInfo.input.startsWith(permitTransferFromSelector)) { - const decodedFunctionData = permit2Interface.decodeFunctionData(permitFunctionName, txInfo.input); - const { - permit: { - permitted: { token, amount }, - nonce, - deadline, - }, - transferDetails: { to }, - owner, - signature, - } = decodedFunctionData as unknown as TxData; - updateQueue.add(signature, { - k: signature, - t: "ether", + for (const permit of permitList) { + const { data: userData } = await octokit.request("GET /user/:id", { id: permit.locations.user_id }); + const { node_url } = permit.locations; + const lastSlashIndex = node_url.lastIndexOf("/"); + const hashIndex = node_url.lastIndexOf("#") || node_url.length; + const issueNumber = Number(node_url.substring(lastSlashIndex + 1, hashIndex)); + const { data: issueData } = await octokit.rest.issues.get({ + issue_number: issueNumber, + owner: permit.owner, + repo: permit.repo, + }); + updateQueue.add(permit.signature, { c: { - nonce, - owner, - token, - amount, - to, - deadline, - signature, + amount: permit.amount, + deadline: permit.deadline, + nonce: permit.nonce, + owner: permit.owner, + signature: permit.signature, + to: permit.users.wallets.address, + token: permit.tokens.address, }, + k: permit.signature, s: { - ether: { - txHash: txInfo.hash, - timestamp: parseInt(txInfo.timestamp, 16), - block_number: parseInt(txInfo.blockNumber, 16), + ether: undefined, + git: { + bounty_hunter: { + name: userData.login, + url: userData.html_url, + }, + issue_number: issueData.number, + issue_title: issueData.title, + owner: permit.owner, + repo: permit.repo, }, - git: undefined, - network: chain as string, + network: getCurrency(permit.network_id) || Chain.Ethereum, }, + t: "git", }); } + } catch (error) { + console.error(`Error fetching issues: ${error}`); } -} -async function dbInit() { - if (isCache) { - const storeHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${REPOSITORY_URL}_${BOT_WALLET_ADDRESS}`)); - const metaData = await readMeta(storeHash); - - if (metaData !== undefined) { - const { hash, issue } = metaData; - gitID = issue as number; - etherHash = hash as string; - - const tableData = await readDB(storeHash); - - if (tableData.length > 0) { - for (const data of tableData) { - const { owner, repo, id, network, tx, bounty_hunter, amount, title } = data as unknown as SavedData; - populateTable(owner, repo, id, network, tx, title, amount, bounty_hunter); - // for filtering - elemList.push({ - id, - tx, - amount, - title, - bounty_hunter, - owner, - repo, - network, - }); - } - } - } - } + return permitList; } async function resetInit() { permitList.splice(0, permitList.length); - isGit = true; - isEther = true; - etherPageNumber = 1; - isRPC = true; elemList.splice(0, elemList.length); - gitID = NULL_ID; - etherHash = NULL_HASH; - lastGitID = false; - lastEtherHash = false; repoArray.splice(0, repoArray.length); - finishedQueue.clearQueue(); updateQueue.clear(); } async function asyncInit() { await resetInit(); - await dbInit(); } function tabInit(repoUrls: GitHubUrlParts[]) { - etherFetcher().catch((error) => console.error(error)); - gitFetcher(repoUrls).catch((error) => console.error(error)); - rpcFetcher().catch((error) => console.error(error)); + fetchPermits(repoUrls) + .finally(() => toggleLoader("none")) + .catch((error) => console.error(error)); } function auditInit() { - tgBtnInput.checked = true; getReportElem.addEventListener("click", async () => { - isCache = tgBtnInput.checked; toggleLoader("block"); resultTableElem.style.display = "table"; resultTableTbodyElem.innerHTML = ""; const quickImportValue = (document.querySelector("#quickName") as HTMLTextAreaElement).value; if (quickImportValue !== "") { - const { WALLET, REPO, PAT }: QuickImport = JSON.parse(quickImportValue); - BOT_WALLET_ADDRESS = WALLET.toLocaleLowerCase(); + const { REPO, PAT }: QuickImport = JSON.parse(quickImportValue); REPOSITORY_URL = REPO.toLocaleLowerCase(); GITHUB_PAT = PAT; parseAndAddUrls(REPOSITORY_URL); } else { - BOT_WALLET_ADDRESS = (document.querySelector("#botWalletAddress") as HTMLInputElement).value.toLocaleLowerCase(); REPOSITORY_URL = (document.querySelector("#repoURLs") as HTMLInputElement).value.toLocaleLowerCase(); GITHUB_PAT = (document.querySelector("#githubPAT") as HTMLInputElement).value; parseAndAddUrls(REPOSITORY_URL); @@ -681,7 +267,7 @@ function auditInit() { const REPOS = getGitHubUrlPartsArray(repoArray); - if (BOT_WALLET_ADDRESS !== "" && REPOSITORY_URL !== "" && GITHUB_PAT !== "" && REPOS.length > 0) { + if (REPOSITORY_URL !== "" && GITHUB_PAT !== "" && REPOS.length > 0) { await asyncInit(); octokit = new rateOctokit({ auth: GITHUB_PAT, From 5a177f196936d4938541407b021e896b817cfa06 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 29 Feb 2024 08:29:42 +0900 Subject: [PATCH 5/6] chore: changed permit fetch to issue fetch and permit link --- static/scripts/audit-report/audit.ts | 185 +++++++++++-------- static/scripts/audit-report/types/audit.d.ts | 2 +- 2 files changed, 106 insertions(+), 81 deletions(-) diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index fea0cb38..fb0e190e 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -34,60 +34,71 @@ const getReportElem = document.querySelector("#getReport") as HTMLButtonElement; const reportLoader = document.querySelector("#report-loader") as HTMLElement; // TODO: should be generated directly from the Supabase db schema -interface Permit { +interface Issue { id: number; - created: Date; - updated: Date; - amount: string; - nonce: string; - deadline: string; - signature: string; - token_id: number; - partner_id: null | number; - beneficiary_id: number; - transaction: string; - location_id: number; - locations: { - id: number; - node_id: string; - node_type: string; - updated: Date; - created: Date; - node_url: string; - user_id: number; - repository_id: number; - organization_id: number; - comment_id: number; - issue_id: number; - }; - users: { + node_id: string; + node_type: string; + updated: string; + created: string; + node_url: string; + user_id: number; + repository_id: number; + organization_id: number; + comment_id: number; + issue_id: number; + permit: { id: number; created: string; updated: string; - wallet_id: number; + amount: string; + nonce: string; + deadline: string; + signature: string; + token_id: number; + partner_id: null; + beneficiary_id: number; + transaction: string; location_id: number; - wallets: { + locations: { + id: number; + node_id: string; + node_type: string; + updated: string; + created: string; + node_url: string; + user_id: number; + repository_id: number; + organization_id: number; + comment_id: number; + issue_id: number; + }; + users: { + id: number; + created: string; + updated: string; + wallet_id: number; + location_id: number; + wallets: { + id: number; + created: string; + updated: null; + address: string; + location_id: null; + }; + }; + tokens: { id: number; created: string; - updated: Date | null; + updated: string; + network: number; address: string; - location_id: number | null; + location_id: null; }; - }; - tokens: { - id: 1; - created: string; - updated: string; - network: number; - address: string; - location_id: null | number; - }; + } | null; owner: string; repo: string; - network_id: number; } - -const permitList: Permit[] = []; +const issueList: Issue[] = []; const elemList: ElemInterface[] = []; @@ -117,14 +128,14 @@ class SmartQueue { } const { s: { ether, git, network }, - c: { amount }, + c, } = this._queue.get(key) as StandardInterface; // check for undefined if (git?.issue_number) { elemList.push({ id: git.issue_number, tx: ether?.txHash || TX_EMPTY_VALUE, // @TODO - handle this better - amount: ethers.utils.formatEther(amount), + amount: ethers.utils.formatEther(c?.amount || 0), title: git.issue_title, bounty_hunter: git.bounty_hunter, owner: git.owner, @@ -153,60 +164,74 @@ class SmartQueue { const updateQueue = new SmartQueue(); async function getPermitsForRepo(owner: string, repo: string) { - const permitList: Permit[] = []; try { const { data: gitData } = await octokit.rest.repos.get({ owner, repo, }); - const { data } = await supabase + + // Gets all the available issues for a given repository + const { data: issues, error: locationError } = await supabase.from("issues_view").select("*").eq("repository_id", gitData?.id).not("issue_id", "is", null); + if (locationError) { + throw locationError; + } + const issueIds = issues?.map((o) => o.issue_id); + + // Gets all the permits that are referenced by that list of issues + const { data: permits } = await supabase .from("permits") .select("*, locations(*), users(*, wallets(*)), tokens(*)") - .eq("locations.repository_id", gitData?.id) + .in("locations.issue_id", issueIds) .not("locations", "is", null); - if (data) { - permitList.push(...data.map((d) => ({ ...d, owner, repo }))); - } + + // Eventually links the permit to the matching issue + issues?.forEach((issue) => { + issue.permit = permits?.find((o) => issue.issue_id === o.locations.issue_id) || null; + issue.owner = owner; + issue.repo = repo; + }); + + return issues as Issue[]; } catch (error) { console.error(error); throw error; } - - return permitList; } async function fetchPermits(repoUrls: GitHubUrlParts[]) { try { - const permitsPromises = repoUrls.map((repoUrl) => getPermitsForRepo(repoUrl.owner, repoUrl.repo)); - const allPermits = await Promise.all(permitsPromises); + const issuePromises = repoUrls.map((repoUrl) => getPermitsForRepo(repoUrl.owner, repoUrl.repo)); + const allIssues = await Promise.all(issuePromises); - for (let i = 0; i < allPermits.length; i++) { - const issues = allPermits[i]; - permitList.push(...issues); - console.log(`Fetched ${issues.length} permits for repository ${repoUrls[i].owner}/${repoUrls[i].repo}`); + for (let i = 0; i < allIssues.length; i++) { + const issues = allIssues[i]; + issueList.push(...issues); + console.log(`Fetched ${issues.length} issues for repository ${repoUrls[i].owner}/${repoUrls[i].repo}`); } - for (const permit of permitList) { - const { data: userData } = await octokit.request("GET /user/:id", { id: permit.locations.user_id }); - const { node_url } = permit.locations; + for (const issue of issueList) { + const { data: userData } = await octokit.request("GET /user/:id", { id: issue.user_id }); + const { node_url } = issue; const lastSlashIndex = node_url.lastIndexOf("/"); - const hashIndex = node_url.lastIndexOf("#") || node_url.length; + const hashIndex = node_url.lastIndexOf("#") > 0 ? node_url.lastIndexOf("#") : node_url.length; const issueNumber = Number(node_url.substring(lastSlashIndex + 1, hashIndex)); const { data: issueData } = await octokit.rest.issues.get({ issue_number: issueNumber, - owner: permit.owner, - repo: permit.repo, + owner: issue.owner, + repo: issue.repo, }); - updateQueue.add(permit.signature, { - c: { - amount: permit.amount, - deadline: permit.deadline, - nonce: permit.nonce, - owner: permit.owner, - signature: permit.signature, - to: permit.users.wallets.address, - token: permit.tokens.address, - }, - k: permit.signature, + updateQueue.add(`${issue.id}`, { + c: issue.permit + ? { + amount: issue.permit.amount, + deadline: issue.permit.deadline, + nonce: issue.permit.nonce, + owner: issue.owner, + signature: issue.permit.signature, + to: issue.permit.users.wallets.address, + token: issue.permit.tokens.address, + } + : null, + k: issue.permit?.signature || "", s: { ether: undefined, git: { @@ -216,10 +241,10 @@ async function fetchPermits(repoUrls: GitHubUrlParts[]) { }, issue_number: issueData.number, issue_title: issueData.title, - owner: permit.owner, - repo: permit.repo, + owner: issue.owner, + repo: issue.repo, }, - network: getCurrency(permit.network_id) || Chain.Ethereum, + network: issue.permit ? getCurrency(issue.permit.tokens.network) || Chain.Ethereum : Chain.Ethereum, }, t: "git", }); @@ -228,11 +253,11 @@ async function fetchPermits(repoUrls: GitHubUrlParts[]) { console.error(`Error fetching issues: ${error}`); } - return permitList; + return issueList; } async function resetInit() { - permitList.splice(0, permitList.length); + issueList.splice(0, issueList.length); elemList.splice(0, elemList.length); repoArray.splice(0, repoArray.length); updateQueue.clear(); diff --git a/static/scripts/audit-report/types/audit.d.ts b/static/scripts/audit-report/types/audit.d.ts index 86c3b64c..ddf57574 100644 --- a/static/scripts/audit-report/types/audit.d.ts +++ b/static/scripts/audit-report/types/audit.d.ts @@ -83,7 +83,7 @@ export interface StandardInterface { to: string; deadline: string; signature: string; - }; + } | null; s: { ether: EtherInterface | undefined; git: GitInterface | undefined; From a0ab4685835bd285eb76beabdeb1a13e872ce84b Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 4 Mar 2024 13:43:51 +0900 Subject: [PATCH 6/6] chore: fixed merge conflicts in audit --- static/scripts/audit-report/audit.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index 39faa427..fb0e190e 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -4,28 +4,15 @@ import { createClient } from "@supabase/supabase-js"; import { ethers } from "ethers"; import { Chain } from "./constants"; import { - RateLimitOptions, getCurrency, getGitHubUrlPartsArray, populateTable, primaryRateLimitHandler, + RateLimitOptions, secondaryRateLimitHandler, TX_EMPTY_VALUE, } from "./helpers"; -import { - ChainScanResult, - ElemInterface, - EtherInterface, - GitHubUrlParts, - GitInterface, - GoDBSchema, - ObserverKeys, - QuickImport, - SavedData, - StandardInterface, - TxData, -} from "./types"; -import { getTxInfo } from "./utils/get-transaction"; +import { ElemInterface, EtherInterface, GitHubUrlParts, GitInterface, QuickImport, SavedData, StandardInterface } from "./types"; declare const SUPABASE_URL: string; declare const SUPABASE_ANON_KEY: string;