From 5ca068ffcab102a1d95673fcc09b7e6fb07e3857 Mon Sep 17 00:00:00 2001 From: Fernando Date: Sat, 2 Mar 2024 13:22:44 +0900 Subject: [PATCH 1/2] feat: fetch permits from database --- .gitignore | 3 +- static/index.html | 120 ++-- static/scripts/audit-report/audit.ts | 691 +++++-------------- static/scripts/audit-report/helpers.ts | 10 +- static/scripts/audit-report/types/audit.d.ts | 2 +- tsconfig.json | 2 +- 6 files changed, 221 insertions(+), 607 deletions(-) diff --git a/.gitignore b/.gitignore index 7791818..f41c58a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ node_modules .pnp.cjs .pnp.loader.mjs .env -static/dist \ No newline at end of file +static/dist +static/out diff --git a/static/index.html b/static/index.html index bf505dc..4806d36 100644 --- a/static/index.html +++ b/static/index.html @@ -1,73 +1,63 @@ - - Audit report - - - - + + Audit report + + + + - -
-

Audit report

- -
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - -
⚡Cache
-
-
- -
- - - - - - - - - - - - - - - -
-
- - - + > + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ + + + diff --git a/static/scripts/audit-report/audit.ts b/static/scripts/audit-report/audit.ts index ed9f862..fb0e190 100644 --- a/static/scripts/audit-report/audit.ts +++ b/static/scripts/audit-report/audit.ts @@ -1,35 +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; @@ -40,7 +23,6 @@ const rateOctokit = Octokit.plugin(throttling); let octokit: Octokit; -let BOT_WALLET_ADDRESS = ""; let REPOSITORY_URL = ""; let GITHUB_PAT = ""; @@ -50,242 +32,86 @@ 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 { +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 issueList: Issue[] = []; -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"); - 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; @@ -297,373 +123,168 @@ 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 || "N/A", // @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); } - } -} -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); + const { + s: { ether, git, network }, + 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(c?.amount || 0), + title: git.issue_title, + bounty_hunter: git.bounty_hunter, + owner: git.owner, + repo: git.repo, + network, + }); } - } - remove() { - const v = this._queue.shift(); - if (v) this._set.delete(v); + 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); + } + } } - read(): ChainScanResult { - return this._queue[0]; + get() { + return this._queue.values() as Readonly>; } - isEmpty() { - return this._queue.length === 0; + clear() { + this._queue.clear(); } } const updateQueue = new SmartQueue(); -const rpcQueue = new QueueSet(); 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 }))); - } - } catch (error) { - console.error(error); - throw error; - } - 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; - } -} + // 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; + }); -async function fetchDataFromChainScanAPI(url: string, chain: string) { - try { - const { data } = await axios.get(url); - return data.result.map((item: NonNullable) => ({ ...item, chain })); - } catch (error: unknown) { + return issues as Issue[]; + } catch (error) { 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); - } -} - -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; - } - } +async function fetchPermits(repoUrls: GitHubUrlParts[]) { + try { + const issuePromises = repoUrls.map((repoUrl) => getPermitsForRepo(repoUrl.owner, repoUrl.repo)); + const allIssues = await Promise.all(issuePromises); - if (isIEF) { - etherPageNumber++; - await etherFetcher(); - } else { - isEther = false; - finishedQueue.mutate("isEther", true); + 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}`); } - } 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", - c: { - nonce, - owner, - token, - amount, - to, - deadline, - signature, - }, + 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("#") > 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: issue.owner, + repo: issue.repo, + }); + 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: { - 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: issue.owner, + repo: issue.repo, }, - git: undefined, - network: chain as string, + network: issue.permit ? getCurrency(issue.permit.tokens.network) || Chain.Ethereum : 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 issueList; } async function resetInit() { - permitList.splice(0, permitList.length); - isGit = true; - isEther = true; - etherPageNumber = 1; - isRPC = true; + issueList.splice(0, issueList.length); 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); @@ -671,7 +292,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, diff --git a/static/scripts/audit-report/helpers.ts b/static/scripts/audit-report/helpers.ts index 38c620b..a5d9c07 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 ""; 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/scripts/audit-report/types/audit.d.ts b/static/scripts/audit-report/types/audit.d.ts index 86c3b64..ddf5757 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; diff --git a/tsconfig.json b/tsconfig.json index bb2f7f6..ac3d48b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ From 92e7dc87a755044ec8067a80314c82087ecdc756 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 5 Mar 2024 11:22:35 +0900 Subject: [PATCH 2/2] chore: fixed indent for index.html --- .github/workflows/knip.yml | 2 +- static/index.html | 112 ++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/workflows/knip.yml b/.github/workflows/knip.yml index df68bda..a16c31f 100644 --- a/.github/workflows/knip.yml +++ b/.github/workflows/knip.yml @@ -27,4 +27,4 @@ jobs: comment_id: ${{ github.workflow }}-reporter command_script_name: knip-ci annotations: true - ignore_results: false \ No newline at end of file + ignore_results: false diff --git a/static/index.html b/static/index.html index 4806d36..5e9fe9a 100644 --- a/static/index.html +++ b/static/index.html @@ -1,63 +1,63 @@ - - - Audit report - - - - + + + Audit report + + + + - -
-

Audit report

- -
- - -
-
- - -
-
- - -
-
- -
- -
- - - - - - - - - - - - - - - -
-
-
    - - + > + +
    + +
    + +
    + + + + + + + + + + + + + + + +
    + +
      + +