Skip to main content

Custom Detectors

info

This documentation page is a work-in-progress.

Vanguard provides a feature to allow users to create custom detectors to identify vulnerable code patterns using a concise query language. Custom detectors utilize Vanguard's advanced static analysis engine and are treated similarly to the built-in Vanguard detectors.

AuditHub includes a pre-defined "library" of custom detectors which catch simple vulnerabilities involving popular smart contract types, such as ERC20 tokens and ERC4626 vaults. You can also create your own custom detector if you want to catch specific vulnerability patterns in your project.

Using the Custom Detectors Library

info

Coming soon

Creating Your Own Custom Detector

info

Coming soon

Program Analysis Query Language (PAQL)

Search patterns are written in a language named Program Analysis Query Language, or PAQL for short. PAQL is similar in style to SQL and CodeQL, but Veridise engineers have designed it to be specialized for solving static analysis problems in DeFi and ZK applications.

Examples

The examples below demonstrate different aspects of PAQL's Solidity dialect.

Identify unsafe ERC20 transfers and approves

FIND ExternalCall c IN Function f WHERE
f.name != "safeTransfer" && f.name != "safeApprove",
c.signature == "transfer(address,uint256)" || c.name == "approve(address,uint256)",
AS
c

Detect simple reentrancy vulnerabilities

A simple reentrancy vulnerability is defined as a location where a storage write can occur after an external call in the same contract.

FIND
ExternalCall call IN Function f,
StorageWrite w IN call.after
WHERE
call.mayHaveUnknownTarget

The above query will also identify simple reentrancies where the call and the storage write occur in different functions, reporting both the call and involved write.

List state-modifying functions that may not be restricted to the owner

FIND
Function f,
WHERE
f.isExternallyCallable,
(EXISTS StorageWrite w IN f.reachable),
!EXISTS
RequireLike r IN f.reachable,
StorageRead owner IN r.backwardSlice
WHERE { owner.location == "owner" }

The above query finds functions that (1) are externally callable; (2) modify at least one storage location in its contract (including in other functions that are called); and (3) may not end up executing at least one "require-like" statement that checks some expression involving the owner storage variable. Vanguard considers a "require-like" statement to be any statement of the form require(condition) or if (!condition) revert() or if (!condition) throw Error().

Tutorial

In this section, we will explain how to write a PAQL search pattern using several examples.

Basic Search Patterns

A PAQL search pattern consists of a find statement of the form:

FIND
<variable declaration list>
WHERE
<clauses>

In the find statement, you must declare a list of variables describing what objects should be searched for, as well as a list of clauses that indicate the conditions that those variables must satisfy. For example, to list all externally callable functions named mint and the contract of that function, you can write the query:

FIND
Contract c,
Function f IN c,
WHERE
f.name == "mint",
f.visibility == "external" || f.visibility == "public",

Each variable declaration is written in the form <type> <name> or <type> <name> IN <expr>, where <type> is an object type (see below subsection) and <name> is a variable name typically written in camelCase form. In the above example, the Contract c declaration indicates that the system should search for a smart contract, and that a variable c will be set to the value of each contract during the search. The Function f IN c declaration similarly requests the system to search for functions, but drawn from a specific set "related" to c. The right-hand side of the IN is called the source expression and limits the search to the specified source.

The WHERE <clauses> section is used to filter the variables so that only the ones that satisfy all of the clauses are reported in the results. Each clause consists of an expression (see below subsection) that may evaluate to a boolean value. In the above example, the first clause checks that the name of the searched function f is equal to mint, and the second clause checks that f has either public or external visibility. f must satisfy both clauses in order to be reported.

To conclude this example, suppose our project consists of the following contracts and functions:

FooContract
function mint(uint256 amount) external
function mint(address who, uint256 amount) internal
function approve(address who, uint256 amount) external

BarContract
function mint(address who, uint256 amount) public

Executing our example query on the project will yield the following results:

2 Results

{
c: FooContract,
f: function mint(uint256 amount) external
},
{
c: BarContract,
f: function mint(address who, uint256 amount) public
}

Notes on Syntax

The WHERE <clauses> part can be omitted if no conditions are needed. For example, to simply list all functions in all contracts, the following query can be used:

