Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ describe("getBlockaidBannerTitle", () => {
);
});

it("labels a simulation revert as likely to fail, not suspicious", () => {
expect(
getBlockaidBannerTitle(t, "transaction", SecurityLevel.LIKELY_TO_FAIL),
).toBe("This transaction is likely to fail");
});

it("uses a soft caution title for unable-to-scan (site gets its own copy)", () => {
expect(
getBlockaidBannerTitle(t, "token", SecurityLevel.UNABLE_TO_SCAN),
Expand Down
10 changes: 8 additions & 2 deletions extension/src/popup/components/BlockaidBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ export type BlockaidEntity =
/**
* Resolves the banner title from the flagged entity and its severity.
* UNABLE_TO_SCAN is a soft "proceed with caution" (site has its own copy).
* LIKELY_TO_FAIL is a simulation revert, not a risk flag.
* SAFE never produces a title.
*/
export const getBlockaidBannerTitle = (
t: TFunction,
entity: BlockaidEntity,
securityLevel: SecurityLevel,
): string => {
if (securityLevel === SecurityLevel.LIKELY_TO_FAIL) {
return t("This transaction is likely to fail");
}

if (securityLevel === SecurityLevel.UNABLE_TO_SCAN) {
return entity === "site"
? t("Unable to scan site for malicious behavior")
Expand Down Expand Up @@ -73,8 +78,9 @@ interface BlockaidBannerProps {

/**
* The single reusable Blockaid warning banner. Filled style, colored by
* severity (red for malicious, amber for suspicious / unable-to-scan), with a
* chevron when it opens a detail sheet. Renders nothing for a SAFE verdict.
* severity (red for malicious, amber for suspicious / likely-to-fail /
* unable-to-scan), with a chevron when it opens a detail sheet. Renders
* nothing for a SAFE verdict.
*/
export const BlockaidBanner = ({
securityLevel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,11 @@ export const ReviewTx = ({
const isMalicious = securityLevel === SecurityLevel.MALICIOUS;
const isSuspicious = securityLevel === SecurityLevel.SUSPICIOUS;

// Determine if a security warning should be shown (tx- or token-driven)
// Determine if a warning should be shown (tx risk, a revert, or a token).
const shouldShowTxWarning =
isMalicious ||
isSuspicious ||
securityLevel === SecurityLevel.LIKELY_TO_FAIL ||
securityLevel === SecurityLevel.UNABLE_TO_SCAN;

// Banner copy for a flagged destination token (null when the token is clean
Expand Down Expand Up @@ -289,10 +290,9 @@ export const ReviewTx = ({

// Which single Blockaid banner to render, by priority cascade:
// tx-malicious > tx-suspicious > token-malicious > token-suspicious >
// any unable-to-scan. A flagged TOKEN outranks a tx that merely couldn't be
// scanned (common on mainnet), so a malicious-token warning is never
// downgraded to the soft "proceed with caution". Only the tx banner opens
// the expandable pane.
// tx likely-to-fail > any unable-to-scan. A flagged token outranks a revert
// and a tx that merely couldn't be scanned, so a malicious-token warning is
// never downgraded. Only the tx banner opens the expandable pane.
const blockaidBannerKind: "tx" | "token" | null = (() => {
if (
txSecurityLevel === SecurityLevel.MALICIOUS ||
Expand All @@ -306,6 +306,9 @@ export const ReviewTx = ({
) {
return "token";
}
if (txSecurityLevel === SecurityLevel.LIKELY_TO_FAIL) {
return "tx";
}
if (txSecurityLevel && shouldShowTxWarning) {
return "tx"; // tx unable-to-scan
}
Expand Down
23 changes: 19 additions & 4 deletions extension/src/popup/components/WarningMessages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -905,14 +905,29 @@ export const BlockAidScanExpanded = ({
return null;
}

// A revert with no risk verdict is a failure prediction. The raw simulation
// error is already one of the rows; the title and subtitle should not call
// it unsafe.
const isSimulationFailureOnly =
!mergedIsMalicious &&
!mergedIsSuspicious &&
Comment thread
JakeUrban marked this conversation as resolved.
Resolved
!isAssetScan &&
!!scanResult &&
"simulation" in scanResult &&
!!scanResult.simulation &&
"error" in scanResult.simulation;
const title = mergedIsMalicious
? t("Do not proceed")
: mergedIsSuspicious
? t("Suspicious Request")
: t("Proceed with caution");
const subtitle = isAssetScan
? t("This token does not appear safe for the following reasons.")
: t("This transaction does not appear safe for the following reasons.");
: isSimulationFailureOnly
? t("Likely to fail")
: t("Proceed with caution");
const subtitle = isSimulationFailureOnly
? t("This transaction is expected to fail for the following reasons.")
: isAssetScan
? t("This token does not appear safe for the following reasons.")
: t("This transaction does not appear safe for the following reasons.");
return (
<div className="BlockaidDetailsExpanded">
<SheetHeader
Expand Down
15 changes: 15 additions & 0 deletions extension/src/popup/constants/__tests__/blockaid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ describe("mergeSecurityLevels", () => {
).toBe(SecurityLevel.MALICIOUS);
});

it("ranks a revert above UNABLE_TO_SCAN and below SUSPICIOUS", () => {
expect(
mergeSecurityLevels([
SecurityLevel.UNABLE_TO_SCAN,
SecurityLevel.LIKELY_TO_FAIL,
]),
).toBe(SecurityLevel.LIKELY_TO_FAIL);
expect(
mergeSecurityLevels([
SecurityLevel.LIKELY_TO_FAIL,
SecurityLevel.SUSPICIOUS,
]),
).toBe(SecurityLevel.SUSPICIOUS);
});

it("ranks SUSPICIOUS above UNABLE_TO_SCAN", () => {
expect(
mergeSecurityLevels([
Expand Down
6 changes: 4 additions & 2 deletions extension/src/popup/constants/blockaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum SecurityLevel {
SUSPICIOUS = "SUSPICIOUS",
MALICIOUS = "MALICIOUS",
UNABLE_TO_SCAN = "UNABLE_TO_SCAN",
LIKELY_TO_FAIL = "LIKELY_TO_FAIL",
}

/**
Expand All @@ -28,8 +29,9 @@ export interface BlockaidWarning {
const SECURITY_LEVEL_SEVERITY: Record<SecurityLevel, number> = {
[SecurityLevel.SAFE]: 0,
[SecurityLevel.UNABLE_TO_SCAN]: 1,
[SecurityLevel.SUSPICIOUS]: 2,
[SecurityLevel.MALICIOUS]: 3,
[SecurityLevel.LIKELY_TO_FAIL]: 2,
[SecurityLevel.SUSPICIOUS]: 3,
[SecurityLevel.MALICIOUS]: 4,
};

/**
Expand Down
46 changes: 44 additions & 2 deletions extension/src/popup/helpers/__tests__/blockaid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
shouldTreatAssetAsUnableToScan,
isTxSuspicious,
useIsTxSuspicious,
getTransactionSecurityLevel,
useShouldTreatTxAsUnableToScan,
isBlockaidEnabled,
getSiteSecurityStates,
Expand Down Expand Up @@ -222,15 +223,15 @@ describe("BlockAid Helper Functions", () => {
expect(isTxSuspicious(null, null)).toBe(false);
});

it("should return true when simulation has error", () => {
it("should return false when the only finding is a simulation error", () => {
expect(
isTxSuspicious(
{
simulation: { error: "test error" },
} as BlockAidScanTxResult,
null,
),
).toBe(true);
).toBe(false);
});

it("should return true when validation result_type is not Benign", () => {
Expand Down Expand Up @@ -286,6 +287,47 @@ describe("BlockAid Helper Functions", () => {
});
});

describe("getTransactionSecurityLevel", () => {
it("classifies a simulation revert as likely to fail", () => {
expect(
getTransactionSecurityLevel(
{
simulation: { error: "insufficient balance for asset USDC" },
validation: null,
} as unknown as BlockAidScanTxResult,
false,
null,
),
).toBe(SecurityLevel.LIKELY_TO_FAIL);
});

it("keeps a malicious verdict when the simulation also reverted", () => {
expect(
getTransactionSecurityLevel(
{
simulation: { error: "insufficient balance for asset USDC" },
validation: { result_type: "Malicious" },
} as unknown as BlockAidScanTxResult,
false,
null,
),
).toBe(SecurityLevel.MALICIOUS);
});

it("keeps a warning verdict as suspicious", () => {
expect(
getTransactionSecurityLevel(
{
simulation: {},
validation: { result_type: "Warning" },
} as unknown as BlockAidScanTxResult,
false,
null,
),
).toBe(SecurityLevel.SUSPICIOUS);
});
});

describe("useIsTxSuspicious hook", () => {
beforeEach(() => {
process.env.DEV_EXTENSION = "true";
Expand Down
24 changes: 11 additions & 13 deletions extension/src/popup/helpers/blockaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,7 @@ export const isTxSuspicious = (
return false;
}

const { simulation, validation } = blockaidData;

if (simulation && "error" in simulation) {
return true;
}
const { validation } = blockaidData;

if (
validation &&
Expand Down Expand Up @@ -728,9 +724,11 @@ export const getSiteSecurityLevel = ({

/**
* Collapses a transaction Blockaid scan into a single SecurityLevel (or null
* when nothing is flagged) for the reusable BlockaidBanner. A simulation error
* is treated as suspicious. The dev override takes precedence. Shared by the
* swap review and the dApp sign-transaction flow so both read one source.
* when nothing is flagged) for the reusable BlockaidBanner. A risk verdict
* wins over a simulation revert: Malicious and Warning stay malicious /
* suspicious, and a revert with no risk verdict is LIKELY_TO_FAIL. The dev
* override takes precedence. Shared by the swap review and the dApp
* sign-transaction flow so both read one source.
*/
export const getTransactionSecurityLevel = (
txScanResult: BlockAidScanTxResult | null | undefined,
Expand All @@ -748,11 +746,6 @@ export const getTransactionSecurityLevel = (

const { simulation, validation } = txScanResult;

// Handle simulation error - treat as suspicious
if (simulation && "error" in simulation) {
return SecurityLevel.SUSPICIOUS;
}

if (validation && "result_type" in validation) {
const resultType = validation.result_type;
if (resultType === "Malicious") {
Expand All @@ -763,6 +756,11 @@ export const getTransactionSecurityLevel = (
}
}

// A revert is "this will fail". It is not a risk verdict.
if (simulation && "error" in simulation) {
return SecurityLevel.LIKELY_TO_FAIL;
}

if (isUnableToScan) {
return SecurityLevel.UNABLE_TO_SCAN;
}
Expand Down
3 changes: 3 additions & 0 deletions extension/src/popup/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
"Leave feedback about Blockaid warnings and messages": "Leave feedback about Blockaid warnings and messages",
"Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.": "Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.",
"Let’s go": "Let’s go",
"Likely to fail": "Likely to fail",
"Limit": "Limit",
"Limited trading data": "Limited trading data",
"Links": "Links",
Expand Down Expand Up @@ -703,6 +704,8 @@
"This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.": "This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.",
"This transaction does not appear safe for the following reasons": "This transaction does not appear safe for the following reasons",
"This transaction does not appear safe for the following reasons.": "This transaction does not appear safe for the following reasons.",
"This transaction is expected to fail for the following reasons.": "This transaction is expected to fail for the following reasons.",
"This transaction is likely to fail": "This transaction is likely to fail",
"This transaction was flagged as malicious": "This transaction was flagged as malicious",
"This transaction was flagged as malicious (override active)": "This transaction was flagged as malicious (override active)",
"This transaction was flagged as suspicious": "This transaction was flagged as suspicious",
Expand Down
3 changes: 3 additions & 0 deletions extension/src/popup/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
"Leave feedback about Blockaid warnings and messages": "Deixar feedback sobre avisos e mensagens do Blockaid",
"Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.": "O Ledger não exibirá os detalhes da transação na tela do dispositivo antes de assinar, então certifique-se de interagir apenas com aplicativos que você conhece e confia.",
"Let’s go": "Vamos lá",
"Likely to fail": "Provável falha",
"Limit": "Limite",
"Limited trading data": "Dados de negociação limitados",
"Links": "Links",
Expand Down Expand Up @@ -703,6 +704,8 @@
"This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.": "Esta transação desativa a chave mestra da sua conta. Você pode perder permanentemente o acesso a esta conta, a menos que outro assinante com peso suficiente seja adicionado.",
"This transaction does not appear safe for the following reasons": "Esta transação não parece segura pelos seguintes motivos",
"This transaction does not appear safe for the following reasons.": "Esta transação não parece segura pelos seguintes motivos.",
"This transaction is expected to fail for the following reasons.": "Esta transação deve falhar pelos seguintes motivos.",
"This transaction is likely to fail": "Esta transação provavelmente vai falhar",
"This transaction was flagged as malicious": "Esta transação foi marcada como maliciosa",
"This transaction was flagged as malicious (override active)": "Esta transação foi sinalizada como maliciosa (substituição ativa)",
"This transaction was flagged as suspicious": "Esta transação foi marcada como suspeita",
Expand Down
30 changes: 26 additions & 4 deletions extension/src/popup/views/SignTransaction/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ import {
MemoRequiredLabel,
} from "popup/components/WarningMessages";
import { BlockaidBanner } from "popup/components/BlockaidBanner";
import { SecurityLevel, mergeSecurityLevels } from "popup/constants/blockaid";
import {
BlockaidWarning,
SecurityLevel,
mergeSecurityLevels,
} from "popup/constants/blockaid";
import {
useShouldTreatTxAsUnableToScan,
useIsTxSuspicious,
Expand Down Expand Up @@ -322,8 +326,9 @@ export const SignTransaction = () => {
(scanResult.validation.result_type === "Malicious" ||
scanResult.validation.result_type === "Warning")
);
const hasSimulationError =
scanResult && scanResult.simulation && "error" in scanResult.simulation;
const hasSimulationError = !!(
scanResult?.simulation && "error" in scanResult.simulation
);
const showBlockAidDetails =
isUnableToScan ||
hasSimulationError ||
Expand All @@ -333,7 +338,6 @@ export const SignTransaction = () => {
(scanResult?.validation &&
"result_type" in scanResult.validation &&
scanResult.validation.result_type === "Malicious") ||
hasSimulationError ||
isSiteMalicious;

// Single Blockaid verdict for the banner: the worst of the transaction scan
Expand All @@ -357,6 +361,22 @@ export const SignTransaction = () => {
]);
const blockaidBannerEntity = txSecurityLevel ? "transaction" : "site";

// The details sheet only receives the transaction scan, so carry the site
// verdict into it. Otherwise a reverting tx from a malicious site would open
// a "Likely to fail" sheet that never mentions the site risk the banner
// reported.
const isSiteFlagged = isSiteMalicious || isSiteSuspicious;
const blockaidSheetSiteWarnings: BlockaidWarning[] = isSiteFlagged
? [
{
description: isSiteMalicious
? t("This site was flagged as malicious")
: t("This site was flagged as suspicious"),
isError: isSiteMalicious,
},
]
: [];

if (_networkPassphrase !== networkPassphrase) {
return (
<WarningMessage
Expand Down Expand Up @@ -562,6 +582,8 @@ export const SignTransaction = () => {
{isOnBlockaidSheet ? (
<BlockAidScanExpanded
scanResult={scanResult}
extraWarnings={blockaidSheetSiteWarnings}
extraSeverityLevel={isSiteFlagged ? siteSecurityLevel : null}
onClose={() => setIsOnBlockaidSheet(false)}
/>
) : (
Expand Down
Loading
Loading