Remix Analyzer is the underlying library for the Remix IDE Solidity Static Analysis plugin. This means that it can be also used for other projects, not just for Remix IDE.
Since Hardhat is a popular contract development tool nowadays, it would be nice to make Remix Analyzer work with it. In the Remix code repository, this test file shows its basic usage, the key code:
const res: CompilationResult = compile('functionParameters.sol')
const Module: any = checksEffectsInteraction
const statRunner: StatRunner = new StatRunner()
...
const reports = statRunner.runWithModuleList(res, [{ name: new Module().name, mod: new Module() }])
The logic is simple: compile code > apply rules > output report.
Note that.
The Module in the code above is Remix Analyzer Rule class, all rules currently supported can be found in: this file.
Multiple Module objects can be passed to runWithModuleList
, if you want to apply multiple rules what you need to do is to initialize multiple Module instances.
Making a Hardhat Task
Using Remix Analyzer in Hardhat is a piece of cake: calling the script above from a Hardhat task. However, there are some tricky things if you just copy it as it is. If you look closely at the code above, you can see it first goes to download a Solidity compiler and then uses it to complete the contracts. In test setup
, there is a script like the following:
test('setup', function (t) {
solc.loadRemoteVersion('v0.5.0+commit.1d4f565a', (error, compiler) => {
...
Of course, there is no way to stop you from copying all code lines to your task script. But it is not a Hardhat style:
- Hardhat comes with a compiler, you should use it directly when you can, rather than downloading a new one.
- Following the previous step, it would be nice to get
CompilationResult
data from the compiled files directly. Then, we don't have to do any data transformation jobs.
As for 1
, Hardhat supports calling tasks within a task script, the code example:
await hre.run(TASK_COMPILE, { quiet: true });
As For 2
, it's simple too. Hardhat exposes fields to allow developers to access the artifacts in a project, including the compiled files. One thing to note here is that syntax analysis does not use the final artifacts but a middle one. That is, it needs artifacts/build-info
rather than artifacts/contracts
.
Also, a json file in artifacts/build-info
is not expected by Remix Analyzer which only uses part of it. By comparing the CompilationResult
type with the contents of the json, it is easy to see the connection: the output
field.
So, the code snippet for the task is like:
const runner = new staticAnalysisRunner();
await hre.run(TASK_COMPILE, { quiet: true });
console.log("√ compiled contracts.\n");
const compileResults = await hre.artifacts.getBuildInfoPaths();
compileResults.forEach((result) => {
const compiled = JSON.parse(fs.readFileSync(result).toString());
const source = Object.keys(compiled.input.sources)[0];
const modules = calculateRules(source, rulesConfig).map((Module: any) => {
return { name: new Module().name, mod: new Module() }
});
const reports = runner.runWithModuleList(compiled.output, modules);
...
})
The main logic here is:
- compile
- generate a report for each compiled file: parse json > compute rules > apply rules
Making a Hardhat Plugin
Syntax analysis is a highly reusable task and there is no reason why it cannot be a plugin.
As a dapp developer, you should really read its documentation if you still don't know what a Hardhat plugin is. With a plugin you can easily share configurations between projects, including custom tasks of course.
The steps recommended in the documentation: first testing the logic of a task in a Hardhat project; then moving it into a plugin project and wrapping it as a plugin. Now, we have completed the first step.
The documentation is very clear on how to build the plug-in, so I won't go into it again here: fork the plug-in template project and make some changes.
Here, I only highlight two things.
Dependencies
Following the rules in the documentation.
Extending Configuration
The documentation does not show too many details on this topic, rather gives a link to an example file. But I found that I have to read this file together to understand the logic.
After reading the code, I summarized the following rules:
- Extending both
XxxUserConfiguration
andXxxConfig
, where:- The fields added to the former are optional and the latter are required.
- Generating the latter based on the former during initialization. Or giving a default if the former is missing.
- When adding a new field to Hardhat existing configuration items, follow what the example did: use the corresponding Types. In the example, they are
ProjectPathsUserConfig
andProjectPathsConfig
. - To add a new top-level configuration item to Hardhat, use
HardhatUserConfig
andHardhatConfig
like below:
declare module "hardhat/types/config" {
interface HardhatUserConfig {
analyzerRules?: AnalyzerConfiguration;
}
interface HardhatConfig {
analyzerRules: AnalyzerConfiguration;
}
}
As for the rest, there's not much more to say. If you're interested, you can visit hardhat-remix-analyzer for more details.
Final Words
There are two other similar tools: slither and solhint.
After comparing among them, I personally recommend focusing on the latter two, as they offer more rules and are updated more frequently. Meanwhile, Hardhat has a plugin for solhint.
Top comments (0)