-
Notifications
You must be signed in to change notification settings - Fork 157
What to do when tools report compilation errors
A recurring problem with Solidity programs is the selection of an appropriate Solidity compiler. Picking the wrong version may lead to compilation errors or unexpected program behavior. For a while now (since version 0.4.x?) the programmer is expected to specify the admissible compiler versions with the pragma solidity directive. However, in the context of SmartBugs some issues remain.
-
Occasionally, programmers get the specification wrong. Typical errors are to specify
>=0.x.yor^0.x.0when actually^0.x.yor just0.x.yis intended, where0.x.yis the version used for developing the contract. -
According to semantic versioning,
0.x.*versions should be upwards compatible, meaning e.g. that a program written for0.6.6should also compile with0.6.12. Unfortunately, the Solidity compiler does not strictly adhere to this rule. As an example, solc0.6.12may refuse to compile a program that was fine for0.6.6(think of multiple license specifications in the comments).(Note that semantic versioning handles
0.x.ydifferently fromx.y.z, for x>0. In both cases, upwards compatibility should be maintained for versions with the same x, even though x occurs in different positions.) -
In automated settings, analysis tools may try to guess and download a suitable solc version on the fly, but may fail to get it right.
-
New compiler versions make use of the instructions introduced with recent forks. Unmaintained tools may not know how to handle such instructions and either refuse to use the new compiler version or fail to analyze the bytecode generated by the new compiler.
When assembling the analysis tasks, SmartBugs inspects the source code and picks the first pragma solidity directive that does not occur as a comment or in a string. The prefix >=0 is rewritten to ^0. Then the highest solc version matching the directive is downloaded for the Linux platform (using a cache to avoid multiple downloads of the same version). The rationale for selecting the highest admissible version is to avoid problems with specifications like ^0.x.0, where the contract was actually tested with a version 0.x.y with y>0. A compiler for Linux is downloaded even if SmartBugs runs under Windows or macOS, as the compiler is used inside the Docker containers, which run under Linux.
As an example, we download the source code deployed at address 0x7ac55ac530f2C29659573Bde0700c6758D69e677 of Ethereum's main chain and store it in the file DemaxPair.sol (DemaxPair is the name of the contract deployed at the address). The source code contains several pragma solidity directives, all but one commented out:
// pragma solidity >=0.5.0;
// pragma solidity >=0.6.0;
// pragma solidity ^0.6.0;
// pragma solidity >=0.6.6;
// pragma solidity >=0.5.16;
pragma solidity >=0.6.6;SmartBugs picks the specification >=0.6.6, corrects it to ^0.6.6, and downloads version 0.6.12. It turns out that compilation fails, since DemaxPair.sol contains, as comments, conflicting license information. This is O.K. for 0.6.6 (compiles without warnings and errors), but not for 0.6.12 (fails with a compilation error). Solution: In the source, change >=0.6.6 to 0.6.6.
Now compilation is a bit better, but tools like Mythril still fail. The reason is that Mythril detects the required solc version with a faulty logic. The tool picks the first directive (in comments), extracts 0.5.0 as required version, and fails because of the mismatch with 0.6.6 or 0.6.12 offered by SmartBugs. Solution: specify the correct version near the beginning of the contract, by adding a line
pragma solidity 0.6.6;at the top of the source. Now the compiler provided by SmartBugs matches the version expected by Mythril (and compiles the contract without errors).
Analyzing bytecode has the disadvantage that weakness reports refer to bytecode addresses instead of line numbers in the source code. So the context of weaknesses is harder to understand. However, some tools focus on the analysis of bytecode alone and therefore cannot be used with source code. Moreover, unmaintained tools may refuse to analyze source code for new solc versions, but may still yield results for the bytecode.
To analyze deployment and runtime bytecodes, either download them from Etherscan, obtain them from an archive node, or generate them using the Solidity compiler (options --bin and --bin-runtime). The last option is quick, but the deployment code generated by the compiler may miss the constructor arguments used for the deployment to the Ethereum chain.
Under Linux/Unix, you can generate the bytecodes from the source by first installing a suitable compiler version and then running the following commands. The deployment bytecode will end up in DemaxPair.hex and the runtime bytecode in DemaxPair.rt.hex.
$ solc --version
solc, the solidity compiler commandline interface
Version: 0.6.6+commit.6c089d02.Linux.g++
$ F=DemaxPair.sol
$ C=DemaxPair
$ solc --bin ${F} | awk 's==2{print;s=0}/=== '"$F"':'"$C"' ===/{s+=1}/Binary/&&s==1{s+=1}' > ${F/.sol/.hex}
$ solc --bin-runtime ${F} | awk 's==2{print;s=0}/=== '"$F"':'"$C"' ===/{s+=1}/Binary/&&s==1{s+=1}' > ${F/.sol/.rt.hex}(Replace DemaxPair.sol and Demax by the name of your Solidity file and the name of the contract within the source that you want to analyze.)
Now you can run SmartBugs on all three versions of the contract, source and bytecodes, with the following command:
./smartbugs -t all -f DemaxPair.* --timeout 300 --mainThe option --main ensures that in the source code, only the contract named DemaxPair is analyzed, while the others are skipped. Some tools may run for a long time, so setting an adequate timeout (here 5 minutes) may be useful.
This note is the result of issue 194 raised by JerryPW, who also provided the link to the sample contract DemaxPair.sol.