FIND Contract c, Function f IN c

Also, since it is common to have source expressions consisting only of variables, such variable declarations can be written in a more concise chained form:

FIND Function f IN Contract c

Various types of code comments are supported in a search pattern:

// This is a single line comment.
-- This is also a single line comment.
/*
This is a
multiline comment
*/

Expressions

info

Documentation is a work-in-progress

Both source expressions and WHERE clauses use expressions, each of which represents some sort of computation of a value. For example, func.name == "mint" is an expression that, when executed, computes whether the name of a function referred to by func is equal to the string "mint".

In PAQL, a value is one of the following:

  • A string value, such as "mint"
  • A boolean value, either true or false
  • An integer value, such as 1 or 15
  • An object, representing a program construct. In the above examples, the contracts and functions are objects.

Expressions consist of values and operators which represent computations involving one or more expressions. A list of some basic expressions is shown below.

ExpressionExampleDescription
String, boolean, integer literal"hello", true, 15A constant value
e1 == e2"hello" == "goodbye"Check if e1 is equal to e2
e1 != e2"hello" != "goodbye"Check if e1 is not equal to e2
e1 && e2a == b && c == dLogical conjunction (and)
!e!(true && false)Logical negation (not)
e.ffunc.nameGet object property
f(e1, ..., en)regexMatch(func.name, "_.*")Invoke named operator

EXISTS expressions

An EXISTS <var declaration list> WHERE { <clauses> } expression is similar to a FIND statement, but evaluates to a boolean value indicating whether at least one combination of the declared variables can be found. This is particularly useful for checking when something is not done. For example, we could write a query that finds all of the contracts that do not define a mint method:

FIND Contract c WHERE
!EXISTS Function f IN c WHERE { f.name == "mint" },

If no clauses are needed, then the WHERE <clauses> part can be omitted; however, the EXISTS expression must then be wrapped in parentheses. For example, to find all contracts that do not declare any storage variables:

FIND Contract c WHERE
!(EXISTS StorageVar v IN c),

FIND projection

In some cases, it may be useful to exclude variables from the search results, rename the resulting variables, or to include extra expressions in the results. All of these may be achieved by adding an extra AS <name> = <expr>, ... section to the FIND statement. The AS section consists of a sequence of alias declarations of the form <name> = <expr> which explicitly sets the named expressions that are reported by the search pattern.

In the running example, we can use an AS section to assign friendlier names to the results:

FIND Function f IN Contract c WHERE
f.name == "mint",
f.visibility == "external" || f.visibility == "public",
AS
contract = c,
function = f,

This may be further shortened by making use of the contract property (see below subsection) of Function, which would allow us to omit the declaration of c:

FIND Function f WHERE
f.name == "mint",
f.visibility == "external" || f.visibility == "public",
AS
contract = f.contract,
function = f,

It is common to write alias declarations of the form name = name. Thus, a syntax sugar is provided so that a variable name by itself in an AS section is aliased to itself. For example, suppose we declare f as function instead:

FIND Function function WHERE
function.name == "mint",
function.visibility == "external" || function.visibility == "public",
AS
contract = f.contract
function, // equivalent to function = function

Types and Objects

info

Documentation is a work-in-progress

PAQL is a strongly-typed language in that every value and expression is typed, with strict rules on what operations can be performed based on type. In particular, PAQL supports two categories of types:

  • Primitive types, comprised of strings, booleans, and integers.
  • Object types, representing the types of objects. All declared variables must have object types as their types. Names of object types are typically written in UpperCase.

Each object has a type associated with it which defines the properties that may be accessed on the object. For example, the Function object type defines a name property that has a string type, which allows any function object named f to be used in the expression f.name to get the name of the function.

Furthermore, every object type supports a set of iterable types that can be "requested" when an object is used as a source expression in a FIND statement or EXISTS expression. For example, Contract supports Function as one of its iterable types, so that each function of a contract may be obtained by "iterating over" that contract:

FIND
Contract c,

// Contract object supports Function as an iterable type
Function f IN c,

// f.reachable returns an object that supports Function as an iterable type
Function g IN f.reachable,
...

Solidity Dialect Reference

info

Documentation coming soon