From e488b46f26d4d8b914c6a7b1b884d8c163c7b9a1 Mon Sep 17 00:00:00 2001 From: Rosco Kalis Date: Tue, 21 Jan 2025 17:39:46 +0100 Subject: [PATCH] feat: add two new risk factors --- .../allowances/dashboard/cells/SpenderCell.tsx | 17 +++++++++++++++-- .../dashboard/wallet-health/RiskTooltip.tsx | 14 +++++++------- lib/utils/risk.tsx | 2 ++ .../risk/OnchainSpenderRiskDataSource.ts | 6 ++++++ locales/en/address.json | 2 ++ locales/es/address.json | 6 ++++-- locales/ja/address.json | 6 ++++-- locales/ru/address.json | 2 ++ locales/zh/address.json | 2 ++ 9 files changed, 44 insertions(+), 13 deletions(-) diff --git a/components/allowances/dashboard/cells/SpenderCell.tsx b/components/allowances/dashboard/cells/SpenderCell.tsx index 5cfee68bc..98a1dff39 100644 --- a/components/allowances/dashboard/cells/SpenderCell.tsx +++ b/components/allowances/dashboard/cells/SpenderCell.tsx @@ -4,10 +4,12 @@ import Href from 'components/common/Href'; import Loader from 'components/common/Loader'; import WithHoverTooltip from 'components/common/WithHoverTooltip'; import { isNullish } from 'lib/utils'; -import type { TokenAllowanceData } from 'lib/utils/allowances'; +import { AllowanceType, type TokenAllowanceData } from 'lib/utils/allowances'; import { getChainExplorerUrl } from 'lib/utils/chains'; import { shortenAddress } from 'lib/utils/formatting'; +import { YEAR } from 'lib/utils/time'; import { getSpenderData } from 'lib/utils/whois'; +import { useMemo } from 'react'; import RiskTooltip from '../wallet-health/RiskTooltip'; interface Props { @@ -24,6 +26,17 @@ const SpenderCell = ({ allowance }: Props) => { enabled: !isNullish(allowance.payload?.spender), }); + // Add non-spender-specific risk factors (TODO: set up a proper system for this) + const riskFactors = useMemo(() => { + const factors = spenderData?.riskFactors ?? []; + + if (allowance.payload?.type === AllowanceType.PERMIT2 && allowance.payload.expiration > Date.now() + 1 * YEAR) { + return [...factors, { type: 'excessive_expiration', source: 'onchain' }]; + } + + return factors; + }, [allowance.payload, spenderData?.riskFactors]); + const explorerUrl = `${getChainExplorerUrl(allowance.chainId)}/address/${allowance.payload?.spender}`; if (!allowance.payload) return null; @@ -44,7 +57,7 @@ const SpenderCell = ({ allowance }: Props) => { - + ); diff --git a/components/allowances/dashboard/wallet-health/RiskTooltip.tsx b/components/allowances/dashboard/wallet-health/RiskTooltip.tsx index 70047fdc6..d3a68b931 100644 --- a/components/allowances/dashboard/wallet-health/RiskTooltip.tsx +++ b/components/allowances/dashboard/wallet-health/RiskTooltip.tsx @@ -1,25 +1,25 @@ import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import WithHoverTooltip from 'components/common/WithHoverTooltip'; -import type { Nullable, SpenderRiskData } from 'lib/interfaces'; +import type { RiskFactor } from 'lib/interfaces'; import { filterUnknownRiskFactors, getRiskLevel } from 'lib/utils/risk'; import { useTranslations } from 'next-intl'; import { twMerge } from 'tailwind-merge'; import RiskFactorDisplay from './RiskFactorDisplay'; interface Props { - riskData?: Nullable; + riskFactors?: RiskFactor[]; } -const RiskTooltip = ({ riskData }: Props) => { +const RiskTooltip = ({ riskFactors }: Props) => { const t = useTranslations(); - const filteredRiskFactors = filterUnknownRiskFactors(riskData?.riskFactors ?? []); + const filteredRiskFactors = filterUnknownRiskFactors(riskFactors ?? []); const riskLevel = getRiskLevel(filteredRiskFactors); // TODO: Properly handle low risk if (riskLevel === 'unknown' || riskLevel === 'low') return null; - const riskFactors = filteredRiskFactors.map((riskFactor) => ( + const riskFactorDisplays = filteredRiskFactors.map((riskFactor) => ( )); @@ -27,8 +27,8 @@ const RiskTooltip = ({ riskData }: Props) => {
{t('address.tooltips.risk_factors', { riskLevel: t(`address.risk_factors.levels.${riskLevel}`) })}
    - {riskFactors?.map((riskFactor) => ( -
  • {riskFactor}
  • + {riskFactorDisplays?.map((riskFactorDisplay) => ( +
  • {riskFactorDisplay}
  • ))}
diff --git a/lib/utils/risk.tsx b/lib/utils/risk.tsx index 2b5447836..9ba089f6e 100644 --- a/lib/utils/risk.tsx +++ b/lib/utils/risk.tsx @@ -8,9 +8,11 @@ export const RiskFactorScore: Record = { closed_source: 40, deprecated: 100, eoa: 100, + excessive_expiration: 60, exploit: 100, phishing_risk: 40, proxy: 20, + suspicious_address: 60, unsafe: 40, uninitialized: 40, }; diff --git a/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts b/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts index 5c99b83e1..2d49ef0ea 100644 --- a/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts +++ b/lib/whois/spender/risk/OnchainSpenderRiskDataSource.ts @@ -31,6 +31,7 @@ export class OnchainSpenderRiskDataSource implements SpenderDataSource { // if (this.isSmallBytecode(bytecode)) riskFactors.push({ type: 'unsafe', source: 'revoke' }); if (this.isOpenSeaProxy(bytecode)) riskFactors.push({ type: 'deprecated', source: 'onchain' }); if (this.hasPhishingRisk(address, bytecode)) riskFactors.push({ type: 'phishing_risk', source: 'onchain' }); + if (this.isSuspiciousAddress(address)) riskFactors.push({ type: 'suspicious_address', source: 'onchain' }); const elapsedTime = (new Date().getTime() - time) / 1000; console.log(elapsedTime, 'Onchain', address); @@ -76,6 +77,11 @@ export class OnchainSpenderRiskDataSource implements SpenderDataSource { return bytecode === OPENSEA_PROXY_BYTECODE; } + // Legitimate apps can sometimes have 0000 prefixes as an optimisation, but never end with 0000 as well + isSuspiciousAddress(address: Address): boolean { + return address.startsWith('0x0000') && address.endsWith('0000'); + } + // TODO: Add checker that gets recent approval / revoke logs and checks if many people are approving / revoking this address // - First needs the Approval event log normalisation // - Also needs registration of average block times for every supported chain diff --git a/locales/en/address.json b/locales/en/address.json index a5f56d0bf..998645d5e 100644 --- a/locales/en/address.json +++ b/locales/en/address.json @@ -81,6 +81,7 @@ "closed_source": "Closed source", "deprecated": "Deprecated or outdated", "eoa": "Externally Owned Account", + "excessive_expiration": "Excessive expiration", "exploit": "Involved in the {data}", "levels": { "high": "high risk", @@ -90,6 +91,7 @@ "phishing_risk": "Increases risk surface in case of phishing", "proxy": "Upgradeable proxy contract", "source": "reported by {source}", + "suspicious_address": "Suspicious-looking address", "uninitialized": "No contract deployed to this address", "unsafe": "Potentially unsafe contract code" }, diff --git a/locales/es/address.json b/locales/es/address.json index 620bd8e0f..4f590bbef 100644 --- a/locales/es/address.json +++ b/locales/es/address.json @@ -76,11 +76,12 @@ "expiration": "Caduca {inTime}" }, "risk_factors": { - "allowlist": "Well-known spender address", + "allowlist": "Dirección conocida del comprador", "blocklist": "Peligroso o malintencionado", "closed_source": "Código cerrado", "deprecated": "Obsoleto o desactualizado", "eoa": "Cuenta de Propiedad Externa", + "excessive_expiration": "Excessive expiration", "exploit": "Involucrado en la {data}", "levels": { "high": "alto riesgo", @@ -88,8 +89,9 @@ "medium": "riesgo medio" }, "phishing_risk": "Aumenta la superficie de riesgo en caso de phishing", - "proxy": "Upgradeable proxy contract", + "proxy": "Contrato de proxy actualizable", "source": "reportado por {source}", + "suspicious_address": "Suspicious-looking address", "uninitialized": "No se ha implementado ningún contrato en esta dirección", "unsafe": "Código de contrato potencialmente inseguro" }, diff --git a/locales/ja/address.json b/locales/ja/address.json index 63eb4dea4..11b89691b 100644 --- a/locales/ja/address.json +++ b/locales/ja/address.json @@ -76,11 +76,12 @@ "expiration": "{inTime}で期限が切れます" }, "risk_factors": { - "allowlist": "Well-known spender address", + "allowlist": "よく知られている支出者のアドレス", "blocklist": "危険または悪意のある", "closed_source": "ソースがクローズになっている", "deprecated": "非推奨または古い", "eoa": "外部所有口座", + "excessive_expiration": "Excessive expiration", "exploit": "{data}に関与していました", "levels": { "high": "高リスク", @@ -88,8 +89,9 @@ "medium": "中リスク" }, "phishing_risk": "フィッシングの場合のリスク領域が増加", - "proxy": "Upgradeable proxy contract", + "proxy": "アップグレード可能なプロキシ契約", "source": "{source}に報告された", + "suspicious_address": "Suspicious-looking address", "uninitialized": "この住所にはコントラクトがデプロイされていません", "unsafe": "安全でない可能性のある契約コード" }, diff --git a/locales/ru/address.json b/locales/ru/address.json index ed634c324..da41155df 100644 --- a/locales/ru/address.json +++ b/locales/ru/address.json @@ -81,6 +81,7 @@ "closed_source": "Закрытый исходный код", "deprecated": "Устаревший или неактуальный", "eoa": "Внешний аккаунт", + "excessive_expiration": "Excessive expiration", "exploit": "Участвует в {data}", "levels": { "high": "высокий риск", @@ -90,6 +91,7 @@ "phishing_risk": "Увеличивает поверхность риска в случае фишинга", "proxy": "Upgradeable proxy contract", "source": "сообщил(а){source}", + "suspicious_address": "Suspicious-looking address", "uninitialized": "Контракт по этому адресу не отправлен", "unsafe": "Потенциально небезопасный код контракта" }, diff --git a/locales/zh/address.json b/locales/zh/address.json index d2efdbe86..245f1e9f5 100644 --- a/locales/zh/address.json +++ b/locales/zh/address.json @@ -81,6 +81,7 @@ "closed_source": "闭源", "deprecated": "已弃用或已过时", "eoa": "外部持有账户", + "excessive_expiration": "Excessive expiration", "exploit": "涉及 {data}", "levels": { "high": "高风险", @@ -90,6 +91,7 @@ "phishing_risk": "增加钓鱼风险", "proxy": "Upgradeable proxy contract", "source": "举报人:{source}", + "suspicious_address": "Suspicious-looking address", "uninitialized": "没有部署到此地址的合约", "unsafe": "可能不安全的合约代码" },